]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'prev-next-button' of https://github.com/shubhamosmosys/BookStack into...
authorDan Brown <redacted>
Wed, 26 May 2021 21:13:19 +0000 (22:13 +0100)
committerDan Brown <redacted>
Wed, 26 May 2021 21:13:19 +0000 (22:13 +0100)
486 files changed:
.env.example.complete
.github/ISSUE_TEMPLATE/config.yml [new file with mode: 0644]
.github/translators.txt
.github/workflows/phpunit.yml
.github/workflows/test-migrations.yml
app/Actions/Activity.php
app/Actions/ActivityService.php
app/Actions/ActivityType.php
app/Actions/Favourite.php [new file with mode: 0644]
app/Actions/Tag.php
app/Actions/TagRepo.php
app/Actions/View.php
app/Actions/ViewService.php [deleted file]
app/Api/ApiDocsGenerator.php
app/Api/ApiTokenGuard.php
app/Auth/Access/Guards/ExternalBaseSessionGuard.php
app/Auth/Access/Guards/LdapSessionGuard.php
app/Auth/Access/Guards/Saml2SessionGuard.php
app/Auth/Access/Ldap.php
app/Auth/Access/LdapService.php
app/Auth/Access/RegistrationService.php
app/Auth/Access/Saml2Service.php
app/Auth/Access/SocialAuthService.php
app/Auth/Permissions/PermissionService.php
app/Auth/User.php
app/Auth/UserRepo.php
app/Config/app.php
app/Config/database.php
app/Config/dompdf.php
app/Config/mail.php
app/Config/saml2.php
app/Config/services.php
app/Config/session.php
app/Config/setting-defaults.php
app/Console/Commands/ClearViews.php
app/Console/Commands/UpdateUrl.php
app/Entities/Models/BookChild.php
app/Entities/Models/Entity.php
app/Entities/Models/Page.php
app/Entities/Queries/EntityQuery.php [new file with mode: 0644]
app/Entities/Queries/Popular.php [new file with mode: 0644]
app/Entities/Queries/RecentlyViewed.php [new file with mode: 0644]
app/Entities/Queries/TopFavourites.php [new file with mode: 0644]
app/Entities/Repos/PageRepo.php
app/Entities/Tools/Markdown/CustomStrikeThroughExtension.php
app/Entities/Tools/Markdown/CustomStrikethroughRenderer.php
app/Entities/Tools/PageContent.php
app/Entities/Tools/SearchOptions.php
app/Entities/Tools/SearchRunner.php
app/Entities/Tools/SlugGenerator.php
app/Entities/Tools/TrashCan.php
app/Exceptions/ApiAuthException.php
app/Exceptions/Handler.php
app/Exceptions/JsonDebugException.php
app/Exceptions/NotFoundException.php
app/Exceptions/NotifyException.php
app/Exceptions/PrettyException.php
app/Exceptions/UnauthorizedException.php
app/Facades/Setting.php [deleted file]
app/Facades/Theme.php [moved from app/Facades/Images.php with 70% similarity]
app/Facades/Views.php [deleted file]
app/Http/Controllers/Api/ApiController.php
app/Http/Controllers/Api/ApiDocsController.php
app/Http/Controllers/Api/BookApiController.php
app/Http/Controllers/Api/BookshelfApiController.php
app/Http/Controllers/AuditLogController.php
app/Http/Controllers/Auth/ConfirmEmailController.php
app/Http/Controllers/Auth/LoginController.php
app/Http/Controllers/Auth/RegisterController.php
app/Http/Controllers/Auth/Saml2Controller.php
app/Http/Controllers/Auth/SocialController.php
app/Http/Controllers/Auth/UserInviteController.php
app/Http/Controllers/BookController.php
app/Http/Controllers/BookshelfController.php
app/Http/Controllers/ChapterController.php
app/Http/Controllers/Controller.php
app/Http/Controllers/FavouriteController.php [new file with mode: 0644]
app/Http/Controllers/HomeController.php
app/Http/Controllers/Images/ImageController.php
app/Http/Controllers/PageController.php
app/Http/Controllers/SearchController.php
app/Http/Controllers/UserApiTokenController.php
app/Http/Controllers/UserController.php
app/Http/Controllers/UserProfileController.php [new file with mode: 0644]
app/Http/Kernel.php
app/Http/Middleware/ChecksForEmailConfirmation.php
app/Http/Middleware/Localization.php
app/Http/Middleware/RunThemeActions.php [new file with mode: 0644]
app/Http/Middleware/ThrottleApiRequests.php
app/Interfaces/Favouritable.php [new file with mode: 0644]
app/Interfaces/Loggable.php
app/Interfaces/Sluggable.php [new file with mode: 0644]
app/Interfaces/Viewable.php [new file with mode: 0644]
app/Providers/AppServiceProvider.php
app/Providers/CustomFacadeProvider.php
app/Providers/CustomValidationServiceProvider.php
app/Providers/ThemeServiceProvider.php [new file with mode: 0644]
app/Providers/TranslationServiceProvider.php
app/Settings/SettingService.php
app/Theming/ThemeEvents.php [new file with mode: 0644]
app/Theming/ThemeService.php [new file with mode: 0644]
app/Traits/HasCreatorAndUpdater.php
app/Traits/HasOwner.php
app/Translation/FileLoader.php
app/Uploads/AttachmentService.php
app/Uploads/Image.php
app/Uploads/ImageRepo.php
app/Uploads/ImageService.php
app/Uploads/UserAvatars.php
app/Util/HtmlContentFilter.php [new file with mode: 0644]
app/helpers.php
composer.json
composer.lock
database/factories/ModelFactory.php
database/migrations/2017_03_19_091553_create_search_index_table.php
database/migrations/2018_07_15_173514_add_role_external_auth_id.php
database/migrations/2018_08_04_115700_create_bookshelves_table.php
database/migrations/2021_01_30_225441_add_settings_type_column.php [new file with mode: 0644]
database/migrations/2021_03_08_215138_add_user_slug.php [new file with mode: 0644]
database/migrations/2021_05_15_173110_create_favourites_table.php [new file with mode: 0644]
dev/docker/Dockerfile
dev/docker/init.db/01.sql [new file with mode: 0644]
dev/docs/logical-theme-system.md [new file with mode: 0644]
dev/docs/visual-theme-system.md [new file with mode: 0644]
docker-compose.yml
package-lock.json
package.json
phpcs.xml
phpunit.xml
readme.md
resources/icons/star-circle.svg
resources/icons/star-outline.svg [new file with mode: 0644]
resources/icons/tag.svg
resources/js/components/dropdown.js
resources/js/components/entity-selector.js
resources/js/components/header-mobile-toggle.js
resources/js/components/markdown-editor.js
resources/js/components/page-display.js
resources/js/components/tri-layout.js
resources/js/components/user-select.js
resources/js/components/wysiwyg-editor.js
resources/js/services/code.js
resources/js/services/drawio.js
resources/js/services/http.js
resources/lang/ar/activities.php
resources/lang/ar/auth.php
resources/lang/ar/common.php
resources/lang/ar/components.php
resources/lang/ar/entities.php
resources/lang/ar/errors.php
resources/lang/ar/settings.php
resources/lang/ar/validation.php
resources/lang/bg/common.php
resources/lang/bg/entities.php
resources/lang/bg/errors.php
resources/lang/bg/settings.php
resources/lang/bg/validation.php
resources/lang/bs/activities.php [new file with mode: 0644]
resources/lang/bs/auth.php [new file with mode: 0644]
resources/lang/bs/common.php [new file with mode: 0644]
resources/lang/bs/components.php [new file with mode: 0644]
resources/lang/bs/entities.php [new file with mode: 0644]
resources/lang/bs/errors.php [new file with mode: 0644]
resources/lang/bs/pagination.php [new file with mode: 0644]
resources/lang/bs/passwords.php [new file with mode: 0644]
resources/lang/bs/settings.php [new file with mode: 0644]
resources/lang/bs/validation.php [new file with mode: 0644]
resources/lang/ca/activities.php [new file with mode: 0644]
resources/lang/ca/auth.php [new file with mode: 0644]
resources/lang/ca/common.php [new file with mode: 0644]
resources/lang/ca/components.php [new file with mode: 0644]
resources/lang/ca/entities.php [new file with mode: 0644]
resources/lang/ca/errors.php [new file with mode: 0644]
resources/lang/ca/pagination.php [new file with mode: 0644]
resources/lang/ca/passwords.php [new file with mode: 0644]
resources/lang/ca/settings.php [new file with mode: 0755]
resources/lang/ca/validation.php [new file with mode: 0644]
resources/lang/cs/common.php
resources/lang/cs/entities.php
resources/lang/cs/errors.php
resources/lang/cs/settings.php
resources/lang/cs/validation.php
resources/lang/da/activities.php
resources/lang/da/auth.php
resources/lang/da/common.php
resources/lang/da/components.php
resources/lang/da/entities.php
resources/lang/da/errors.php
resources/lang/da/settings.php
resources/lang/da/validation.php
resources/lang/de/common.php
resources/lang/de/entities.php
resources/lang/de/errors.php
resources/lang/de/settings.php
resources/lang/de/validation.php
resources/lang/de_informal/activities.php
resources/lang/de_informal/common.php
resources/lang/de_informal/entities.php
resources/lang/de_informal/errors.php
resources/lang/de_informal/settings.php
resources/lang/de_informal/validation.php
resources/lang/en/activities.php
resources/lang/en/common.php
resources/lang/en/entities.php
resources/lang/en/errors.php
resources/lang/en/settings.php
resources/lang/en/validation.php
resources/lang/es/common.php
resources/lang/es/entities.php
resources/lang/es/errors.php
resources/lang/es/settings.php
resources/lang/es/validation.php
resources/lang/es_AR/common.php
resources/lang/es_AR/entities.php
resources/lang/es_AR/errors.php
resources/lang/es_AR/settings.php
resources/lang/es_AR/validation.php
resources/lang/fa/activities.php
resources/lang/fa/common.php
resources/lang/fa/entities.php
resources/lang/fa/errors.php
resources/lang/fa/settings.php
resources/lang/fa/validation.php
resources/lang/fr/common.php
resources/lang/fr/entities.php
resources/lang/fr/errors.php
resources/lang/fr/settings.php
resources/lang/fr/validation.php
resources/lang/he/common.php
resources/lang/he/components.php
resources/lang/he/entities.php
resources/lang/he/errors.php
resources/lang/he/passwords.php
resources/lang/he/settings.php
resources/lang/he/validation.php
resources/lang/hu/common.php
resources/lang/hu/entities.php
resources/lang/hu/errors.php
resources/lang/hu/settings.php
resources/lang/hu/validation.php
resources/lang/id/activities.php [new file with mode: 0644]
resources/lang/id/auth.php [new file with mode: 0644]
resources/lang/id/common.php [new file with mode: 0644]
resources/lang/id/components.php [new file with mode: 0644]
resources/lang/id/entities.php [new file with mode: 0644]
resources/lang/id/errors.php [new file with mode: 0644]
resources/lang/id/pagination.php [new file with mode: 0644]
resources/lang/id/passwords.php [new file with mode: 0644]
resources/lang/id/settings.php [new file with mode: 0644]
resources/lang/id/validation.php [new file with mode: 0644]
resources/lang/it/activities.php
resources/lang/it/common.php
resources/lang/it/components.php
resources/lang/it/entities.php
resources/lang/it/errors.php
resources/lang/it/passwords.php
resources/lang/it/settings.php
resources/lang/it/validation.php
resources/lang/ja/common.php
resources/lang/ja/entities.php
resources/lang/ja/errors.php
resources/lang/ja/settings.php
resources/lang/ja/validation.php
resources/lang/ko/activities.php
resources/lang/ko/common.php
resources/lang/ko/entities.php
resources/lang/ko/errors.php
resources/lang/ko/settings.php
resources/lang/ko/validation.php
resources/lang/lv/activities.php [new file with mode: 0644]
resources/lang/lv/auth.php [new file with mode: 0644]
resources/lang/lv/common.php [new file with mode: 0644]
resources/lang/lv/components.php [new file with mode: 0644]
resources/lang/lv/entities.php [new file with mode: 0644]
resources/lang/lv/errors.php [new file with mode: 0644]
resources/lang/lv/pagination.php [new file with mode: 0644]
resources/lang/lv/passwords.php [new file with mode: 0644]
resources/lang/lv/settings.php [new file with mode: 0644]
resources/lang/lv/validation.php [new file with mode: 0644]
resources/lang/nb/activities.php
resources/lang/nb/common.php
resources/lang/nb/entities.php
resources/lang/nb/errors.php
resources/lang/nb/settings.php
resources/lang/nb/validation.php
resources/lang/nl/activities.php
resources/lang/nl/auth.php
resources/lang/nl/common.php
resources/lang/nl/components.php
resources/lang/nl/entities.php
resources/lang/nl/errors.php
resources/lang/nl/settings.php
resources/lang/nl/validation.php
resources/lang/pl/activities.php
resources/lang/pl/common.php
resources/lang/pl/entities.php
resources/lang/pl/errors.php
resources/lang/pl/settings.php
resources/lang/pl/validation.php
resources/lang/pt/activities.php
resources/lang/pt/auth.php
resources/lang/pt/common.php
resources/lang/pt/components.php
resources/lang/pt/entities.php
resources/lang/pt/errors.php
resources/lang/pt/pagination.php
resources/lang/pt/passwords.php
resources/lang/pt/settings.php
resources/lang/pt/validation.php
resources/lang/pt_BR/activities.php
resources/lang/pt_BR/common.php
resources/lang/pt_BR/entities.php
resources/lang/pt_BR/errors.php
resources/lang/pt_BR/settings.php
resources/lang/pt_BR/validation.php
resources/lang/ru/common.php
resources/lang/ru/entities.php
resources/lang/ru/errors.php
resources/lang/ru/settings.php
resources/lang/ru/validation.php
resources/lang/sk/common.php
resources/lang/sk/entities.php
resources/lang/sk/errors.php
resources/lang/sk/settings.php
resources/lang/sk/validation.php
resources/lang/sl/common.php
resources/lang/sl/entities.php
resources/lang/sl/errors.php
resources/lang/sl/settings.php
resources/lang/sl/validation.php
resources/lang/sv/common.php
resources/lang/sv/entities.php
resources/lang/sv/errors.php
resources/lang/sv/settings.php
resources/lang/sv/validation.php
resources/lang/th/validation.php
resources/lang/tr/common.php
resources/lang/tr/entities.php
resources/lang/tr/errors.php
resources/lang/tr/settings.php
resources/lang/tr/validation.php
resources/lang/uk/activities.php
resources/lang/uk/common.php
resources/lang/uk/entities.php
resources/lang/uk/errors.php
resources/lang/uk/settings.php
resources/lang/uk/validation.php
resources/lang/vi/activities.php
resources/lang/vi/common.php
resources/lang/vi/entities.php
resources/lang/vi/errors.php
resources/lang/vi/settings.php
resources/lang/vi/validation.php
resources/lang/zh_CN/auth.php
resources/lang/zh_CN/common.php
resources/lang/zh_CN/entities.php
resources/lang/zh_CN/errors.php
resources/lang/zh_CN/settings.php
resources/lang/zh_CN/validation.php
resources/lang/zh_TW/activities.php
resources/lang/zh_TW/auth.php
resources/lang/zh_TW/common.php
resources/lang/zh_TW/components.php
resources/lang/zh_TW/entities.php
resources/lang/zh_TW/errors.php
resources/lang/zh_TW/passwords.php
resources/lang/zh_TW/settings.php
resources/lang/zh_TW/validation.php
resources/sass/_blocks.scss
resources/sass/_buttons.scss
resources/sass/_components.scss
resources/sass/_footer.scss [new file with mode: 0644]
resources/sass/_forms.scss
resources/sass/_header.scss
resources/sass/_html.scss
resources/sass/_layout.scss
resources/sass/_lists.scss
resources/sass/_tables.scss
resources/sass/_tinymce.scss
resources/sass/_variables.scss
resources/sass/export-styles.scss
resources/sass/styles.scss
resources/views/api-docs/index.blade.php
resources/views/attachments/manager-list.blade.php
resources/views/auth/forms/login/saml2.blade.php
resources/views/auth/login.blade.php
resources/views/auth/register.blade.php
resources/views/base.blade.php
resources/views/books/export.blade.php
resources/views/books/form.blade.php
resources/views/books/list-item.blade.php
resources/views/books/show.blade.php
resources/views/chapters/export.blade.php
resources/views/chapters/form.blade.php
resources/views/chapters/show.blade.php
resources/views/common/detailed-listing-paginated.blade.php [moved from resources/views/pages/detailed-listing.blade.php with 82% similarity]
resources/views/common/detailed-listing-with-more.blade.php [new file with mode: 0644]
resources/views/common/footer.blade.php [new file with mode: 0644]
resources/views/common/header.blade.php
resources/views/common/home-book.blade.php
resources/views/common/home-shelves.blade.php
resources/views/common/home-sidebar.blade.php
resources/views/common/home.blade.php
resources/views/components/entity-selector.blade.php
resources/views/components/image-manager-form.blade.php
resources/views/components/page-picker.blade.php
resources/views/components/tag-list.blade.php
resources/views/components/user-select-list.blade.php
resources/views/components/user-select.blade.php
resources/views/errors/404.blade.php
resources/views/export-layout.blade.php [new file with mode: 0644]
resources/views/form/entity-permissions.blade.php
resources/views/form/password.blade.php
resources/views/pages/export.blade.php
resources/views/pages/markdown-editor.blade.php
resources/views/pages/move.blade.php
resources/views/pages/revisions.blade.php
resources/views/pages/show.blade.php
resources/views/pages/wysiwyg-editor.blade.php
resources/views/partials/custom-head-content.blade.php [deleted file]
resources/views/partials/custom-head.blade.php
resources/views/partials/entity-export-menu.blade.php
resources/views/partials/entity-export-meta.blade.php
resources/views/partials/entity-favourite-action.blade.php [new file with mode: 0644]
resources/views/partials/entity-list-item.blade.php
resources/views/partials/entity-list.blade.php
resources/views/partials/entity-meta.blade.php
resources/views/partials/export-custom-head.blade.php [new file with mode: 0644]
resources/views/partials/export-styles.blade.php
resources/views/search/all.blade.php
resources/views/settings/audit.blade.php
resources/views/settings/footer-links.blade.php [new file with mode: 0644]
resources/views/settings/index.blade.php
resources/views/shelves/form.blade.php
resources/views/shelves/show.blade.php
resources/views/tri-layout.blade.php
resources/views/users/delete.blade.php
resources/views/users/form.blade.php
resources/views/users/index.blade.php
routes/web.php
tests/AuditLogTest.php
tests/Auth/AuthTest.php
tests/Auth/LdapTest.php
tests/Auth/Saml2Test.php
tests/Auth/SocialAuthTest.php
tests/Auth/UserInviteTest.php
tests/BrowserKitTest.php
tests/Commands/AddAdminCommandTest.php [new file with mode: 0644]
tests/Commands/ClearActivityCommandTest.php [new file with mode: 0644]
tests/Commands/ClearRevisionsCommandTest.php [new file with mode: 0644]
tests/Commands/ClearViewsCommandTest.php [new file with mode: 0644]
tests/Commands/CopyShelfPermissionsCommandTest.php [new file with mode: 0644]
tests/Commands/RegenerateCommentContentCommandTest.php [new file with mode: 0644]
tests/Commands/RegeneratePermissionsCommandTest.php [new file with mode: 0644]
tests/Commands/UpdateUrlCommandTest.php [new file with mode: 0644]
tests/CommandsTest.php [deleted file]
tests/Entity/BookShelfTest.php
tests/Entity/EntitySearchTest.php
tests/Entity/EntityTest.php
tests/Entity/ExportTest.php
tests/Entity/PageRevisionTest.php
tests/Entity/PageTest.php
tests/Entity/SortTest.php
tests/Entity/TagTest.php
tests/ErrorTest.php
tests/FavouriteTest.php [new file with mode: 0644]
tests/FooterLinksTest.php [new file with mode: 0644]
tests/HomepageTest.php
tests/Permissions/EntityPermissionsTest.php
tests/PublicActionTest.php
tests/RecycleBinTest.php
tests/SharedTestHelpers.php
tests/StatusTest.php
tests/TestResponse.php
tests/ThemeTest.php
tests/Unit/ConfigTest.php
tests/Uploads/ImageTest.php
tests/Uploads/UsesImages.php
tests/User/UserPreferencesTest.php
tests/User/UserProfileTest.php
tests/test-data/bad-php.base64 [new file with mode: 0644]
tests/test-data/bad-phtml-png.base64 [new file with mode: 0644]
tests/test-data/bad-phtml.base64 [new file with mode: 0644]
tests/test-data/bad.php [deleted file]
tests/test-data/bad.phtml [deleted file]
tests/test-data/bad.phtml.png [deleted file]

index e3dbdb857ddde1c7393e944a17ab9bea0f6b7104..71fe66bca93c7d5fddfbe334afbbddf14488db5e 100644 (file)
@@ -51,7 +51,7 @@ DB_USERNAME=database_username
 DB_PASSWORD=database_user_password
 
 # Mail system to use
-# Can be 'smtp', 'mail' or 'sendmail'
+# Can be 'smtp' or 'sendmail'
 MAIL_DRIVER=smtp
 
 # Mail sending options
@@ -195,6 +195,7 @@ LDAP_DN=false
 LDAP_PASS=false
 LDAP_USER_FILTER=false
 LDAP_VERSION=false
+LDAP_START_TLS=false
 LDAP_TLS_INSECURE=false
 LDAP_ID_ATTRIBUTE=uid
 LDAP_EMAIL_ATTRIBUTE=mail
@@ -221,6 +222,7 @@ SAML2_IDP_x509=null
 SAML2_ONELOGIN_OVERRIDES=null
 SAML2_DUMP_USER_DETAILS=false
 SAML2_AUTOLOAD_METADATA=false
+SAML2_IDP_AUTHNCONTEXT=true
 
 # SAML group sync configuration
 # Refer to https://www.bookstackapp.com/docs/admin/saml2-auth/
@@ -245,10 +247,15 @@ AVATAR_URL=
 DRAWIO=true
 
 # Default item listing view
-# Used for public visitors and user's without a preference
-# Can be 'list' or 'grid'
+# Used for public visitors and user's without a preference.
+# Can be 'list' or 'grid'.
 APP_VIEWS_BOOKS=list
 APP_VIEWS_BOOKSHELVES=grid
+APP_VIEWS_BOOKSHELF=grid
+
+# Use dark mode by default
+# Will be overriden by any user/session preference.
+APP_DEFAULT_DARK_MODE=false
 
 # Page revision limit
 # Number of page revisions to keep in the system before deleting old revisions.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644 (file)
index 0000000..d0ac0c6
--- /dev/null
@@ -0,0 +1,9 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Discord chat support
+    url: https://discord.gg/ztkBqR2
+    about: Realtime support / chat with the community and the team.
+
+  - name: Debugging & Common Issues
+    url: https://www.bookstackapp.com/docs/admin/debugging/
+    about: Find details on how to debug issues and view common issues with thier resolutions.
index 9849b4cb27b6f0e4fab16750e4a877a8d14e5d7c..d789a43aa0382cb0cdaf4b89b9abaccf18bfe5af 100644 (file)
@@ -49,6 +49,12 @@ Name :: Languages
 @jzoy :: Simplified Chinese
 @ististudio :: Korean
 @leomartinez :: Spanish Argentina
+@geins :: German
+@Ereza :: Catalan
+@benediktvolke :: German
+@Baptistou :: French
+@arcoai :: Spanish
+@Jokuna :: Korean
 cipi1965 :: Italian
 Mykola Ronik (Mantikor) :: Ukrainian
 furkanoyk :: Turkish
@@ -134,3 +140,28 @@ Douradinho :: Portuguese, Brazilian
 Gaku Yaguchi (tama11) :: Japanese
 johnroyer :: Chinese Traditional
 jackaaa :: Chinese Traditional
+Irfan Hukama Arsyad (IrfanArsyad) :: Indonesian
+Jeff Huang (s8321414) :: Chinese Traditional
+Luís Tiago Favas (starkyller) :: Portuguese
+semirte :: Bosnian
+aarchijs :: Latvian
+Martins Pilsetnieks (pilsetnieks) :: Latvian
+Yonatan Magier (yonatanmgr) :: Hebrew
+FastHogi :: German Informal; German
+Ole Anders (Swoy) :: Norwegian Bokmal
+Atlochowski (atlochowski) :: Polish
+Simon (DefaultSimon) :: Slovenian
+Reinis Mednis (Mednis) :: Latvian
+toisho (toishoki) :: Turkish
+nikservik :: Ukrainian; Russian; Polish
+HenrijsS :: Latvian
+Pascal R-B (pborgner) :: German
+Boris (Ginfred) :: Russian
+Jonas Anker Rasmussen (jonasanker) :: Danish
+Gerwin de Keijzer (gdekeijzer) :: Dutch; German Informal; German
+kometchtech :: Japanese
+Auri (Atalonica) :: Catalan
+Francesco Franchina (ffranchina) :: Italian
+Aimrane Kds (aimrane.kds) :: Arabic
+whenwesober :: Indonesian
+Rem (remkovdhoef) :: Dutch
index 7646f06879ab81802f774d19e2610476f02f265c..b0ef05b8fcf43c0c001ef5615ce360ba2d0c45e3 100644 (file)
@@ -5,6 +5,7 @@ on:
     branches:
       - master
       - release
+      - gh_actions_update
   pull_request:
     branches:
       - '*'
@@ -13,13 +14,19 @@ on:
 
 jobs:
   build:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
     strategy:
       matrix:
-        php: [7.2, 7.4]
+        php: ['7.3', '7.4', '8.0']
     steps:
     - uses: actions/checkout@v1
 
+    - name: Setup PHP
+      uses: shivammathur/setup-php@b7d1d9c9a92d8d8463ce36d7f60da34d461724f8
+      with:
+        php-version: ${{ matrix.php }}
+        extensions: gd, mbstring, json, curl, xml, mysql, ldap
+
     - name: Get Composer Cache Directory
       id: composer-cache
       run: |
@@ -38,7 +45,7 @@ jobs:
     - name: Setup Database
       run: |
         mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
-        mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';"
+        mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
         mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
         mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
 
index bff6f70d1d162b698b9ace7b31f9b4b85c223346..34aaf9c3fc9b1f2c1cb2b09a2dc08aeb792f697b 100644 (file)
@@ -5,6 +5,7 @@ on:
     branches:
       - master
       - release
+      - gh_actions_update
   pull_request:
     branches:
       - '*'
@@ -13,13 +14,19 @@ on:
 
 jobs:
   build:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
     strategy:
       matrix:
-        php: [7.2, 7.4]
+        php: ['7.3', '7.4', '8.0']
     steps:
       - uses: actions/checkout@v1
 
+      - name: Setup PHP
+        uses: shivammathur/setup-php@b7d1d9c9a92d8d8463ce36d7f60da34d461724f8
+        with:
+          php-version: ${{ matrix.php }}
+          extensions: gd, mbstring, json, curl, xml, mysql, ldap
+
       - name: Get Composer Cache Directory
         id: composer-cache
         run: |
@@ -38,7 +45,7 @@ jobs:
       - name: Create database & user
         run: |
           mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
-          mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED BY 'bookstack-test';"
+          mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
           mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
           mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
 
index 9d256c9b2918b2cdf0d7f543662bd7a7fb920f87..c8590f0b25d12dc8f4f29210e51cd3c8b28de897 100644 (file)
@@ -6,6 +6,7 @@ use BookStack\Auth\User;
 use BookStack\Entities\Models\Entity;
 use BookStack\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
 use Illuminate\Support\Str;
 
 /**
@@ -23,7 +24,7 @@ class Activity extends Model
     /**
      * Get the entity for this activity.
      */
-    public function entity()
+    public function entity(): MorphTo
     {
         if ($this->entity_type === '') {
             $this->entity_type = null;
index b2a35fd2a5115c7ba869a35ddbd34e69287b8e6d..73f827e70e329186d9a8fc8d2ecf6749f919da6c 100644 (file)
@@ -78,7 +78,7 @@ class ActivityService
     public function latest(int $count = 20, int $page = 0): array
     {
         $activityList = $this->permissionService
-            ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type')
+            ->filterRestrictedEntityRelations($this->activity->newQuery(), 'activities', 'entity_id', 'entity_type')
             ->orderBy('created_at', 'desc')
             ->with(['user', 'entity'])
             ->skip($count * $page)
@@ -131,7 +131,7 @@ class ActivityService
     public function userActivity(User $user, int $count = 20, int $page = 0): array
     {
         $activityList = $this->permissionService
-            ->filterRestrictedEntityRelations($this->activity, 'activities', 'entity_id', 'entity_type')
+            ->filterRestrictedEntityRelations($this->activity->newQuery(), 'activities', 'entity_id', 'entity_type')
             ->orderBy('created_at', 'desc')
             ->where('user_id', '=', $user->id)
             ->skip($count * $page)
index 216f612499ba3145c9584e0dc9dd77a173946a1c..ec02bed25483696747c9fceceb6f9b560e4da819 100644 (file)
@@ -48,4 +48,4 @@ class ActivityType
     const AUTH_PASSWORD_RESET_UPDATE = 'auth_password_reset_update';
     const AUTH_LOGIN = 'auth_login';
     const AUTH_REGISTER = 'auth_register';
-}
\ No newline at end of file
+}
diff --git a/app/Actions/Favourite.php b/app/Actions/Favourite.php
new file mode 100644 (file)
index 0000000..107a765
--- /dev/null
@@ -0,0 +1,17 @@
+<?php namespace BookStack\Actions;
+
+use BookStack\Model;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
+
+class Favourite extends Model
+{
+    protected $fillable = ['user_id'];
+
+    /**
+     * Get the related model that can be favourited.
+     */
+    public function favouritable(): MorphTo
+    {
+        return $this->morphTo();
+    }
+}
index 5968ffe6d5ea9d875cf4fa574ac63d3d4c8d62a1..f73b2ed2d25423e55a044ac78f4a23327c5ef895 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Actions;
 
 use BookStack\Model;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
 
 class Tag extends Model
 {
@@ -9,10 +10,25 @@ class Tag extends Model
 
     /**
      * Get the entity that this tag belongs to
-     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
      */
-    public function entity()
+    public function entity(): MorphTo
     {
         return $this->morphTo('entity');
     }
+
+    /**
+     * Get a full URL to start a tag name search for this tag name.
+     */
+    public function nameUrl(): string
+    {
+        return url('/search?term=%5B' . urlencode($this->name) .'%5D');
+    }
+
+    /**
+     * Get a full URL to start a tag name and value search for this tag's values.
+     */
+    public function valueUrl(): string
+    {
+        return url('/search?term=%5B' . urlencode($this->name) .'%3D' . urlencode($this->value) . '%5D');
+    }
 }
index f58589ccd589c4d019d055156f8596974da447c4..c80e8abe3225e70a121aca5d21873ec1fda1a6a7 100644 (file)
@@ -26,7 +26,9 @@ class TagRepo
      */
     public function getNameSuggestions(?string $searchTerm): Collection
     {
-        $query = $this->tag->select('*', DB::raw('count(*) as count'))->groupBy('name');
+        $query = $this->tag->newQuery()
+            ->select('*', DB::raw('count(*) as count'))
+            ->groupBy('name');
 
         if ($searchTerm) {
             $query = $query->where('name', 'LIKE', $searchTerm . '%')->orderBy('name', 'desc');
@@ -45,7 +47,9 @@ class TagRepo
      */
     public function getValueSuggestions(?string $searchTerm, ?string $tagName): Collection
     {
-        $query = $this->tag->select('*', DB::raw('count(*) as count'))->groupBy('value');
+        $query = $this->tag->newQuery()
+            ->select('*', DB::raw('count(*) as count'))
+            ->groupBy('value');
 
         if ($searchTerm) {
             $query = $query->where('value', 'LIKE', $searchTerm . '%')->orderBy('value', 'desc');
index e9841293b5c72a1d20f045b81e8f95a7a534a9e0..de30900c76f2a6d4600a75f726dd9645fe7713ff 100644 (file)
@@ -1,7 +1,19 @@
 <?php namespace BookStack\Actions;
 
+use BookStack\Interfaces\Viewable;
 use BookStack\Model;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
 
+/**
+ * Class View
+ * Views are stored per-item per-person within the database.
+ * They can be used to find popular items or recently viewed items
+ * at a per-person level. They do not record every view instance as an
+ * activity. Only the latest and original view times could be recognised.
+ *
+ * @property int $views
+ * @property int $user_id
+ */
 class View extends Model
 {
 
@@ -9,10 +21,37 @@ class View extends Model
 
     /**
      * Get all owning viewable models.
-     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
      */
-    public function viewable()
+    public function viewable(): MorphTo
     {
         return $this->morphTo();
     }
+
+    /**
+     * Increment the current user's view count for the given viewable model.
+     */
+    public static function incrementFor(Viewable $viewable): int
+    {
+        $user = user();
+        if (is_null($user) || $user->isDefault()) {
+            return 0;
+        }
+
+        /** @var View $view */
+        $view = $viewable->views()->firstOrNew([
+            'user_id' => $user->id,
+        ], ['views' => 0]);
+
+        $view->forceFill(['views' => $view->views + 1])->save();
+
+        return $view->views;
+    }
+
+    /**
+     * Clear all views from the system.
+     */
+    public static function clearAll()
+    {
+        static::query()->truncate();
+    }
 }
diff --git a/app/Actions/ViewService.php b/app/Actions/ViewService.php
deleted file mode 100644 (file)
index 51a60d9..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php namespace BookStack\Actions;
-
-use BookStack\Auth\Permissions\PermissionService;
-use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Entity;
-use BookStack\Entities\EntityProvider;
-use DB;
-use Illuminate\Support\Collection;
-
-class ViewService
-{
-    protected $view;
-    protected $permissionService;
-    protected $entityProvider;
-
-    /**
-     * ViewService constructor.
-     * @param View $view
-     * @param PermissionService $permissionService
-     * @param EntityProvider $entityProvider
-     */
-    public function __construct(View $view, PermissionService $permissionService, EntityProvider $entityProvider)
-    {
-        $this->view = $view;
-        $this->permissionService = $permissionService;
-        $this->entityProvider = $entityProvider;
-    }
-
-    /**
-     * Add a view to the given entity.
-     * @param \BookStack\Entities\Models\Entity $entity
-     * @return int
-     */
-    public function add(Entity $entity)
-    {
-        $user = user();
-        if ($user === null || $user->isDefault()) {
-            return 0;
-        }
-        $view = $entity->views()->where('user_id', '=', $user->id)->first();
-        // Add view if model exists
-        if ($view) {
-            $view->increment('views');
-            return $view->views;
-        }
-
-        // Otherwise create new view count
-        $entity->views()->save($this->view->newInstance([
-            'user_id' => $user->id,
-            'views' => 1
-        ]));
-
-        return 1;
-    }
-
-    /**
-     * Get the entities with the most views.
-     * @param int $count
-     * @param int $page
-     * @param string|array $filterModels
-     * @param string $action - used for permission checking
-     * @return Collection
-     */
-    public function getPopular(int $count = 10, int $page = 0, array $filterModels = null, string $action = 'view')
-    {
-        $skipCount = $count * $page;
-        $query = $this->permissionService
-            ->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
-            ->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));
-        }
-
-        return $query->with('viewable')
-            ->skip($skipCount)
-            ->take($count)
-            ->get()
-            ->pluck('viewable')
-            ->filter();
-    }
-
-    /**
-     * Get all recently viewed entities for the current user.
-     */
-    public function getUserRecentlyViewed(int $count = 10, int $page = 1)
-    {
-        $user = user();
-        if ($user === null || $user->isDefault()) {
-            return collect();
-        }
-
-        $all = collect();
-        /** @var Entity $instance */
-        foreach ($this->entityProvider->all() as $name => $instance) {
-            $items = $instance::visible()->withLastView()
-                ->orderBy('last_viewed_at', 'desc')
-                ->skip($count * ($page - 1))
-                ->take($count)
-                ->get();
-            $all = $all->concat($items);
-        }
-
-        return $all->sortByDesc('last_viewed_at')->slice(0, $count);
-    }
-
-    /**
-     * Reset all view counts by deleting all views.
-     */
-    public function resetAll()
-    {
-        $this->view->truncate();
-    }
-}
index 2953647bb33b21d59f565ce990cffecfc7a8beea..8b520eda2b7432e67c49228e73a349495993fa83 100644 (file)
@@ -142,5 +142,4 @@ class ApiDocsGenerator
             ];
         });
     }
-
-}
\ No newline at end of file
+}
index e0a50ebe3d2a03f7e90a8b4362ff15283bb73849..59ab72f4eb8509be037704fdddff907bda4403b4 100644 (file)
@@ -163,4 +163,4 @@ class ApiTokenGuard implements Guard
     {
         $this->user = null;
     }
-}
\ No newline at end of file
+}
index 9a0c691c8bbb272301c45bccbf6b7c3739554f84..b133754d8e932f43c750ab40b35fab224904d107 100644 (file)
@@ -299,5 +299,4 @@ class ExternalBaseSessionGuard implements StatefulGuard
 
         return $this;
     }
-
 }
index 652141c0ce280963abc337f20cd62e19d79e6f40..71417aed2607d007fc0858039fe3df50e5265f86 100644 (file)
@@ -5,14 +5,12 @@ namespace BookStack\Auth\Access\Guards;
 use BookStack\Auth\Access\LdapService;
 use BookStack\Auth\Access\RegistrationService;
 use BookStack\Auth\User;
-use BookStack\Auth\UserRepo;
 use BookStack\Exceptions\LdapException;
 use BookStack\Exceptions\LoginAttemptException;
 use BookStack\Exceptions\LoginAttemptEmailNeededException;
 use BookStack\Exceptions\UserRegistrationException;
 use Illuminate\Contracts\Auth\UserProvider;
 use Illuminate\Contracts\Session\Session;
-use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Str;
 
 class LdapSessionGuard extends ExternalBaseSessionGuard
@@ -23,13 +21,13 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
     /**
      * LdapSessionGuard constructor.
      */
-    public function __construct($name,
+    public function __construct(
+        $name,
         UserProvider $provider,
         Session $session,
         LdapService $ldapService,
         RegistrationService $registrationService
-    )
-    {
+    ) {
         $this->ldapService = $ldapService;
         parent::__construct($name, $provider, $session, $registrationService);
     }
@@ -92,6 +90,11 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
             $this->ldapService->syncGroups($user, $username);
         }
 
+        // Attach avatar if non-existent
+        if (is_null($user->avatar)) {
+            $this->ldapService->saveAndAttachAvatar($user, $userDetails);
+        }
+
         $this->login($user, $remember);
         return true;
     }
@@ -117,7 +120,8 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
             'password' => Str::random(32),
         ];
 
-        return $this->registrationService->registerUser($details, null, false);
+        $user = $this->registrationService->registerUser($details, null, false);
+        $this->ldapService->saveAndAttachAvatar($user, $ldapUserDetails);
+        return $user;
     }
-
 }
index 68683bb4368b61ac23769fdce2eeb1ed7e876497..044c2f3833f3b2f4652bf22c210c775efbc1b583 100644 (file)
@@ -34,5 +34,4 @@ class Saml2SessionGuard extends ExternalBaseSessionGuard
     {
         return false;
     }
-
 }
index 6b7bd9b9bf2a4d4699a74609ab507d33cc72f98d..352231df54a8e3633a68fbcacb52fc07c332f92f 100644 (file)
@@ -31,6 +31,14 @@ class Ldap
         return ldap_set_option($ldapConnection, $option, $value);
     }
 
+    /**
+     * Start TLS on the given LDAP connection.
+     */
+    public function startTls($ldapConnection): bool
+    {
+        return ldap_start_tls($ldapConnection);
+    }
+
     /**
      * Set the version number for the given ldap connection.
      * @param $ldapConnection
index 92234edcf906bc2934a47443af011de388d2f853..2f632b0b533e8534ae44835920a5279727da7338 100644 (file)
@@ -3,7 +3,9 @@
 use BookStack\Auth\User;
 use BookStack\Exceptions\JsonDebugException;
 use BookStack\Exceptions\LdapException;
+use BookStack\Uploads\UserAvatars;
 use ErrorException;
+use Illuminate\Support\Facades\Log;
 
 /**
  * Class LdapService
@@ -14,15 +16,17 @@ class LdapService extends ExternalAuthService
 
     protected $ldap;
     protected $ldapConnection;
+    protected $userAvatars;
     protected $config;
     protected $enabled;
 
     /**
      * LdapService constructor.
      */
-    public function __construct(Ldap $ldap)
+    public function __construct(Ldap $ldap, UserAvatars $userAvatars)
     {
         $this->ldap = $ldap;
+        $this->userAvatars = $userAvatars;
         $this->config = config('services.ldap');
         $this->enabled = config('auth.method') === 'ldap';
     }
@@ -76,19 +80,23 @@ class LdapService extends ExternalAuthService
         $idAttr = $this->config['id_attribute'];
         $emailAttr = $this->config['email_attribute'];
         $displayNameAttr = $this->config['display_name_attribute'];
+        $thumbnailAttr = $this->config['thumbnail_attribute'];
 
-        $user = $this->getUserWithAttributes($userName, ['cn', 'dn', $idAttr, $emailAttr, $displayNameAttr]);
+        $user = $this->getUserWithAttributes($userName, array_filter([
+            'cn', 'dn', $idAttr, $emailAttr, $displayNameAttr, $thumbnailAttr,
+        ]));
 
-        if ($user === null) {
+        if (is_null($user)) {
             return null;
         }
 
         $userCn = $this->getUserResponseProperty($user, 'cn', null);
         $formatted = [
-            'uid'   => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
-            'name'  => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
-            'dn'    => $user['dn'],
+            'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
+            'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
+            'dn' => $user['dn'],
             'email' => $this->getUserResponseProperty($user, $emailAttr, null),
+            'avatar'=> $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
         ];
 
         if ($this->config['dump_user_details']) {
@@ -187,8 +195,8 @@ class LdapService extends ExternalAuthService
             throw new LdapException(trans('errors.ldap_extension_not_installed'));
         }
 
-         // Check if TLS_INSECURE is set. The handle is set to NULL due to the nature of
-         // the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not per handle.
+        // Disable certificate verification.
+        // This option works globally and must be set before a connection is created.
         if ($this->config['tls_insecure']) {
             $this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
         }
@@ -205,6 +213,14 @@ class LdapService extends ExternalAuthService
             $this->ldap->setVersion($ldapConnection, $this->config['version']);
         }
 
+        // Start and verify TLS if it's enabled
+        if ($this->config['start_tls']) {
+            $started = $this->ldap->startTls($ldapConnection);
+            if (!$started) {
+                throw new LdapException('Could not start TLS connection');
+            }
+        }
+
         $this->ldapConnection = $ldapConnection;
         return $this->ldapConnection;
     }
@@ -342,4 +358,22 @@ class LdapService extends ExternalAuthService
         $userLdapGroups = $this->getUserGroups($username);
         $this->syncWithGroups($user, $userLdapGroups);
     }
+
+    /**
+     * Save and attach an avatar image, if found in the ldap details, and attach
+     * to the given user model.
+     */
+    public function saveAndAttachAvatar(User $user, array $ldapUserDetails): void
+    {
+        if (is_null(config('services.ldap.thumbnail_attribute')) || is_null($ldapUserDetails['avatar'])) {
+            return;
+        }
+
+        try {
+            $imageData = $ldapUserDetails['avatar'];
+            $this->userAvatars->assignToUserFromExistingData($user, $imageData, 'jpg');
+        } catch (\Exception $exception) {
+            Log::info("Failed to use avatar image from LDAP data for user id {$user->id}");
+        }
+    }
 }
index 2aff6c37d5140c84644f75077cceea2cd25d43d1..68b17771d628552fe57a1d40fe7301774d43adb9 100644 (file)
@@ -6,6 +6,8 @@ use BookStack\Auth\User;
 use BookStack\Auth\UserRepo;
 use BookStack\Exceptions\UserRegistrationException;
 use BookStack\Facades\Activity;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
 use Exception;
 
 class RegistrationService
@@ -71,6 +73,7 @@ class RegistrationService
         }
 
         Activity::add(ActivityType::AUTH_REGISTER, $socialAccount ?? $newUser);
+        Theme::dispatch(ThemeEvents::AUTH_REGISTER, $socialAccount ? $socialAccount->driver : auth()->getDefaultDriver(), $newUser);
 
         // Start email confirmation flow if required
         if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) {
@@ -83,7 +86,6 @@ class RegistrationService
                 $message = trans('auth.email_confirm_send_error');
                 throw new UserRegistrationException($message, '/register/confirm');
             }
-
         }
 
         return $newUser;
@@ -109,5 +111,4 @@ class RegistrationService
             throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), $redirect);
         }
     }
-
-}
\ No newline at end of file
+}
index 0316ff976e4623e222ac69cdcf956f8efab55334..105853997cd4d939c3fefe66678915dea89bdaa9 100644 (file)
@@ -6,6 +6,8 @@ use BookStack\Exceptions\JsonDebugException;
 use BookStack\Exceptions\SamlException;
 use BookStack\Exceptions\UserRegistrationException;
 use BookStack\Facades\Activity;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
 use Exception;
 use Illuminate\Support\Str;
 use OneLogin\Saml2\Auth;
@@ -375,6 +377,7 @@ class Saml2Service extends ExternalAuthService
 
         auth()->login($user);
         Activity::add(ActivityType::AUTH_LOGIN, "saml2; {$user->logDescriptor()}");
+        Theme::dispatch(ThemeEvents::AUTH_LOGIN, 'saml2', $user);
         return $user;
     }
 }
index b0383a938522e0ba67cad2213a29895f5d82cba2..a03eb2b1d97d662421f6b41a451dc5f33674fe6a 100644 (file)
@@ -2,37 +2,63 @@
 
 use BookStack\Actions\ActivityType;
 use BookStack\Auth\SocialAccount;
-use BookStack\Auth\UserRepo;
+use BookStack\Auth\User;
 use BookStack\Exceptions\SocialDriverNotConfigured;
 use BookStack\Exceptions\SocialSignInAccountNotUsed;
 use BookStack\Exceptions\UserRegistrationException;
 use BookStack\Facades\Activity;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
+use Illuminate\Support\Facades\Event;
 use Illuminate\Support\Str;
 use Laravel\Socialite\Contracts\Factory as Socialite;
 use Laravel\Socialite\Contracts\Provider;
 use Laravel\Socialite\Contracts\User as SocialUser;
+use SocialiteProviders\Manager\SocialiteWasCalled;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 
 class SocialAuthService
 {
-
-    protected $userRepo;
+    /**
+     * The core socialite library used.
+     * @var Socialite
+     */
     protected $socialite;
-    protected $socialAccount;
 
-    protected $validSocialDrivers = ['google', 'github', 'facebook', 'slack', 'twitter', 'azure', 'okta', 'gitlab', 'twitch', 'discord'];
+    /**
+     * The default built-in social drivers we support.
+     * @var string[]
+     */
+    protected $validSocialDrivers = [
+        'google',
+        'github',
+        'facebook',
+        'slack',
+        'twitter',
+        'azure',
+        'okta',
+        'gitlab',
+        'twitch',
+        'discord'
+    ];
+
+    /**
+     * Callbacks to run when configuring a social driver
+     * for an initial redirect action.
+     * Array is keyed by social driver name.
+     * Callbacks are passed an instance of the driver.
+     * @var array<string, callable>
+     */
+    protected $configureForRedirectCallbacks = [];
 
     /**
      * SocialAuthService constructor.
      */
-    public function __construct(UserRepo $userRepo, Socialite $socialite, SocialAccount $socialAccount)
+    public function __construct(Socialite $socialite)
     {
-        $this->userRepo = $userRepo;
         $this->socialite = $socialite;
-        $this->socialAccount = $socialAccount;
     }
 
-
     /**
      * Start the social login path.
      * @throws SocialDriverNotConfigured
@@ -40,7 +66,7 @@ class SocialAuthService
     public function startLogIn(string $socialDriver): RedirectResponse
     {
         $driver = $this->validateDriver($socialDriver);
-        return $this->getSocialDriver($driver)->redirect();
+        return $this->getDriverForRedirect($driver)->redirect();
     }
 
     /**
@@ -50,7 +76,7 @@ class SocialAuthService
     public function startRegister(string $socialDriver): RedirectResponse
     {
         $driver = $this->validateDriver($socialDriver);
-        return $this->getSocialDriver($driver)->redirect();
+        return $this->getDriverForRedirect($driver)->redirect();
     }
 
     /**
@@ -60,11 +86,11 @@ class SocialAuthService
     public function handleRegistrationCallback(string $socialDriver, SocialUser $socialUser): SocialUser
     {
         // Check social account has not already been used
-        if ($this->socialAccount->where('driver_id', '=', $socialUser->getId())->exists()) {
-            throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver]), '/login');
+        if (SocialAccount::query()->where('driver_id', '=', $socialUser->getId())->exists()) {
+            throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount' => $socialDriver]), '/login');
         }
 
-        if ($this->userRepo->getByEmail($socialUser->getEmail())) {
+        if (User::query()->where('email', '=', $socialUser->getEmail())->exists()) {
             $email = $socialUser->getEmail();
             throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $email]), '/login');
         }
@@ -91,7 +117,7 @@ class SocialAuthService
         $socialId = $socialUser->getId();
 
         // Get any attached social accounts or users
-        $socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first();
+        $socialAccount = SocialAccount::query()->where('driver_id', '=', $socialId)->first();
         $isLoggedIn = auth()->check();
         $currentUser = user();
         $titleCaseDriver = Str::title($socialDriver);
@@ -101,14 +127,15 @@ class SocialAuthService
         if (!$isLoggedIn && $socialAccount !== null) {
             auth()->login($socialAccount->user);
             Activity::add(ActivityType::AUTH_LOGIN, $socialAccount);
+            Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $socialAccount->user);
             return redirect()->intended('/');
         }
 
         // When a user is logged in but the social account does not exist,
         // Create the social account and attach it to the user & redirect to the profile page.
         if ($isLoggedIn && $socialAccount === null) {
-            $this->fillSocialAccount($socialDriver, $socialUser);
-            $currentUser->socialAccounts()->save($this->socialAccount);
+            $account = $this->newSocialAccount($socialDriver, $socialUser);
+            $currentUser->socialAccounts()->save($account);
             session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver]));
             return redirect($currentUser->getEditUrl());
         }
@@ -130,7 +157,7 @@ class SocialAuthService
         if (setting('registration-enabled') && config('auth.method') !== 'ldap' && config('auth.method') !== 'saml2') {
             $message .= trans('errors.social_account_register_instructions', ['socialAccount' => $titleCaseDriver]);
         }
-        
+
         throw new SocialSignInAccountNotUsed($message, '/login');
     }
 
@@ -207,21 +234,19 @@ class SocialAuthService
     /**
      * Fill and return a SocialAccount from the given driver name and SocialUser.
      */
-    public function fillSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount
+    public function newSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount
     {
-        $this->socialAccount->fill([
-            'driver'    => $socialDriver,
+        return new SocialAccount([
+            'driver' => $socialDriver,
             'driver_id' => $socialUser->getId(),
-            'avatar'    => $socialUser->getAvatar()
+            'avatar' => $socialUser->getAvatar()
         ]);
-        return $this->socialAccount;
     }
 
     /**
      * Detach a social account from a user.
-     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
      */
-    public function detachSocialAccount(string $socialDriver)
+    public function detachSocialAccount(string $socialDriver): void
     {
         user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
     }
@@ -229,7 +254,7 @@ class SocialAuthService
     /**
      * Provide redirect options per service for the Laravel Socialite driver
      */
-    public function getSocialDriver(string $driverName): Provider
+    protected function getDriverForRedirect(string $driverName): Provider
     {
         $driver = $this->socialite->driver($driverName);
 
@@ -240,6 +265,33 @@ class SocialAuthService
             $driver->with(['resource' => 'https://graph.windows.net']);
         }
 
+        if (isset($this->configureForRedirectCallbacks[$driverName])) {
+            $this->configureForRedirectCallbacks[$driverName]($driver);
+        }
+
         return $driver;
     }
+
+    /**
+     * Add a custom socialite driver to be used.
+     * Driver name should be lower_snake_case.
+     * Config array should mirror the structure of a service
+     * within the `Config/services.php` file.
+     * Handler should be a Class@method handler to the SocialiteWasCalled event.
+     */
+    public function addSocialDriver(
+        string $driverName,
+        array $config,
+        string $socialiteHandler,
+        callable $configureForRedirect = null
+    ) {
+        $this->validSocialDrivers[] = $driverName;
+        config()->set('services.' . $driverName, $config);
+        config()->set('services.' . $driverName . '.redirect', url('/login/service/' . $driverName . '/callback'));
+        config()->set('services.' . $driverName . '.name', $config['name'] ?? $driverName);
+        Event::listen(SocialiteWasCalled::class, $socialiteHandler);
+        if (!is_null($configureForRedirect)) {
+            $this->configureForRedirectCallbacks[$driverName] = $configureForRedirect;
+        }
+    }
 }
index 89c8a5fbb25a55fe54fd95cc8d2721c27c6b9334..4565986535d6240a9a0c80211bc61f0929117aa6 100644 (file)
@@ -1,73 +1,54 @@
 <?php namespace BookStack\Auth\Permissions;
 
-use BookStack\Auth\Permissions;
 use BookStack\Auth\Role;
+use BookStack\Auth\User;
 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\EntityProvider;
+use BookStack\Entities\Models\Page;
 use BookStack\Model;
 use BookStack\Traits\HasCreatorAndUpdater;
 use BookStack\Traits\HasOwner;
 use Illuminate\Database\Connection;
 use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Collection as EloquentCollection;
 use Illuminate\Database\Query\Builder as QueryBuilder;
-use Illuminate\Support\Collection;
+use Throwable;
 
 class PermissionService
 {
-
-    protected $currentAction;
-    protected $isAdminUser;
-    protected $userRoles = false;
-    protected $currentUserModel = false;
-
-    /**
-     * @var Connection
-     */
-    protected $db;
-
     /**
-     * @var JointPermission
+     * @var ?array
      */
-    protected $jointPermission;
+    protected $userRoles = null;
 
     /**
-     * @var Role
+     * @var ?User
      */
-    protected $role;
+    protected $currentUserModel = null;
 
     /**
-     * @var EntityPermission
+     * @var Connection
      */
-    protected $entityPermission;
+    protected $db;
 
     /**
-     * @var EntityProvider
+     * @var array
      */
-    protected $entityProvider;
-
     protected $entityCache;
 
     /**
      * PermissionService constructor.
      */
-    public function __construct(
-        JointPermission $jointPermission,
-        Permissions\EntityPermission $entityPermission,
-        Role $role,
-        Connection $db,
-        EntityProvider $entityProvider
-    ) {
+    public function __construct(Connection $db)
+    {
         $this->db = $db;
-        $this->jointPermission = $jointPermission;
-        $this->entityPermission = $entityPermission;
-        $this->role = $role;
-        $this->entityProvider = $entityProvider;
     }
 
     /**
      * Set the database connection
-     * @param Connection $connection
      */
     public function setConnection(Connection $connection)
     {
@@ -76,81 +57,63 @@ class PermissionService
 
     /**
      * Prepare the local entity cache and ensure it's empty
-     * @param \BookStack\Entities\Models\Entity[] $entities
+     * @param Entity[] $entities
      */
-    protected function readyEntityCache($entities = [])
+    protected function readyEntityCache(array $entities = [])
     {
         $this->entityCache = [];
 
         foreach ($entities as $entity) {
-            $type = $entity->getType();
-            if (!isset($this->entityCache[$type])) {
-                $this->entityCache[$type] = collect();
+            $class = get_class($entity);
+            if (!isset($this->entityCache[$class])) {
+                $this->entityCache[$class] = collect();
             }
-            $this->entityCache[$type]->put($entity->id, $entity);
+            $this->entityCache[$class]->put($entity->id, $entity);
         }
     }
 
     /**
      * Get a book via ID, Checks local cache
-     * @param $bookId
-     * @return Book
      */
-    protected function getBook($bookId)
+    protected function getBook(int $bookId): ?Book
     {
-        if (isset($this->entityCache['book']) && $this->entityCache['book']->has($bookId)) {
-            return $this->entityCache['book']->get($bookId);
-        }
-
-        $book = $this->entityProvider->book->find($bookId);
-        if ($book === null) {
-            $book = false;
+        if (isset($this->entityCache[Book::class]) && $this->entityCache[Book::class]->has($bookId)) {
+            return $this->entityCache[Book::class]->get($bookId);
         }
 
-        return $book;
+        return Book::query()->withTrashed()->find($bookId);
     }
 
     /**
      * Get a chapter via ID, Checks local cache
-     * @param $chapterId
-     * @return \BookStack\Entities\Models\Book
      */
-    protected function getChapter($chapterId)
+    protected function getChapter(int $chapterId): ?Chapter
     {
-        if (isset($this->entityCache['chapter']) && $this->entityCache['chapter']->has($chapterId)) {
-            return $this->entityCache['chapter']->get($chapterId);
+        if (isset($this->entityCache[Chapter::class]) && $this->entityCache[Chapter::class]->has($chapterId)) {
+            return $this->entityCache[Chapter::class]->get($chapterId);
         }
 
-        $chapter = $this->entityProvider->chapter->find($chapterId);
-        if ($chapter === null) {
-            $chapter = false;
-        }
-
-        return $chapter;
+        return Chapter::query()
+            ->withTrashed()
+            ->find($chapterId);
     }
 
     /**
-     * Get the roles for the current user;
-     * @return array|bool
+     * Get the roles for the current logged in user.
      */
-    protected function getRoles()
+    protected function getCurrentUserRoles(): array
     {
-        if ($this->userRoles !== false) {
+        if (!is_null($this->userRoles)) {
             return $this->userRoles;
         }
 
-        $roles = [];
-
         if (auth()->guest()) {
-            $roles[] = $this->role->getSystemRole('public')->id;
-            return $roles;
+            $this->userRoles = [Role::getSystemRole('public')->id];
+        } else {
+            $this->userRoles = $this->currentUser()->roles->pluck('id')->values()->all();
         }
 
-
-        foreach ($this->currentUser()->roles as $role) {
-            $roles[] = $role->id;
-        }
-        return $roles;
+        return $this->userRoles;
     }
 
     /**
@@ -158,59 +121,57 @@ class PermissionService
      */
     public function buildJointPermissions()
     {
-        $this->jointPermission->truncate();
+        JointPermission::query()->truncate();
         $this->readyEntityCache();
 
         // Get all roles (Should be the most limited dimension)
-        $roles = $this->role->with('permissions')->get()->all();
+        $roles = Role::query()->with('permissions')->get()->all();
 
         // Chunk through all books
-        $this->bookFetchQuery()->chunk(5, function ($books) use ($roles) {
+        $this->bookFetchQuery()->chunk(5, function (EloquentCollection $books) use ($roles) {
             $this->buildJointPermissionsForBooks($books, $roles);
         });
 
         // Chunk through all bookshelves
-        $this->entityProvider->bookshelf->newQuery()->withTrashed()->select(['id', 'restricted', 'owned_by'])
-            ->chunk(50, function ($shelves) use ($roles) {
+        Bookshelf::query()->withTrashed()->select(['id', 'restricted', 'owned_by'])
+            ->chunk(50, function (EloquentCollection $shelves) use ($roles) {
                 $this->buildJointPermissionsForShelves($shelves, $roles);
             });
     }
 
     /**
      * Get a query for fetching a book with it's children.
-     * @return QueryBuilder
      */
-    protected function bookFetchQuery()
+    protected function bookFetchQuery(): Builder
     {
-        return $this->entityProvider->book->withTrashed()->newQuery()
-            ->select(['id', 'restricted', 'owned_by'])->with(['chapters' => function ($query) {
-                $query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id']);
-            }, 'pages'  => function ($query) {
-                $query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']);
-            }]);
+        return Book::query()->withTrashed()
+            ->select(['id', 'restricted', 'owned_by'])->with([
+                'chapters' => function ($query) {
+                    $query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id']);
+                },
+                'pages' => function ($query) {
+                    $query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']);
+                }
+            ]);
     }
 
     /**
-     * @param Collection $shelves
-     * @param array $roles
-     * @param bool $deleteOld
-     * @throws \Throwable
+     * Build joint permissions for the given shelf and role combinations.
+     * @throws Throwable
      */
-    protected function buildJointPermissionsForShelves($shelves, $roles, $deleteOld = false)
+    protected function buildJointPermissionsForShelves(EloquentCollection $shelves, array $roles, bool $deleteOld = false)
     {
         if ($deleteOld) {
             $this->deleteManyJointPermissionsForEntities($shelves->all());
         }
-        $this->createManyJointPermissions($shelves, $roles);
+        $this->createManyJointPermissions($shelves->all(), $roles);
     }
 
     /**
-     * Build joint permissions for an array of books
-     * @param Collection $books
-     * @param array $roles
-     * @param bool $deleteOld
+     * Build joint permissions for the given book and role combinations.
+     * @throws Throwable
      */
-    protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false)
+    protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false)
     {
         $entities = clone $books;
 
@@ -227,55 +188,53 @@ class PermissionService
         if ($deleteOld) {
             $this->deleteManyJointPermissionsForEntities($entities->all());
         }
-        $this->createManyJointPermissions($entities, $roles);
+        $this->createManyJointPermissions($entities->all(), $roles);
     }
 
     /**
      * Rebuild the entity jointPermissions for a particular entity.
-     * @param \BookStack\Entities\Models\Entity $entity
-     * @throws \Throwable
+     * @throws Throwable
      */
     public function buildJointPermissionsForEntity(Entity $entity)
     {
         $entities = [$entity];
-        if ($entity->isA('book')) {
+        if ($entity instanceof Book) {
             $books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
-            $this->buildJointPermissionsForBooks($books, $this->role->newQuery()->get(), true);
+            $this->buildJointPermissionsForBooks($books, Role::query()->get()->all(), true);
             return;
         }
 
+        /** @var BookChild $entity */
         if ($entity->book) {
             $entities[] = $entity->book;
         }
 
-        if ($entity->isA('page') && $entity->chapter_id) {
+        if ($entity instanceof Page && $entity->chapter_id) {
             $entities[] = $entity->chapter;
         }
 
-        if ($entity->isA('chapter')) {
+        if ($entity instanceof Chapter) {
             foreach ($entity->pages as $page) {
                 $entities[] = $page;
             }
         }
 
-        $this->buildJointPermissionsForEntities(collect($entities));
+        $this->buildJointPermissionsForEntities($entities);
     }
 
     /**
      * Rebuild the entity jointPermissions for a collection of entities.
-     * @param Collection $entities
-     * @throws \Throwable
+     * @throws Throwable
      */
-    public function buildJointPermissionsForEntities(Collection $entities)
+    public function buildJointPermissionsForEntities(array $entities)
     {
-        $roles = $this->role->newQuery()->get();
-        $this->deleteManyJointPermissionsForEntities($entities->all());
+        $roles = Role::query()->get()->values()->all();
+        $this->deleteManyJointPermissionsForEntities($entities);
         $this->createManyJointPermissions($entities, $roles);
     }
 
     /**
      * Build the entity jointPermissions for a particular role.
-     * @param Role $role
      */
     public function buildJointPermissionForRole(Role $role)
     {
@@ -288,7 +247,7 @@ class PermissionService
         });
 
         // Chunk through all bookshelves
-        $this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'owned_by'])
+        Bookshelf::query()->select(['id', 'restricted', 'owned_by'])
             ->chunk(50, function ($shelves) use ($roles) {
                 $this->buildJointPermissionsForShelves($shelves, $roles);
             });
@@ -296,7 +255,6 @@ class PermissionService
 
     /**
      * Delete the entity jointPermissions attached to a particular role.
-     * @param Role $role
      */
     public function deleteJointPermissionsForRole(Role $role)
     {
@@ -312,13 +270,13 @@ class PermissionService
         $roleIds = array_map(function ($role) {
             return $role->id;
         }, $roles);
-        $this->jointPermission->newQuery()->whereIn('role_id', $roleIds)->delete();
+        JointPermission::query()->whereIn('role_id', $roleIds)->delete();
     }
 
     /**
      * Delete the entity jointPermissions for a particular entity.
      * @param Entity $entity
-     * @throws \Throwable
+     * @throws Throwable
      */
     public function deleteJointPermissionsForEntity(Entity $entity)
     {
@@ -327,10 +285,10 @@ class PermissionService
 
     /**
      * Delete all of the entity jointPermissions for a list of entities.
-     * @param \BookStack\Entities\Models\Entity[] $entities
-     * @throws \Throwable
+     * @param Entity[] $entities
+     * @throws Throwable
      */
-    protected function deleteManyJointPermissionsForEntities($entities)
+    protected function deleteManyJointPermissionsForEntities(array $entities)
     {
         if (count($entities) === 0) {
             return;
@@ -352,19 +310,19 @@ class PermissionService
     }
 
     /**
-     * Create & Save entity jointPermissions for many entities and jointPermissions.
-     * @param Collection $entities
-     * @param array $roles
-     * @throws \Throwable
+     * Create & Save entity jointPermissions for many entities and roles.
+     * @param Entity[] $entities
+     * @param Role[] $roles
+     * @throws Throwable
      */
-    protected function createManyJointPermissions($entities, $roles)
+    protected function createManyJointPermissions(array $entities, array $roles)
     {
         $this->readyEntityCache($entities);
         $jointPermissions = [];
 
         // Fetch Entity Permissions and create a mapping of entity restricted statuses
         $entityRestrictedMap = [];
-        $permissionFetch = $this->entityPermission->newQuery();
+        $permissionFetch = EntityPermission::query();
         foreach ($entities as $entity) {
             $entityRestrictedMap[$entity->getMorphClass() . ':' . $entity->id] = boolval($entity->getRawAttribute('restricted'));
             $permissionFetch->orWhere(function ($query) use ($entity) {
@@ -408,16 +366,14 @@ class PermissionService
 
     /**
      * Get the actions related to an entity.
-     * @param \BookStack\Entities\Models\Entity $entity
-     * @return array
      */
-    protected function getActions(Entity $entity)
+    protected function getActions(Entity $entity): array
     {
         $baseActions = ['view', 'update', 'delete'];
-        if ($entity->isA('chapter') || $entity->isA('book')) {
+        if ($entity instanceof Chapter || $entity instanceof Book) {
             $baseActions[] = 'page-create';
         }
-        if ($entity->isA('book')) {
+        if ($entity instanceof Book) {
             $baseActions[] = 'chapter-create';
         }
         return $baseActions;
@@ -426,14 +382,8 @@ class PermissionService
     /**
      * Create entity permission data for an entity and role
      * for a particular action.
-     * @param Entity $entity
-     * @param Role $role
-     * @param string $action
-     * @param array $permissionMap
-     * @param array $rolePermissionMap
-     * @return array
      */
-    protected function createJointPermissionData(Entity $entity, Role $role, $action, $permissionMap, $rolePermissionMap)
+    protected function createJointPermissionData(Entity $entity, Role $role, string $action, array $permissionMap, array $rolePermissionMap): array
     {
         $permissionPrefix = (strpos($action, '-') === false ? ($entity->getType() . '-') : '') . $action;
         $roleHasPermission = isset($rolePermissionMap[$role->getRawAttribute('id') . ':' . $permissionPrefix . '-all']);
@@ -450,7 +400,7 @@ class PermissionService
             return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
         }
 
-        if ($entity->isA('book') || $entity->isA('bookshelf')) {
+        if ($entity instanceof Book || $entity instanceof Bookshelf) {
             return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
         }
 
@@ -460,7 +410,7 @@ class PermissionService
         $hasPermissiveAccessToParents = !$book->restricted;
 
         // For pages with a chapter, Check if explicit permissions are set on the Chapter
-        if ($entity->isA('page') && $entity->chapter_id !== 0 && $entity->chapter_id !== '0') {
+        if ($entity instanceof Page && intval($entity->chapter_id) !== 0) {
             $chapter = $this->getChapter($entity->chapter_id);
             $hasPermissiveAccessToParents = $hasPermissiveAccessToParents && !$chapter->restricted;
             if ($chapter->restricted) {
@@ -479,38 +429,27 @@ class PermissionService
 
     /**
      * Check for an active restriction in an entity map.
-     * @param $entityMap
-     * @param Entity $entity
-     * @param Role $role
-     * @param $action
-     * @return bool
      */
-    protected function mapHasActiveRestriction($entityMap, Entity $entity, Role $role, $action)
+    protected function mapHasActiveRestriction(array $entityMap, Entity $entity, Role $role, string $action): bool
     {
         $key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action;
-        return isset($entityMap[$key]) ? $entityMap[$key] : false;
+        return $entityMap[$key] ?? false;
     }
 
     /**
      * Create an array of data with the information of an entity jointPermissions.
      * Used to build data for bulk insertion.
-     * @param \BookStack\Entities\Models\Entity $entity
-     * @param Role $role
-     * @param $action
-     * @param $permissionAll
-     * @param $permissionOwn
-     * @return array
      */
-    protected function createJointPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn)
+    protected function createJointPermissionDataArray(Entity $entity, Role $role, string $action, bool $permissionAll, bool $permissionOwn): array
     {
         return [
-            'role_id'            => $role->getRawAttribute('id'),
-            'entity_id'          => $entity->getRawAttribute('id'),
-            'entity_type'        => $entity->getMorphClass(),
-            'action'             => $action,
-            'has_permission'     => $permissionAll,
+            'role_id' => $role->getRawAttribute('id'),
+            'entity_id' => $entity->getRawAttribute('id'),
+            'entity_type' => $entity->getMorphClass(),
+            'action' => $action,
+            'has_permission' => $permissionAll,
             'has_permission_own' => $permissionOwn,
-            'owned_by'         => $entity->getRawAttribute('owned_by')
+            'owned_by' => $entity->getRawAttribute('owned_by'),
         ];
     }
 
@@ -524,55 +463,47 @@ class PermissionService
 
         $baseQuery = $ownable->newQuery()->where('id', '=', $ownable->id);
         $action = end($explodedPermission);
-        $this->currentAction = $action;
+        $user = $this->currentUser();
 
         $nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
 
         // Handle non entity specific jointPermissions
         if (in_array($explodedPermission[0], $nonJointPermissions)) {
-            $allPermission = $this->currentUser() && $this->currentUser()->can($permission . '-all');
-            $ownPermission = $this->currentUser() && $this->currentUser()->can($permission . '-own');
-            $this->currentAction = 'view';
+            $allPermission = $user && $user->can($permission . '-all');
+            $ownPermission = $user && $user->can($permission . '-own');
             $ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by';
-            $isOwner = $this->currentUser() && $this->currentUser()->id === $ownable->$ownerField;
+            $isOwner = $user && $user->id === $ownable->$ownerField;
             return ($allPermission || ($isOwner && $ownPermission));
         }
 
         // Handle abnormal create jointPermissions
         if ($action === 'create') {
-            $this->currentAction = $permission;
+            $action = $permission;
         }
 
-        $q = $this->entityRestrictionQuery($baseQuery)->count() > 0;
+        $hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0;
         $this->clean();
-        return $q;
+        return $hasAccess;
     }
 
     /**
      * Checks if a user has the given permission for any items in the system.
      * Can be passed an entity instance to filter on a specific type.
-     * @param string $permission
-     * @param string $entityClass
-     * @return bool
      */
-    public function checkUserHasPermissionOnAnything(string $permission, string $entityClass = null)
+    public function checkUserHasPermissionOnAnything(string $permission, ?string $entityClass = null): bool
     {
         $userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray();
         $userId = $this->currentUser()->id;
 
-        $permissionQuery = $this->db->table('joint_permissions')
+        $permissionQuery = JointPermission::query()
             ->where('action', '=', $permission)
             ->whereIn('role_id', $userRoleIds)
-            ->where(function ($query) use ($userId) {
-                $query->where('has_permission', '=', 1)
-                    ->orWhere(function ($query2) use ($userId) {
-                        $query2->where('has_permission_own', '=', 1)
-                            ->where('owned_by', '=', $userId);
-                    });
+            ->where(function (Builder $query) use ($userId) {
+                $this->addJointHasPermissionCheck($query, $userId);
             });
 
         if (!is_null($entityClass)) {
-            $entityInstance = app()->make($entityClass);
+            $entityInstance = app($entityClass);
             $permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
         }
 
@@ -581,46 +512,22 @@ class PermissionService
         return $hasPermission;
     }
 
-    /**
-     * Check if an entity has restrictions set on itself or its
-     * parent tree.
-     * @param \BookStack\Entities\Models\Entity $entity
-     * @param $action
-     * @return bool|mixed
-     */
-    public function checkIfRestrictionsSet(Entity $entity, $action)
-    {
-        $this->currentAction = $action;
-        if ($entity->isA('page')) {
-            return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted;
-        } elseif ($entity->isA('chapter')) {
-            return $entity->restricted || $entity->book->restricted;
-        } elseif ($entity->isA('book')) {
-            return $entity->restricted;
-        }
-    }
-
     /**
      * The general query filter to remove all entities
      * that the current user does not have access to.
-     * @param $query
-     * @return mixed
-     */
-    protected function entityRestrictionQuery($query)
-    {
-        $q = $query->where(function ($parentQuery) {
-            $parentQuery->whereHas('jointPermissions', function ($permissionQuery) {
-                $permissionQuery->whereIn('role_id', $this->getRoles())
-                    ->where('action', '=', $this->currentAction)
-                    ->where(function ($query) {
-                        $query->where('has_permission', '=', true)
-                            ->orWhere(function ($query) {
-                                $query->where('has_permission_own', '=', true)
-                                    ->where('owned_by', '=', $this->currentUser()->id);
-                            });
+     */
+    protected function entityRestrictionQuery(Builder $query, string $action): Builder
+    {
+        $q = $query->where(function ($parentQuery) use ($action) {
+            $parentQuery->whereHas('jointPermissions', function ($permissionQuery) use ($action) {
+                $permissionQuery->whereIn('role_id', $this->getCurrentUserRoles())
+                    ->where('action', '=', $action)
+                    ->where(function (Builder $query) {
+                        $this->addJointHasPermissionCheck($query, $this->currentUser()->id);
                     });
             });
         });
+
         $this->clean();
         return $q;
     }
@@ -634,14 +541,10 @@ class PermissionService
         $this->clean();
         return $query->where(function (Builder $parentQuery) use ($ability) {
             $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
-                $permissionQuery->whereIn('role_id', $this->getRoles())
+                $permissionQuery->whereIn('role_id', $this->getCurrentUserRoles())
                     ->where('action', '=', $ability)
                     ->where(function (Builder $query) {
-                        $query->where('has_permission', '=', true)
-                            ->orWhere(function (Builder $query) {
-                                $query->where('has_permission_own', '=', true)
-                                    ->where('owned_by', '=', $this->currentUser()->id);
-                            });
+                        $this->addJointHasPermissionCheck($query, $this->currentUser()->id);
                     });
             });
         });
@@ -651,7 +554,7 @@ class PermissionService
      * Extend the given page query to ensure draft items are not visible
      * unless created by the given user.
      */
-    public function enforceDraftVisiblityOnQuery(Builder $query): Builder
+    public function enforceDraftVisibilityOnQuery(Builder $query): Builder
     {
         return $query->where(function (Builder $query) {
             $query->where('draft', '=', false)
@@ -663,109 +566,90 @@ class PermissionService
     }
 
     /**
-     * Add restrictions for a generic entity
-     * @param string $entityType
-     * @param Builder|\BookStack\Entities\Models\Entity $query
-     * @param string $action
-     * @return Builder
+     * Add restrictions for a generic entity.
      */
-    public function enforceEntityRestrictions($entityType, $query, $action = 'view')
+    public function enforceEntityRestrictions(Entity $entity, Builder $query, string $action = 'view'): Builder
     {
-        if (strtolower($entityType) === 'page') {
+        if ($entity instanceof Page) {
             // Prevent drafts being visible to others.
-            $query = $query->where(function ($query) {
-                $query->where('draft', '=', false)
-                    ->orWhere(function ($query) {
-                        $query->where('draft', '=', true)
-                            ->where('owned_by', '=', $this->currentUser()->id);
-                    });
-            });
+            $this->enforceDraftVisibilityOnQuery($query);
         }
 
-        $this->currentAction = $action;
-        return $this->entityRestrictionQuery($query);
+        return $this->entityRestrictionQuery($query, $action);
     }
 
     /**
      * Filter items that have entities set as a polymorphic relation.
-     * @param $query
-     * @param string $tableName
-     * @param string $entityIdColumn
-     * @param string $entityTypeColumn
-     * @param string $action
-     * @return QueryBuilder
+     * @param Builder|\Illuminate\Database\Query\Builder $query
      */
-    public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
+    public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view')
     {
-
-        $this->currentAction = $action;
         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
 
-        $q = $query->where(function ($query) use ($tableDetails) {
-            $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
-                $permissionQuery->select('id')->from('joint_permissions')
+        $q = $query->where(function ($query) use ($tableDetails, $action) {
+            $query->whereExists(function ($permissionQuery) use (&$tableDetails, $action) {
+                $permissionQuery->select(['role_id'])->from('joint_permissions')
                     ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
                     ->whereRaw('joint_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
-                    ->where('action', '=', $this->currentAction)
-                    ->whereIn('role_id', $this->getRoles())
-                    ->where(function ($query) {
-                        $query->where('has_permission', '=', true)->orWhere(function ($query) {
-                            $query->where('has_permission_own', '=', true)
-                                ->where('owned_by', '=', $this->currentUser()->id);
-                        });
+                    ->where('action', '=', $action)
+                    ->whereIn('role_id', $this->getCurrentUserRoles())
+                    ->where(function (QueryBuilder $query) {
+                        $this->addJointHasPermissionCheck($query, $this->currentUser()->id);
                     });
             });
         });
+
         $this->clean();
         return $q;
     }
 
     /**
      * Add conditions to a query to filter the selection to related entities
-     * where permissions are granted.
-     * @param $entityType
-     * @param $query
-     * @param $tableName
-     * @param $entityIdColumn
-     * @return mixed
+     * where view permissions are granted.
      */
-    public function filterRelatedEntity($entityType, $query, $tableName, $entityIdColumn)
+    public function filterRelatedEntity(string $entityClass, Builder $query, string $tableName, string $entityIdColumn): Builder
     {
-        $this->currentAction = 'view';
         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
+        $morphClass = app($entityClass)->getMorphClass();
 
-        $pageMorphClass = $this->entityProvider->get($entityType)->getMorphClass();
-
-        $q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) {
-            $query->where(function ($query) use (&$tableDetails, $pageMorphClass) {
-                $query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) {
+        $q = $query->where(function ($query) use ($tableDetails, $morphClass) {
+            $query->where(function ($query) use (&$tableDetails, $morphClass) {
+                $query->whereExists(function ($permissionQuery) use (&$tableDetails, $morphClass) {
                     $permissionQuery->select('id')->from('joint_permissions')
                         ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
-                        ->where('entity_type', '=', $pageMorphClass)
-                        ->where('action', '=', $this->currentAction)
-                        ->whereIn('role_id', $this->getRoles())
-                        ->where(function ($query) {
-                            $query->where('has_permission', '=', true)->orWhere(function ($query) {
-                                $query->where('has_permission_own', '=', true)
-                                    ->where('owned_by', '=', $this->currentUser()->id);
-                            });
+                        ->where('entity_type', '=', $morphClass)
+                        ->where('action', '=', 'view')
+                        ->whereIn('role_id', $this->getCurrentUserRoles())
+                        ->where(function (QueryBuilder $query) {
+                            $this->addJointHasPermissionCheck($query, $this->currentUser()->id);
                         });
                 });
             })->orWhere($tableDetails['entityIdColumn'], '=', 0);
         });
 
         $this->clean();
-
         return $q;
     }
 
+    /**
+     * Add the query for checking the given user id has permission
+     * within the join_permissions table.
+     * @param QueryBuilder|Builder $query
+     */
+    protected function addJointHasPermissionCheck($query, int $userIdToCheck)
+    {
+        $query->where('has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) {
+            $query->where('has_permission_own', '=', true)
+                ->where('owned_by', '=', $userIdToCheck);
+        });
+    }
+
     /**
      * Get the current user
-     * @return \BookStack\Auth\User
      */
-    private function currentUser()
+    private function currentUser(): User
     {
-        if ($this->currentUserModel === false) {
+        if (is_null($this->currentUserModel)) {
             $this->currentUserModel = user();
         }
 
@@ -775,10 +659,9 @@ class PermissionService
     /**
      * Clean the cached user elements.
      */
-    private function clean()
+    private function clean(): void
     {
-        $this->currentUserModel = false;
-        $this->userRoles = false;
-        $this->isAdminUser = null;
+        $this->currentUserModel = null;
+        $this->userRoles = null;
     }
 }
index 9d7eaa72e211a2206a88baa0bcc909bf6eedc9a2..2f3c00a4b51346e1e333304318fed80fa2872032 100644 (file)
@@ -1,7 +1,10 @@
 <?php namespace BookStack\Auth;
 
+use BookStack\Actions\Favourite;
 use BookStack\Api\ApiToken;
+use BookStack\Entities\Tools\SlugGenerator;
 use BookStack\Interfaces\Loggable;
+use BookStack\Interfaces\Sluggable;
 use BookStack\Model;
 use BookStack\Notifications\ResetPassword;
 use BookStack\Uploads\Image;
@@ -22,6 +25,7 @@ use Illuminate\Support\Collection;
  * Class User
  * @property string $id
  * @property string $name
+ * @property string $slug
  * @property string $email
  * @property string $password
  * @property Carbon $created_at
@@ -30,8 +34,9 @@ use Illuminate\Support\Collection;
  * @property int $image_id
  * @property string $external_auth_id
  * @property string $system_name
+ * @property Collection $roles
  */
-class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable
+class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable
 {
     use Authenticatable, CanResetPassword, Notifiable;
 
@@ -72,23 +77,21 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
 
     /**
      * Returns the default public user.
-     * @return User
      */
-    public static function getDefault()
+    public static function getDefault(): User
     {
         if (!is_null(static::$defaultUser)) {
             return static::$defaultUser;
         }
         
-        static::$defaultUser = static::where('system_name', '=', 'public')->first();
+        static::$defaultUser = static::query()->where('system_name', '=', 'public')->first();
         return static::$defaultUser;
     }
 
     /**
      * Check if the user is the default public user.
-     * @return bool
      */
-    public function isDefault()
+    public function isDefault(): bool
     {
         return $this->system_name === 'public';
     }
@@ -115,12 +118,10 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
 
     /**
      * Check if the user has a role.
-     * @param $role
-     * @return mixed
      */
-    public function hasSystemRole($role)
+    public function hasSystemRole(string $roleSystemName): bool
     {
-        return $this->roles->pluck('system_name')->contains($role);
+        return $this->roles->pluck('system_name')->contains($roleSystemName);
     }
 
     /**
@@ -184,9 +185,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
 
     /**
      * Get the social account associated with this user.
-     * @return HasMany
      */
-    public function socialAccounts()
+    public function socialAccounts(): HasMany
     {
         return $this->hasMany(SocialAccount::class);
     }
@@ -207,11 +207,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
     }
 
     /**
-     * Returns the user's avatar,
-     * @param int $size
-     * @return string
+     * Returns a URL to the user's avatar
      */
-    public function getAvatar($size = 50)
+    public function getAvatar(int $size = 50): string
     {
         $default = url('/user_avatar.png');
         $imageId = $this->image_id;
@@ -229,9 +227,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
 
     /**
      * Get the avatar for the user.
-     * @return BelongsTo
      */
-    public function avatar()
+    public function avatar(): BelongsTo
     {
         return $this->belongsTo(Image::class, 'image_id');
     }
@@ -244,6 +241,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
         return $this->hasMany(ApiToken::class);
     }
 
+    /**
+     * Get the favourite instances for this user.
+     */
+    public function favourites(): HasMany
+    {
+        return $this->hasMany(Favourite::class);
+    }
+
     /**
      * Get the last activity time for this user.
      */
@@ -271,15 +276,13 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
      */
     public function getProfileUrl(): string
     {
-        return url('/user/' . $this->id);
+        return url('/user/' . $this->slug);
     }
 
     /**
      * Get a shortened version of the user's name.
-     * @param int $chars
-     * @return string
      */
-    public function getShortName($chars = 8)
+    public function getShortName(int $chars = 8): string
     {
         if (mb_strlen($this->name) <= $chars) {
             return $this->name;
@@ -310,4 +313,13 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
     {
         return "({$this->id}) {$this->name}";
     }
+
+    /**
+     * @inheritDoc
+     */
+    public function refreshSlug(): string
+    {
+        $this->slug = app(SlugGenerator::class)->generate($this);
+        return $this->slug;
+    }
 }
index 29a0ebc14aceae23c3e33fcfece504b4897d14b8..15ce2cbc9bb28d2b9aaf52208cc4fc0d488a358d 100644 (file)
@@ -8,13 +8,11 @@ use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Models\Page;
 use BookStack\Exceptions\NotFoundException;
 use BookStack\Exceptions\UserUpdateException;
-use BookStack\Uploads\Image;
 use BookStack\Uploads\UserAvatars;
 use Exception;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Collection;
 use Illuminate\Pagination\LengthAwarePaginator;
-use Images;
 use Log;
 
 class UserRepo
@@ -45,6 +43,14 @@ class UserRepo
         return User::query()->findOrFail($id);
     }
 
+    /**
+     * Get a user by their slug.
+     */
+    public function getBySlug(string $slug): User
+    {
+        return User::query()->where('slug', '=', $slug)->firstOrFail();
+    }
+
     /**
      * Get all the users with their permissions.
      */
@@ -159,7 +165,13 @@ class UserRepo
             'email_confirmed' => $emailConfirmed,
             'external_auth_id' => $data['external_auth_id'] ?? '',
         ];
-        return User::query()->forceCreate($details);
+
+        $user = new User();
+        $user->forceFill($details);
+        $user->refreshSlug();
+        $user->save();
+
+        return $user;
     }
 
     /**
@@ -170,16 +182,11 @@ class UserRepo
     {
         $user->socialAccounts()->delete();
         $user->apiTokens()->delete();
+        $user->favourites()->delete();
         $user->delete();
         
         // Delete user profile images
-        $profileImages = Image::query()->where('type', '=', 'user')
-            ->where('uploaded_to', '=', $user->id)
-            ->get();
-
-        foreach ($profileImages as $image) {
-            Images::destroy($image);
-        }
+        $this->userAvatar->destroyAllForUser($user);
 
         if (!empty($newOwnerId)) {
             $newOwner = User::query()->find($newOwnerId);
index 762845e9f2bb681d47504c66a72cf8d37d49cc85..ea5252f4fc282a2b47fc1943bd6d55006b6796b7 100755 (executable)
@@ -19,13 +19,6 @@ return [
     // private configuration variables so should remain disabled in public.
     'debug' => env('APP_DEBUG', false),
 
-    // Set the default view type for various lists. Can be overridden by user preferences.
-    // These will be used for public viewers and users that have not set a preference.
-    'views' => [
-        'books' => env('APP_VIEWS_BOOKS', 'list'),
-        'bookshelves' => env('APP_VIEWS_BOOKSHELVES', 'grid'),
-    ],
-
     // The number of revisions to keep in the database.
     // Once this limit is reached older revisions will be deleted.
     // If set to false then a limit will not be enforced.
@@ -63,7 +56,7 @@ return [
     'locale' => env('APP_LANG', 'en'),
 
     // Locales available
-    'locales' => ['en', 'ar', 'bg', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'fa', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl',  'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW',],
+    'locales' => ['en', 'ar', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'de_informal', 'es', 'es_AR', 'fa', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'ko', 'lv', 'nl', 'nb', 'pt', 'pt_BR', 'sk', 'sl', 'sv', 'pl',  'ru', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW',],
 
     //  Application Fallback Locale
     'fallback_locale' => 'en',
@@ -122,6 +115,7 @@ return [
         BookStack\Providers\TranslationServiceProvider::class,
 
         // BookStack custom service providers
+        BookStack\Providers\ThemeServiceProvider::class,
         BookStack\Providers\AuthServiceProvider::class,
         BookStack\Providers\AppServiceProvider::class,
         BookStack\Providers\BroadcastServiceProvider::class,
@@ -190,11 +184,8 @@ return [
 
         // Custom BookStack
         'Activity' => BookStack\Facades\Activity::class,
-        'Setting'  => BookStack\Facades\Setting::class,
-        'Views'    => BookStack\Facades\Views::class,
-        'Images'   => BookStack\Facades\Images::class,
         'Permissions' => BookStack\Facades\Permissions::class,
-
+        'Theme'    => BookStack\Facades\Theme::class,
     ],
 
     // Proxy configuration
index ed654ffb9172b4789a62c922d971adb8f550976d..170666ddba86465e0b0a8a66b972116eab52b524 100644 (file)
@@ -85,6 +85,7 @@ return [
             'database'  => 'bookstack-test',
             'username'  => env('MYSQL_USER', 'bookstack-test'),
             'password'  => env('MYSQL_PASSWORD', 'bookstack-test'),
+            'port'      => $mysql_port,
             'charset'   => 'utf8mb4',
             'collation' => 'utf8mb4_unicode_ci',
             'prefix'    => '',
index 87be53df52b373d7c701a590f3051c7066fff711..094739cd9e22314cf67664814b5605692677d62e 100644 (file)
@@ -38,7 +38,7 @@ return [
          * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
          * Symbol, ZapfDingbats.
          */
-        "DOMPDF_FONT_DIR" => app_path('vendor/dompdf/dompdf/lib/fonts/'), //storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
+        "DOMPDF_FONT_DIR" => storage_path('fonts/'),  // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
 
         /**
          * The location of the DOMPDF font cache directory
@@ -219,7 +219,7 @@ return [
          *
          * @var bool
          */
-        "DOMPDF_ENABLE_JAVASCRIPT" => true,
+        "DOMPDF_ENABLE_JAVASCRIPT" => false,
 
         /**
          * Enable remote file access
index a91bdf23797ef182325da8f49d621d541f1bc8d2..abdbd382c862a36ec0c769729d0de87dbad9660d 100644 (file)
@@ -11,7 +11,7 @@
 return [
 
     // Mail driver to use.
-    // Options: smtp, mail, sendmail, log
+    // Options: smtp, sendmail, log, array
     'driver' => env('MAIL_DRIVER', 'smtp'),
 
     // SMTP host address
index d695abf325d1e5260b14d8325ea503c18bfe1abb..8ba96954905f029c5883c3b4bd5616945d9cfe5a 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+$SAML2_IDP_AUTHNCONTEXT = env('SAML2_IDP_AUTHNCONTEXT', true);
+
 return [
 
     // Display name, shown to users, for SAML2 option
@@ -139,6 +141,14 @@ return [
             //      )
             // ),
         ],
+        'security' => [
+            // SAML2 Authn context
+            // When set to false no AuthContext will be sent in the AuthNRequest,
+            // When set to true (Default) you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'.
+            // Multiple forced values can be passed via a space separated array, For example:
+            // SAML2_IDP_AUTHNCONTEXT="urn:federation:authentication:windows urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
+            'requestedAuthnContext' => is_string($SAML2_IDP_AUTHNCONTEXT) ? explode(' ', $SAML2_IDP_AUTHNCONTEXT) : $SAML2_IDP_AUTHNCONTEXT,
+        ],
     ],
 
 ];
index fcde621d2b5296bd9239085adc6409d7d39784a3..0f9f78d1e53eda3e4625fafdd8e3c964ef1a728e 100644 (file)
@@ -132,6 +132,8 @@ return [
         'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'),
         'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
         'tls_insecure' => env('LDAP_TLS_INSECURE', false),
+        'start_tls' => env('LDAP_START_TLS', false),
+        'thumbnail_attribute' => env('LDAP_THUMBNAIL_ATTRIBUTE', null),
     ],
 
 ];
index 571836bd2af2276a20732bfbe86459e83343551f..c750e1ef9a4ff48f93eb836dbdd182ea63a1a14f 100644 (file)
@@ -59,7 +59,7 @@ return [
     // The session cookie path determines the path for which the cookie will
     // be regarded as available. Typically, this will be the root path of
     // your application but you are free to change this when necessary.
-    'path' => '/',
+    'path' => '/' . (explode('/', env('APP_URL', ''), 4)[3] ?? ''),
 
     // Session Cookie Domain
     // Here you may change the domain of the cookie used to identify a session
index d84c0c2641397700da7b551414122c66e88fa24b..879c636bcda2c2f4961cf3687fccf3f4eb0860b7 100644 (file)
@@ -24,4 +24,12 @@ return [
     'app-custom-head'      => false,
     'registration-enabled' => false,
 
+    // User-level default settings
+    'user' => [
+        'dark-mode-enabled' => env('APP_DEFAULT_DARK_MODE', false),
+        'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'),
+        'bookshelf_view_type' =>env('APP_VIEWS_BOOKSHELF', 'grid'),
+        'books_view_type' => env('APP_VIEWS_BOOKS', 'grid'),
+    ],
+
 ];
index 35356210b66809c62687e8fbb1eaa437bc447106..693d9363973e8229bb37a3037db667de862b1f85 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace BookStack\Console\Commands;
 
+use BookStack\Actions\View;
 use Illuminate\Console\Command;
 
 class ClearViews extends Command
@@ -36,7 +37,7 @@ class ClearViews extends Command
      */
     public function handle()
     {
-        \Views::resetAll();
+        View::clearAll();
         $this->comment('Views cleared');
     }
 }
index b95e277d176f6ba2f50779dd82bdd3badd6c9cb3..2a16884680729e3dc502909cdc71d76addb70177 100644 (file)
@@ -4,6 +4,7 @@ namespace BookStack\Console\Commands;
 
 use Illuminate\Console\Command;
 use Illuminate\Database\Connection;
+use Illuminate\Support\Facades\DB;
 
 class UpdateUrl extends Command
 {
@@ -60,22 +61,50 @@ class UpdateUrl extends Command
             "attachments" => ["path"],
             "pages" => ["html", "text", "markdown"],
             "images" => ["url"],
+            "settings" => ["value"],
             "comments" => ["html", "text"],
         ];
 
         foreach ($columnsToUpdateByTable as $table => $columns) {
             foreach ($columns as $column) {
-                $changeCount = $this->db->table($table)->update([
-                    $column => $this->db->raw("REPLACE({$column}, '{$oldUrl}', '{$newUrl}')")
-                ]);
+                $changeCount = $this->replaceValueInTable($table, $column, $oldUrl, $newUrl);
                 $this->info("Updated {$changeCount} rows in {$table}->{$column}");
             }
         }
 
+        $jsonColumnsToUpdateByTable = [
+            "settings" => ["value"],
+        ];
+
+        foreach ($jsonColumnsToUpdateByTable as $table => $columns) {
+            foreach ($columns as $column) {
+                $oldJson = trim(json_encode($oldUrl), '"');
+                $newJson = trim(json_encode($newUrl), '"');
+                $changeCount = $this->replaceValueInTable($table, $column, $oldJson, $newJson);
+                $this->info("Updated {$changeCount} JSON encoded rows in {$table}->{$column}");
+            }
+        }
+
         $this->info("URL update procedure complete.");
+        $this->info('============================================================================');
+        $this->info('Be sure to run "php artisan cache:clear" to clear any old URLs in the cache.');
+        $this->info('============================================================================');
         return 0;
     }
 
+    /**
+     * Perform a find+replace operations in the provided table and column.
+     * Returns the count of rows changed.
+     */
+    protected function replaceValueInTable(string $table, string $column, string $oldUrl, string $newUrl): int
+    {
+        $oldQuoted = $this->db->getPdo()->quote($oldUrl);
+        $newQuoted = $this->db->getPdo()->quote($newUrl);
+        return $this->db->table($table)->update([
+            $column => $this->db->raw("REPLACE({$column}, {$oldQuoted}, {$newQuoted})")
+        ]);
+    }
+
     /**
      * Warn the user of the dangers of this operation.
      * Returns a boolean indicating if they've accepted the warnings.
index 8b968cc8b8d8268ae755bbc79ad42bd24419031e..c73fa3959423598da0e1619c07d9720760374a39 100644 (file)
@@ -1,8 +1,5 @@
 <?php namespace BookStack\Entities\Models;
 
-use BookStack\Entities\Models\Chapter;
-use BookStack\Entities\Models\Entity;
-use BookStack\Entities\Models\Book;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
@@ -49,7 +46,7 @@ abstract class BookChild extends Entity
 
         // Update all child pages if a chapter
         if ($this instanceof Chapter) {
-            foreach ($this->pages as $page) {
+            foreach ($this->pages()->withTrashed()->get() as $page) {
                 $page->changeBook($newBookId);
             }
         }
index c6b2468b0814afd124d1c64a4e0f8b116b9ec8d5..5618767693b34311ee6057fae86dde062a1192e0 100644 (file)
@@ -2,6 +2,7 @@
 
 use BookStack\Actions\Activity;
 use BookStack\Actions\Comment;
+use BookStack\Actions\Favourite;
 use BookStack\Actions\Tag;
 use BookStack\Actions\View;
 use BookStack\Auth\Permissions\EntityPermission;
@@ -9,6 +10,9 @@ use BookStack\Auth\Permissions\JointPermission;
 use BookStack\Entities\Tools\SearchIndex;
 use BookStack\Entities\Tools\SlugGenerator;
 use BookStack\Facades\Permissions;
+use BookStack\Interfaces\Favouritable;
+use BookStack\Interfaces\Sluggable;
+use BookStack\Interfaces\Viewable;
 use BookStack\Model;
 use BookStack\Traits\HasCreatorAndUpdater;
 use BookStack\Traits\HasOwner;
@@ -37,7 +41,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @method static Builder withLastView()
  * @method static Builder withViewCount()
  */
-abstract class Entity extends Model
+abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
 {
     use SoftDeletes;
     use HasCreatorAndUpdater;
@@ -289,11 +293,29 @@ abstract class Entity extends Model
     }
 
     /**
-     * Generate and set a new URL slug for this model.
+     * @inheritdoc
      */
     public function refreshSlug(): string
     {
-        $this->slug = (new SlugGenerator)->generate($this);
+        $this->slug = app(SlugGenerator::class)->generate($this);
         return $this->slug;
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function favourites(): MorphMany
+    {
+        return $this->morphMany(Favourite::class, 'favouritable');
+    }
+
+    /**
+     * Check if the entity is a favourite of the current user.
+     */
+    public function isFavourite(): bool
+    {
+        return $this->favourites()
+            ->where('user_id', '=', user()->id)
+            ->exists();
+    }
 }
index b60da01212f4c5ac8bf2fc5cd69efa0c501401c5..888a0db33b09445afb5e5639322190d6dd903e1b 100644 (file)
@@ -40,7 +40,7 @@ class Page extends BookChild
      */
     public function scopeVisible(Builder $query): Builder
     {
-        $query = Permissions::enforceDraftVisiblityOnQuery($query);
+        $query = Permissions::enforceDraftVisibilityOnQuery($query);
         return parent::scopeVisible($query);
     }
 
@@ -75,11 +75,23 @@ class Page extends BookChild
 
     /**
      * Get the associated page revisions, ordered by created date.
-     * @return mixed
+     * Only provides actual saved page revision instances, Not drafts.
+     */
+    public function revisions(): HasMany
+    {
+        return $this->allRevisions()
+            ->where('type', '=', 'version')
+            ->orderBy('created_at', 'desc')
+            ->orderBy('id', 'desc');
+    }
+
+    /**
+     * Get all revision instances assigned to this page.
+     * Includes all types of revisions.
      */
-    public function revisions()
+    public function allRevisions(): HasMany
     {
-        return $this->hasMany(PageRevision::class)->where('type', '=', 'version')->orderBy('created_at', 'desc')->orderBy('id', 'desc');
+        return $this->hasMany(PageRevision::class);
     }
 
     /**
diff --git a/app/Entities/Queries/EntityQuery.php b/app/Entities/Queries/EntityQuery.php
new file mode 100644 (file)
index 0000000..bd920c3
--- /dev/null
@@ -0,0 +1,17 @@
+<?php namespace BookStack\Entities\Queries;
+
+use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Entities\EntityProvider;
+
+abstract class EntityQuery
+{
+    protected function permissionService(): PermissionService
+    {
+        return app()->make(PermissionService::class);
+    }
+
+    protected function entityProvider(): EntityProvider
+    {
+        return app()->make(EntityProvider::class);
+    }
+}
\ No newline at end of file
diff --git a/app/Entities/Queries/Popular.php b/app/Entities/Queries/Popular.php
new file mode 100644 (file)
index 0000000..98db2fe
--- /dev/null
@@ -0,0 +1,29 @@
+<?php namespace BookStack\Entities\Queries;
+
+
+use BookStack\Actions\View;
+use Illuminate\Support\Facades\DB;
+
+class Popular extends EntityQuery
+{
+    public function run(int $count, int $page, array $filterModels = null, string $action = 'view')
+    {
+        $query = $this->permissionService()
+            ->filterRestrictedEntityRelations(View::query(), 'views', 'viewable_id', 'viewable_type', $action)
+            ->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));
+        }
+
+        return $query->with('viewable')
+            ->skip($count * ($page - 1))
+            ->take($count)
+            ->get()
+            ->pluck('viewable')
+            ->filter();
+    }
+
+}
\ No newline at end of file
diff --git a/app/Entities/Queries/RecentlyViewed.php b/app/Entities/Queries/RecentlyViewed.php
new file mode 100644 (file)
index 0000000..d528fea
--- /dev/null
@@ -0,0 +1,32 @@
+<?php namespace BookStack\Entities\Queries;
+
+use BookStack\Actions\View;
+use Illuminate\Support\Collection;
+
+class RecentlyViewed extends EntityQuery
+{
+    public function run(int $count, int $page): Collection
+    {
+        $user = user();
+        if ($user === null || $user->isDefault()) {
+            return collect();
+        }
+
+        $query = $this->permissionService()->filterRestrictedEntityRelations(
+            View::query(),
+            'views',
+            'viewable_id',
+            'viewable_type',
+            'view'
+        )
+            ->orderBy('views.updated_at', 'desc')
+            ->where('user_id', '=', user()->id);
+
+        return $query->with('viewable')
+            ->skip(($page - 1) * $count)
+            ->take($count)
+            ->get()
+            ->pluck('viewable')
+            ->filter();
+    }
+}
diff --git a/app/Entities/Queries/TopFavourites.php b/app/Entities/Queries/TopFavourites.php
new file mode 100644 (file)
index 0000000..1434180
--- /dev/null
@@ -0,0 +1,33 @@
+<?php namespace BookStack\Entities\Queries;
+
+use BookStack\Actions\Favourite;
+use Illuminate\Database\Query\JoinClause;
+
+class TopFavourites extends EntityQuery
+{
+    public function run(int $count, int $skip = 0)
+    {
+        $user = user();
+        if (is_null($user) || $user->isDefault()) {
+            return collect();
+        }
+
+        $query = $this->permissionService()
+            ->filterRestrictedEntityRelations(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type', 'view')
+            ->select('favourites.*')
+            ->leftJoin('views', function (JoinClause $join) {
+                $join->on('favourites.favouritable_id', '=', 'views.viewable_id');
+                $join->on('favourites.favouritable_type', '=', 'views.viewable_type');
+                $join->where('views.user_id', '=', user()->id);
+            })
+            ->orderBy('views.views', 'desc')
+            ->where('favourites.user_id', '=', user()->id);
+
+        return $query->with('favouritable')
+            ->skip($skip)
+            ->take($count)
+            ->get()
+            ->pluck('favouritable')
+            ->filter();
+    }
+}
index ca5748c86569fcf1a388ea91a63ca60174c8c189..651548885a815fca2c7853974f5f27926b8d2be8 100644 (file)
@@ -177,25 +177,24 @@ class PageRepo
         // Hold the old details to compare later
         $oldHtml = $page->html;
         $oldName = $page->name;
+        $oldMarkdown = $page->markdown;
 
         $this->updateTemplateStatusAndContentFromInput($page, $input);
         $this->baseRepo->update($page, $input);
 
         // Update with new details
         $page->revision_count++;
-
-        if (setting('app-editor') !== 'markdown') {
-            $page->markdown = '';
-        }
-
         $page->save();
 
         // Remove all update drafts for this user & page.
         $this->getUserDraftQuery($page)->delete();
 
         // Save a revision after updating
-        $summary = $input['summary'] ?? null;
-        if ($oldHtml !== $input['html'] || $oldName !== $input['name'] || $summary !== null) {
+        $summary = trim($input['summary'] ?? "");
+        $htmlChanged = isset($input['html']) && $input['html'] !== $oldHtml;
+        $nameChanged = isset($input['name']) && $input['name'] !== $oldName;
+        $markdownChanged = isset($input['markdown']) && $input['markdown'] !== $oldMarkdown;
+        if ($htmlChanged || $nameChanged || $markdownChanged || $summary) {
             $this->savePageRevision($page, $summary);
         }
 
@@ -213,7 +212,7 @@ class PageRepo
         if (!empty($input['markdown'] ?? '')) {
             $pageContent->setNewMarkdown($input['markdown']);
         } else {
-            $pageContent->setNewHTML($input['html']);
+            $pageContent->setNewHTML($input['html'] ?? '');
         }
     }
 
@@ -224,10 +223,6 @@ class PageRepo
     {
         $revision = new PageRevision($page->getAttributes());
 
-        if (setting('app-editor') !== 'markdown') {
-            $revision->markdown = '';
-        }
-
         $revision->page_id = $page->id;
         $revision->slug = $page->slug;
         $revision->book_slug = $page->book->slug;
@@ -290,7 +285,13 @@ class PageRepo
 
         $page->fill($revision->toArray());
         $content = new PageContent($page);
-        $content->setNewHTML($revision->html);
+
+        if (!empty($revision->markdown)) {
+            $content->setNewMarkdown($revision->markdown);
+        } else {
+            $content->setNewHTML($revision->html);
+        }
+        
         $page->updated_by = user()->id;
         $page->refreshSlug();
         $page->save();
index cb8b0ffc2d2a5eda26636d95fbd106b8789a78c2..d4984ef081dd4965e23e697a7ced6dace0e67c37 100644 (file)
@@ -13,4 +13,4 @@ class CustomStrikeThroughExtension implements ExtensionInterface
         $environment->addDelimiterProcessor(new StrikethroughDelimiterProcessor());
         $environment->addInlineRenderer(Strikethrough::class, new CustomStrikethroughRenderer());
     }
-}
\ No newline at end of file
+}
index 4371fb84ce8292a18d844d44d439f6729f0a6cb0..7de95c2637b057ecfcaca84864e3b84f4b873270 100644 (file)
@@ -21,4 +21,4 @@ class CustomStrikethroughRenderer implements InlineRendererInterface
 
         return new HtmlElement('s', $inline->getData('attributes', []), $htmlRenderer->renderInlines($inline->children()));
     }
-}
\ No newline at end of file
+}
index 62982f4ad5dbbb3ea2524b83f7ed028da4c3971c..ff502d1640c5de2252d4d0c1acbce43255c87133 100644 (file)
@@ -2,6 +2,9 @@
 
 use BookStack\Entities\Models\Page;
 use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
+use BookStack\Util\HtmlContentFilter;
 use DOMDocument;
 use DOMNodeList;
 use DOMXPath;
@@ -53,6 +56,7 @@ class PageContent
         $environment->addExtension(new TableExtension());
         $environment->addExtension(new TaskListExtension());
         $environment->addExtension(new CustomStrikeThroughExtension());
+        $environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
         $converter = new CommonMarkConverter([], $environment);
         return $converter->convertToHtml($markdown);
     }
@@ -166,7 +170,7 @@ class PageContent
         $content = $this->page->html;
 
         if (!config('app.allow_content_scripts')) {
-            $content = $this->escapeScripts($content);
+            $content = HtmlContentFilter::removeScripts($content);
         }
 
         if ($blankIncludes) {
@@ -305,65 +309,4 @@ class PageContent
 
         return $innerContent;
     }
-
-    /**
-     * Escape script tags within HTML content.
-     */
-    protected function escapeScripts(string $html) : string
-    {
-        if (empty($html)) {
-            return $html;
-        }
-
-        libxml_use_internal_errors(true);
-        $doc = new DOMDocument();
-        $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
-        $xPath = new DOMXPath($doc);
-
-        // Remove standard script tags
-        $scriptElems = $xPath->query('//script');
-        foreach ($scriptElems as $scriptElem) {
-            $scriptElem->parentNode->removeChild($scriptElem);
-        }
-
-        // Remove clickable links to JavaScript URI
-        $badLinks = $xPath->query('//*[contains(@href, \'javascript:\')]');
-        foreach ($badLinks as $badLink) {
-            $badLink->parentNode->removeChild($badLink);
-        }
-
-        // Remove forms with calls to JavaScript URI
-        $badForms = $xPath->query('//*[contains(@action, \'javascript:\')] | //*[contains(@formaction, \'javascript:\')]');
-        foreach ($badForms as $badForm) {
-            $badForm->parentNode->removeChild($badForm);
-        }
-
-        // Remove meta tag to prevent external redirects
-        $metaTags = $xPath->query('//meta[contains(@content, \'url\')]');
-        foreach ($metaTags as $metaTag) {
-            $metaTag->parentNode->removeChild($metaTag);
-        }
-
-        // Remove data or JavaScript iFrames
-        $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
-        foreach ($badIframes as $badIframe) {
-            $badIframe->parentNode->removeChild($badIframe);
-        }
-
-        // Remove 'on*' attributes
-        $onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]');
-        foreach ($onAttributes as $attr) {
-            /** @var \DOMAttr $attr*/
-            $attrName = $attr->nodeName;
-            $attr->parentNode->removeAttribute($attrName);
-        }
-
-        $html = '';
-        $topElems = $doc->documentElement->childNodes->item(0)->childNodes;
-        foreach ($topElems as $child) {
-            $html .= $doc->saveHTML($child);
-        }
-
-        return $html;
-    }
 }
index 60e3a9b7876d971edd29c681643ec58a0fbab09d..6c03c57a6105ce7a36e4c311e2b2f75ba3b13149 100644 (file)
@@ -137,5 +137,4 @@ class SearchOptions
 
         return $string;
     }
-
-}
\ No newline at end of file
+}
index acfe8d9565fdf1ea2884d337e31e0402270f6cb9..fc127f9068a49075d8cd14c2ad0e87968f96dd5b 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Entities\Tools;
 
 use BookStack\Auth\Permissions\PermissionService;
+use BookStack\Auth\User;
 use BookStack\Entities\EntityProvider;
 use BookStack\Entities\Models\Entity;
 use Illuminate\Database\Connection;
@@ -178,7 +179,7 @@ class SearchRunner
             }
         }
 
-        return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, $action);
+        return $this->permissionService->enforceEntityRestrictions($entity, $entitySelect, $action);
     }
 
     /**
@@ -270,24 +271,29 @@ class SearchRunner
 
     protected function filterCreatedBy(EloquentBuilder $query, Entity $model, $input)
     {
-        if (!is_numeric($input) && $input !== 'me') {
-            return;
-        }
-        if ($input === 'me') {
-            $input = user()->id;
+        $userSlug = $input === 'me' ? user()->slug : trim($input);
+        $user = User::query()->where('slug', '=', $userSlug)->first(['id']);
+        if ($user) {
+            $query->where('created_by', '=', $user->id);
         }
-        $query->where('created_by', '=', $input);
     }
 
     protected function filterUpdatedBy(EloquentBuilder $query, Entity $model, $input)
     {
-        if (!is_numeric($input) && $input !== 'me') {
-            return;
+        $userSlug = $input === 'me' ? user()->slug : trim($input);
+        $user = User::query()->where('slug', '=', $userSlug)->first(['id']);
+        if ($user) {
+            $query->where('updated_by', '=', $user->id);
         }
-        if ($input === 'me') {
-            $input = user()->id;
+    }
+
+    protected function filterOwnedBy(EloquentBuilder $query, Entity $model, $input)
+    {
+        $userSlug = $input === 'me' ? user()->slug : trim($input);
+        $user = User::query()->where('slug', '=', $userSlug)->first(['id']);
+        if ($user) {
+            $query->where('owned_by', '=', $user->id);
         }
-        $query->where('updated_by', '=', $input);
     }
 
     protected function filterInName(EloquentBuilder $query, Entity $model, $input)
index 7075bc72c14ce50e78119592be480f70c6756266..4501279f2a31d3c367e6a2ce34be9f1afedaf639 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Entities\Tools;
 
-use BookStack\Entities\Models\Entity;
+use BookStack\Entities\Models\BookChild;
+use BookStack\Interfaces\Sluggable;
 use Illuminate\Support\Str;
 
 class SlugGenerator
@@ -10,11 +11,11 @@ class SlugGenerator
      * Generate a fresh slug for the given entity.
      * The slug will generated so it does not conflict within the same parent item.
      */
-    public function generate(Entity $entity): string
+    public function generate(Sluggable $model): string
     {
-        $slug = $this->formatNameAsSlug($entity->name);
-        while ($this->slugInUse($slug, $entity)) {
-            $slug .= '-' . substr(md5(rand(1, 500)), 0, 3);
+        $slug = $this->formatNameAsSlug($model->name);
+        while ($this->slugInUse($slug, $model)) {
+            $slug .= '-' . Str::random(3);
         }
         return $slug;
     }
@@ -35,16 +36,16 @@ class SlugGenerator
      * Check if a slug is already in-use for this
      * type of model within the same parent.
      */
-    protected function slugInUse(string $slug, Entity $entity): bool
+    protected function slugInUse(string $slug, Sluggable $model): bool
     {
-        $query = $entity->newQuery()->where('slug', '=', $slug);
+        $query = $model->newQuery()->where('slug', '=', $slug);
 
-        if ($entity instanceof BookChild) {
-            $query->where('book_id', '=', $entity->book_id);
+        if ($model instanceof BookChild) {
+            $query->where('book_id', '=', $model->book_id);
         }
 
-        if ($entity->id) {
-            $query->where('id', '!=', $entity->id);
+        if ($model->id) {
+            $query->where('id', '!=', $model->id);
         }
 
         return $query->count() > 0;
index d2447ec6830c8935f3914a4410c16cedf81b3043..0b6081ae46a1208cc9d74b384b61a1c15f5efde3 100644 (file)
@@ -151,6 +151,7 @@ class TrashCan
     protected function destroyPage(Page $page): int
     {
         $this->destroyCommonRelations($page);
+        $page->allRevisions()->delete();
 
         // Delete Attached Files
         $attachmentService = app(AttachmentService::class);
@@ -273,11 +274,11 @@ class TrashCan
             $count++;
         };
 
-        if ($entity->isA('chapter') || $entity->isA('book')) {
+        if ($entity instanceof Chapter || $entity instanceof Book) {
             $entity->pages()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
         }
 
-        if ($entity->isA('book')) {
+        if ($entity instanceof Book) {
             $entity->chapters()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
         }
 
@@ -286,19 +287,20 @@ class TrashCan
 
     /**
      * Destroy the given entity.
+     * @throws Exception
      */
     protected function destroyEntity(Entity $entity): int
     {
-        if ($entity->isA('page')) {
+        if ($entity instanceof Page) {
             return $this->destroyPage($entity);
         }
-        if ($entity->isA('chapter')) {
+        if ($entity instanceof Chapter) {
             return $this->destroyChapter($entity);
         }
-        if ($entity->isA('book')) {
+        if ($entity instanceof Book) {
             return $this->destroyBook($entity);
         }
-        if ($entity->isA('shelf')) {
+        if ($entity instanceof Bookshelf) {
             return $this->destroyShelf($entity);
         }
     }
@@ -316,6 +318,7 @@ class TrashCan
         $entity->jointPermissions()->delete();
         $entity->searchTerms()->delete();
         $entity->deletions()->delete();
+        $entity->favourites()->delete();
 
         if ($entity instanceof HasCoverImage && $entity->cover) {
             $imageService = app()->make(ImageService::class);
index cc68ba8cf424c31c4f741e11aea34f9f50177a4e..36ea8be9de5dc8b2bbe3232483b25c3da68ad491 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace BookStack\Exceptions;
 
-class ApiAuthException extends UnauthorizedException {
+class ApiAuthException extends UnauthorizedException
+{
 
-}
\ No newline at end of file
+}
index 57078522b748bbbf905b1836705a4d24c0695bbb..a06c0fdbc103821792a62c86625354096d05a481 100644 (file)
@@ -3,49 +3,52 @@
 namespace BookStack\Exceptions;
 
 use Exception;
-use Illuminate\Auth\Access\AuthorizationException;
 use Illuminate\Auth\AuthenticationException;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
 use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 use Illuminate\Validation\ValidationException;
 use Symfony\Component\HttpKernel\Exception\HttpException;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 class Handler extends ExceptionHandler
 {
     /**
-     * A list of the exception types that should not be reported.
+     * A list of the exception types that are not reported.
      *
      * @var array
      */
     protected $dontReport = [
-        AuthorizationException::class,
-        HttpException::class,
-        ModelNotFoundException::class,
-        ValidationException::class,
         NotFoundException::class,
     ];
 
+    /**
+     * A list of the inputs that are never flashed for validation exceptions.
+     *
+     * @var array
+     */
+    protected $dontFlash = [
+        'password',
+        'password_confirmation',
+    ];
+
     /**
      * Report or log an exception.
-     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
      *
-     * @param  \Exception $e
-     * @return mixed
+     * @param Exception $exception
+     * @return void
+     *
      * @throws Exception
      */
-    public function report(Exception $e)
+    public function report(Exception $exception)
     {
-        return parent::report($e);
+        parent::report($exception);
     }
 
     /**
      * Render an exception into an HTTP response.
      *
      * @param  \Illuminate\Http\Request $request
-     * @param  \Exception $e
+     * @param Exception $e
      * @return \Illuminate\Http\Response
      */
     public function render($request, Exception $e)
@@ -54,29 +57,6 @@ class Handler extends ExceptionHandler
             return $this->renderApiException($e);
         }
 
-        // Handle notify exceptions which will redirect to the
-        // specified location then show a notification message.
-        if ($this->isExceptionType($e, NotifyException::class)) {
-            $message = $this->getOriginalMessage($e);
-            if (!empty($message)) {
-                session()->flash('error', $message);
-            }
-            return redirect($e->redirectLocation);
-        }
-
-        // Handle pretty exceptions which will show a friendly application-fitting page
-        // Which will include the basic message to point the user roughly to the cause.
-        if ($this->isExceptionType($e, PrettyException::class)  && !config('app.debug')) {
-            $message = $this->getOriginalMessage($e);
-            $code = ($e->getCode() === 0) ? 500 : $e->getCode();
-            return response()->view('errors/' . $code, ['message' => $message], $code);
-        }
-
-        // Handle 404 errors with a loaded session to enable showing user-specific information
-        if ($this->isExceptionType($e, NotFoundHttpException::class)) {
-            return \Route::respondWithRoute('fallback');
-        }
-
         return parent::render($request, $e);
     }
 
@@ -115,35 +95,6 @@ class Handler extends ExceptionHandler
         return new JsonResponse($responseData, $code, $headers);
     }
 
-    /**
-     * Check the exception chain to compare against the original exception type.
-     * @param Exception $e
-     * @param $type
-     * @return bool
-     */
-    protected function isExceptionType(Exception $e, $type)
-    {
-        do {
-            if (is_a($e, $type)) {
-                return true;
-            }
-        } while ($e = $e->getPrevious());
-        return false;
-    }
-
-    /**
-     * Get original exception message.
-     * @param Exception $e
-     * @return string
-     */
-    protected function getOriginalMessage(Exception $e)
-    {
-        do {
-            $message = $e->getMessage();
-        } while ($e = $e->getPrevious());
-        return $message;
-    }
-
     /**
      * Convert an authentication exception into an unauthenticated response.
      *
index 6314533ce11d2fdf0afab4c11131ea4f5af2e68a..e155579d601366292cdc88e5e7ae106c4e38949f 100644 (file)
@@ -22,4 +22,4 @@ class JsonDebugException extends Exception
     {
         return response()->json($this->data);
     }
-}
\ No newline at end of file
+}
index af85ee4c9a2ffe3768748758946cdade32dc29b5..94a4054152b6e9a1c3ad1e78d622ad1b0136eac0 100644 (file)
@@ -5,7 +5,6 @@ class NotFoundException extends PrettyException
 
     /**
      * NotFoundException constructor.
-     * @param string $message
      */
     public function __construct($message = 'Item not found')
     {
index 4f810596099cf2132093677b571ce40bf44a5aa8..efca625708b75ef2509d4f9790664c8ab3c18d24 100644 (file)
@@ -1,8 +1,10 @@
 <?php namespace BookStack\Exceptions;
 
-class NotifyException extends \Exception
-{
+use Exception;
+use Illuminate\Contracts\Support\Responsable;
 
+class NotifyException extends Exception implements Responsable
+{
     public $message;
     public $redirectLocation;
 
@@ -15,4 +17,19 @@ class NotifyException extends \Exception
         $this->redirectLocation = $redirectLocation;
         parent::__construct();
     }
+
+    /**
+     * Send the response for this type of exception.
+     * @inheritdoc
+     */
+    public function toResponse($request)
+    {
+        $message = $this->getMessage();
+
+        if (!empty($message)) {
+            session()->flash('error', $message);
+        }
+
+        return redirect($this->redirectLocation);
+    }
 }
index 7fad7df45812e1fc2e75881e50af063b07c23ff5..8ed135de7c11f0e3c0b89a3845e4dc4d77cfca1c 100644 (file)
@@ -1,6 +1,43 @@
 <?php namespace BookStack\Exceptions;
 
-class PrettyException extends \Exception
+use Exception;
+use Illuminate\Contracts\Support\Responsable;
+
+class PrettyException extends Exception implements Responsable
 {
+    /**
+     * @var ?string
+     */
+    protected $subtitle = null;
+
+    /**
+     * @var ?string
+     */
+    protected $details = null;
+
+    /**
+     * Render a response for when this exception occurs.
+     * @inheritdoc
+     */
+    public function toResponse($request)
+    {
+        $code = ($this->getCode() === 0) ? 500 : $this->getCode();
+        return response()->view('errors.' . $code, [
+            'message' => $this->getMessage(),
+            'subtitle' => $this->subtitle,
+            'details' => $this->details,
+        ], $code);
+    }
+
+    public function setSubtitle(string $subtitle): self
+    {
+        $this->subtitle = $subtitle;
+        return $this;
+    }
 
+    public function setDetails(string $details): self
+    {
+        $this->details = $details;
+        return $this;
+    }
 }
index 525b431c7b45de7538e37526f31cf5f4cd9764de..a13ba3a55d807033b7b79a19d8684396a4649d7c 100644 (file)
@@ -14,4 +14,4 @@ class UnauthorizedException extends Exception
     {
         parent::__construct($message, $code);
     }
-}
\ No newline at end of file
+}
diff --git a/app/Facades/Setting.php b/app/Facades/Setting.php
deleted file mode 100644 (file)
index 80feef8..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php namespace BookStack\Facades;
-
-use Illuminate\Support\Facades\Facade;
-
-class Setting extends Facade
-{
-    /**
-     * Get the registered name of the component.
-     *
-     * @return string
-     */
-    protected static function getFacadeAccessor()
-    {
-        return 'setting';
-    }
-}
similarity index 70%
rename from app/Facades/Images.php
rename to app/Facades/Theme.php
index fdbd35a99ad321a680a6619dc22c8db797e9f14c..9f4f61ecb47cfeb3938a6e399d3906e9bc7cdce2 100644 (file)
@@ -1,8 +1,9 @@
 <?php namespace BookStack\Facades;
 
+use BookStack\Theming\ThemeService;
 use Illuminate\Support\Facades\Facade;
 
-class Images extends Facade
+class Theme extends Facade
 {
     /**
      * Get the registered name of the component.
@@ -11,6 +12,6 @@ class Images extends Facade
      */
     protected static function getFacadeAccessor()
     {
-        return 'images';
+        return ThemeService::class;
     }
 }
diff --git a/app/Facades/Views.php b/app/Facades/Views.php
deleted file mode 100644 (file)
index f535711..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php namespace BookStack\Facades;
-
-use Illuminate\Support\Facades\Facade;
-
-class Views extends Facade
-{
-    /**
-     * Get the registered name of the component.
-     *
-     * @return string
-     */
-    protected static function getFacadeAccessor()
-    {
-        return 'views';
-    }
-}
index 0a3d8945356cb9414fc592fb3c4f7668c272e4b7..f143ea5cd50a3d33f9a66ae58464a71b8bf9066c 100644 (file)
@@ -27,4 +27,4 @@ abstract class ApiController extends Controller
     {
         return $this->rules;
     }
-}
\ No newline at end of file
+}
index 80e86e101038f475da2e9f19a6821f7cab27b462..c63ca698cc53808edad9c279f7066af417fcad39 100644 (file)
@@ -25,5 +25,4 @@ class ApiDocsController extends ApiController
         $docs = ApiDocsGenerator::generateConsideringCache();
         return response()->json($docs);
     }
-
 }
index 1b9bddbb166321d33c7ae4c65dd931f46141365b..81ac9c7aa6c56e44bd2551e379d37e1f75ec5882 100644 (file)
@@ -91,4 +91,4 @@ class BookApiController extends ApiController
         $this->bookRepo->destroy($book);
         return response('', 204);
     }
-}
\ No newline at end of file
+}
index 57461fce55c9ce71e38d058524d5ec87fbe00ac5..4ce93defa9e152d7b7837cae05618277fccab0ba 100644 (file)
@@ -112,4 +112,4 @@ class BookshelfApiController extends ApiController
         $this->bookshelfRepo->destroy($shelf);
         return response('', 204);
     }
-}
\ No newline at end of file
+}
index eb6eecc944ec0ba29567b2147b5d88af4f39d437..f73ee4a20d227e0907ba51b7f07e8273ee580e04 100644 (file)
@@ -20,6 +20,7 @@ class AuditLogController extends Controller
             'sort' => $request->get('sort', 'created_at'),
             'date_from' => $request->get('date_from', ''),
             'date_to' => $request->get('date_to', ''),
+            'user' => $request->get('user', ''),
         ];
 
         $query = Activity::query()
@@ -34,6 +35,9 @@ class AuditLogController extends Controller
         if ($listDetails['event']) {
             $query->where('type', '=', $listDetails['event']);
         }
+        if ($listDetails['user']) {
+            $query->where('user_id', '=', $listDetails['user']);
+        }
 
         if ($listDetails['date_from']) {
             $query->where('created_at', '>=', $listDetails['date_from']);
index bffeb5f61b2f2f7528b87e6ad506f4b6dded53d2..6e6a0e779d0871e49e9863ffd76a5f8373288395 100644 (file)
@@ -2,12 +2,15 @@
 
 namespace BookStack\Http\Controllers\Auth;
 
+use BookStack\Actions\ActivityType;
 use BookStack\Auth\Access\EmailConfirmationService;
 use BookStack\Auth\UserRepo;
 use BookStack\Exceptions\ConfirmationEmailException;
 use BookStack\Exceptions\UserTokenExpiredException;
 use BookStack\Exceptions\UserTokenNotFoundException;
+use BookStack\Facades\Theme;
 use BookStack\Http\Controllers\Controller;
+use BookStack\Theming\ThemeEvents;
 use Exception;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
@@ -80,6 +83,8 @@ class ConfirmEmailController extends Controller
         $user->save();
 
         auth()->login($user);
+        Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
+        $this->logActivity(ActivityType::AUTH_LOGIN, $user);
         $this->showSuccessNotification(trans('auth.email_confirm_success'));
         $this->emailConfirmationService->deleteByUser($user);
 
index 1252e6217a8b66f1bda3b405af28085a177308de..01255f466886005e26c155b7217133bbc6f76825 100644 (file)
@@ -7,7 +7,9 @@ use BookStack\Actions\ActivityType;
 use BookStack\Auth\Access\SocialAuthService;
 use BookStack\Exceptions\LoginAttemptEmailNeededException;
 use BookStack\Exceptions\LoginAttemptException;
+use BookStack\Facades\Theme;
 use BookStack\Http\Controllers\Controller;
+use BookStack\Theming\ThemeEvents;
 use Illuminate\Foundation\Auth\AuthenticatesUsers;
 use Illuminate\Http\Request;
 
@@ -150,6 +152,7 @@ class LoginController extends Controller
             }
         }
 
+        Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
         $this->logActivity(ActivityType::AUTH_LOGIN, $user);
         return redirect()->intended($this->redirectPath());
     }
@@ -195,5 +198,4 @@ class LoginController extends Controller
 
         return redirect('/login');
     }
-
 }
index e3d22264d5301a73c2d2bee41daa1c71209512a9..7d7d8732b5f1e37d1177b230f8229b9d65b9c07f 100644 (file)
@@ -2,11 +2,14 @@
 
 namespace BookStack\Http\Controllers\Auth;
 
+use BookStack\Actions\ActivityType;
 use BookStack\Auth\Access\RegistrationService;
 use BookStack\Auth\Access\SocialAuthService;
 use BookStack\Auth\User;
 use BookStack\Exceptions\UserRegistrationException;
+use BookStack\Facades\Theme;
 use BookStack\Http\Controllers\Controller;
+use BookStack\Theming\ThemeEvents;
 use Illuminate\Foundation\Auth\RegistersUsers;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Hash;
@@ -93,6 +96,8 @@ class RegisterController extends Controller
         try {
             $user = $this->registrationService->registerUser($userData);
             auth()->login($user);
+            Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
+            $this->logActivity(ActivityType::AUTH_LOGIN, $user);
         } catch (UserRegistrationException $exception) {
             if ($exception->getMessage()) {
                 $this->showErrorNotification($exception->getMessage());
@@ -117,5 +122,4 @@ class RegisterController extends Controller
             'password' => Hash::make($data['password']),
         ]);
     }
-
 }
index 8a3bf065ed566b55062a184c76c7144797417060..dc7814c4b173055bdb07c5c4a0e298249c113e65 100644 (file)
@@ -82,5 +82,4 @@ class Saml2Controller extends Controller
 
         return redirect()->intended();
     }
-
 }
index 0c53c92330b2504cece585772131c3147fa9e566..447f0afc9358f85eb8e7975ea08930878c332c1a 100644 (file)
@@ -2,16 +2,17 @@
 
 namespace BookStack\Http\Controllers\Auth;
 
+use BookStack\Actions\ActivityType;
 use BookStack\Auth\Access\RegistrationService;
 use BookStack\Auth\Access\SocialAuthService;
 use BookStack\Exceptions\SocialDriverNotConfigured;
 use BookStack\Exceptions\SocialSignInAccountNotUsed;
 use BookStack\Exceptions\SocialSignInException;
 use BookStack\Exceptions\UserRegistrationException;
+use BookStack\Facades\Theme;
 use BookStack\Http\Controllers\Controller;
-use Illuminate\Http\RedirectResponse;
+use BookStack\Theming\ThemeEvents;
 use Illuminate\Http\Request;
-use Illuminate\Routing\Redirector;
 use Illuminate\Support\Str;
 use Laravel\Socialite\Contracts\User as SocialUser;
 
@@ -31,12 +32,11 @@ class SocialController extends Controller
         $this->registrationService = $registrationService;
     }
 
-
     /**
      * Redirect to the relevant social site.
-     * @throws \BookStack\Exceptions\SocialDriverNotConfigured
+     * @throws SocialDriverNotConfigured
      */
-    public function getSocialLogin(string $socialDriver)
+    public function login(string $socialDriver)
     {
         session()->put('social-callback', 'login');
         return $this->socialAuthService->startLogIn($socialDriver);
@@ -47,7 +47,7 @@ class SocialController extends Controller
      * @throws SocialDriverNotConfigured
      * @throws UserRegistrationException
      */
-    public function socialRegister(string $socialDriver)
+    public function register(string $socialDriver)
     {
         $this->registrationService->ensureRegistrationAllowed();
         session()->put('social-callback', 'register');
@@ -60,7 +60,7 @@ class SocialController extends Controller
      * @throws SocialDriverNotConfigured
      * @throws UserRegistrationException
      */
-    public function socialCallback(Request $request, string $socialDriver)
+    public function callback(Request $request, string $socialDriver)
     {
         if (!session()->has('social-callback')) {
             throw new SocialSignInException(trans('errors.social_no_action_defined'), '/login');
@@ -99,7 +99,7 @@ class SocialController extends Controller
     /**
      * Detach a social account from a user.
      */
-    public function detachSocialAccount(string $socialDriver)
+    public function detach(string $socialDriver)
     {
         $this->socialAuthService->detachSocialAccount($socialDriver);
         session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => Str::title($socialDriver)]));
@@ -113,7 +113,7 @@ class SocialController extends Controller
     protected function socialRegisterCallback(string $socialDriver, SocialUser $socialUser)
     {
         $socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver, $socialUser);
-        $socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser);
+        $socialAccount = $this->socialAuthService->newSocialAccount($socialDriver, $socialUser);
         $emailVerified = $this->socialAuthService->driverAutoConfirmEmailEnabled($socialDriver);
 
         // Create an array of the user data to create a new user instance
@@ -130,6 +130,8 @@ class SocialController extends Controller
 
         $user = $this->registrationService->registerUser($userData, $socialAccount, $emailVerified);
         auth()->login($user);
+        Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $user);
+        $this->logActivity(ActivityType::AUTH_LOGIN, $user);
 
         $this->showSuccessNotification(trans('auth.register_success'));
         return redirect('/');
index 926458fa613ddc1073305ea5405750c7ce01e085..ab745224836d4e82477e0513999d049c07a4528f 100644 (file)
@@ -2,11 +2,14 @@
 
 namespace BookStack\Http\Controllers\Auth;
 
+use BookStack\Actions\ActivityType;
 use BookStack\Auth\Access\UserInviteService;
 use BookStack\Auth\UserRepo;
 use BookStack\Exceptions\UserTokenExpiredException;
 use BookStack\Exceptions\UserTokenNotFoundException;
+use BookStack\Facades\Theme;
 use BookStack\Http\Controllers\Controller;
+use BookStack\Theming\ThemeEvents;
 use Exception;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
@@ -68,6 +71,8 @@ class UserInviteController extends Controller
         $user->save();
 
         auth()->login($user);
+        Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user);
+        $this->logActivity(ActivityType::AUTH_LOGIN, $user);
         $this->showSuccessNotification(trans('auth.user_invite_success', ['appName' => setting('app-name')]));
         $this->inviteService->deleteByUser($user);
 
index 3d695ba85dbdae692f35d24e429e4fc553effbd5..d111f9f0add92615563e8799500d73b6ac04950e 100644 (file)
@@ -2,6 +2,7 @@
 
 use Activity;
 use BookStack\Actions\ActivityType;
+use BookStack\Actions\View;
 use BookStack\Entities\Tools\BookContents;
 use BookStack\Entities\Models\Bookshelf;
 use BookStack\Entities\Tools\PermissionsUpdater;
@@ -30,7 +31,7 @@ class BookController extends Controller
      */
     public function index()
     {
-        $view = setting()->getForCurrentUser('books_view_type', config('app.views.books'));
+        $view = setting()->getForCurrentUser('books_view_type');
         $sort = setting()->getForCurrentUser('books_sort', 'name');
         $order = setting()->getForCurrentUser('books_sort_order', 'asc');
 
@@ -112,7 +113,7 @@ class BookController extends Controller
         $bookChildren = (new BookContents($book))->getTree(true);
         $bookParentShelves = $book->shelves()->visible()->get();
 
-        Views::add($book);
+        View::incrementFor($book);
         if ($request->has('shelf')) {
             $this->entityContextManager->setShelfContext(intval($request->get('shelf')));
         }
index 32c22e185fa20116cabc0d3221d621c0e3f70d12..b4795db09029d3c6ea2d6a8136c98c8ee66bb039 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Http\Controllers;
 
 use Activity;
+use BookStack\Actions\View;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Tools\PermissionsUpdater;
 use BookStack\Entities\Tools\ShelfContext;
@@ -32,7 +33,7 @@ class BookshelfController extends Controller
      */
     public function index()
     {
-        $view = setting()->getForCurrentUser('bookshelves_view_type', config('app.views.bookshelves', 'grid'));
+        $view = setting()->getForCurrentUser('bookshelves_view_type');
         $sort = setting()->getForCurrentUser('bookshelves_sort', 'name');
         $order = setting()->getForCurrentUser('bookshelves_sort_order', 'asc');
         $sortOptions = [
@@ -101,15 +102,26 @@ class BookshelfController extends Controller
         $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('book-view', $shelf);
 
-        Views::add($shelf);
+        $sort = setting()->getForCurrentUser('shelf_books_sort', 'default');
+        $order = setting()->getForCurrentUser('shelf_books_sort_order', 'asc');
+
+        $sortedVisibleShelfBooks = $shelf->visibleBooks()->get()
+            ->sortBy($sort === 'default' ? 'pivot.order' : $sort, SORT_REGULAR, $order === 'desc')
+            ->values()
+            ->all();
+
+        View::incrementFor($shelf);
         $this->entityContextManager->setShelfContext($shelf->id);
-        $view = setting()->getForCurrentUser('bookshelf_view_type', config('app.views.books'));
+        $view = setting()->getForCurrentUser('bookshelf_view_type');
 
         $this->setPageTitle($shelf->getShortName());
         return view('shelves.show', [
             'shelf' => $shelf,
+            'sortedVisibleShelfBooks' => $sortedVisibleShelfBooks,
             'view' => $view,
-            'activity' => Activity::entityActivity($shelf, 20, 1)
+            'activity' => Activity::entityActivity($shelf, 20, 1),
+            'order' => $order,
+            'sort' => $sort
         ]);
     }
 
index 1d69df2a2f6029e148ff59a4f1bb59f04678d8b0..fbef815821e3bcaacf15cd34b38b4594778f1739 100644 (file)
@@ -1,5 +1,6 @@
 <?php namespace BookStack\Http\Controllers;
 
+use BookStack\Actions\View;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Tools\BookContents;
 use BookStack\Entities\Repos\ChapterRepo;
@@ -64,7 +65,7 @@ class ChapterController extends Controller
 
         $sidebarTree = (new BookContents($chapter->book))->getTree();
         $pages = $chapter->getVisiblePages();
-        Views::add($chapter);
+        View::incrementFor($chapter);
 
         $this->setPageTitle($chapter->getShortName());
         return view('chapters.show', [
index 479d5ac15852be57d44370f433185c15839af226..034dfa524192013b4766880ebb878176ef95d496 100644 (file)
@@ -159,6 +159,6 @@ abstract class Controller extends BaseController
      */
     protected function getImageValidationRules(): string
     {
-        return 'image_extension|no_double_extension|mimes:jpeg,png,gif,webp';
+        return 'image_extension|mimes:jpeg,png,gif,webp';
     }
 }
diff --git a/app/Http/Controllers/FavouriteController.php b/app/Http/Controllers/FavouriteController.php
new file mode 100644 (file)
index 0000000..f4aeb4f
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+namespace BookStack\Http\Controllers;
+
+use BookStack\Entities\Models\Entity;
+use BookStack\Entities\Queries\TopFavourites;
+use BookStack\Interfaces\Favouritable;
+use BookStack\Model;
+use Illuminate\Http\Request;
+
+class FavouriteController extends Controller
+{
+    /**
+     * Show a listing of all favourite items for the current user.
+     */
+    public function index(Request $request)
+    {
+        $viewCount = 20;
+        $page = intval($request->get('page', 1));
+        $favourites = (new TopFavourites)->run($viewCount + 1, (($page - 1) * $viewCount));
+
+        $hasMoreLink = ($favourites->count() > $viewCount) ? url("/favourites?page=" . ($page+1)) : null;
+
+        return view('common.detailed-listing-with-more', [
+            'title' => trans('entities.my_favourites'),
+            'entities' => $favourites->slice(0, $viewCount),
+            'hasMoreLink' => $hasMoreLink,
+        ]);
+    }
+
+    /**
+     * Add a new item as a favourite.
+     */
+    public function add(Request $request)
+    {
+        $favouritable = $this->getValidatedModelFromRequest($request);
+        $favouritable->favourites()->firstOrCreate([
+            'user_id' => user()->id,
+        ]);
+
+        $this->showSuccessNotification(trans('activities.favourite_add_notification', [
+            'name' => $favouritable->name,
+        ]));
+        return redirect()->back();
+    }
+
+    /**
+     * Remove an item as a favourite.
+     */
+    public function remove(Request $request)
+    {
+        $favouritable = $this->getValidatedModelFromRequest($request);
+        $favouritable->favourites()->where([
+            'user_id' => user()->id,
+        ])->delete();
+
+        $this->showSuccessNotification(trans('activities.favourite_remove_notification', [
+            'name' => $favouritable->name,
+        ]));
+        return redirect()->back();
+    }
+
+    /**
+     * @throws \Illuminate\Validation\ValidationException
+     * @throws \Exception
+     */
+    protected function getValidatedModelFromRequest(Request $request): Favouritable
+    {
+        $modelInfo = $this->validate($request, [
+            'type' => 'required|string',
+            'id' => 'required|integer',
+        ]);
+
+        if (!class_exists($modelInfo['type'])) {
+            throw new \Exception('Model not found');
+        }
+
+        /** @var Model $model */
+        $model = new $modelInfo['type'];
+        if (! $model instanceof Favouritable) {
+            throw new \Exception('Model not favouritable');
+        }
+
+        $modelInstance = $model->newQuery()
+            ->where('id', '=', $modelInfo['id'])
+            ->first(['id', 'name']);
+
+        $inaccessibleEntity = ($modelInstance instanceof Entity && !userCan('view', $modelInstance));
+        if (is_null($modelInstance) || $inaccessibleEntity) {
+            throw new \Exception('Model instance not found');
+        }
+
+        return $modelInstance;
+    }
+}
index 3258f43693d5600a26985caa5de675ab04c6f8a5..7bc17052652aa65652b762c9335aee9fd72eaf4f 100644 (file)
@@ -2,11 +2,12 @@
 
 use Activity;
 use BookStack\Entities\Models\Book;
+use BookStack\Entities\Queries\RecentlyViewed;
+use BookStack\Entities\Queries\TopFavourites;
 use BookStack\Entities\Tools\PageContent;
 use BookStack\Entities\Models\Page;
 use BookStack\Entities\Repos\BookRepo;
 use BookStack\Entities\Repos\BookshelfRepo;
-use Illuminate\Http\Response;
 use Views;
 
 class HomeController extends Controller
@@ -32,12 +33,13 @@ class HomeController extends Controller
 
         $recentFactor = count($draftPages) > 0 ? 0.5 : 1;
         $recents = $this->isSignedIn() ?
-              Views::getUserRecentlyViewed(12*$recentFactor, 1)
+            (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')
             ->where('draft', false)
             ->orderBy('updated_at', 'desc')
-            ->take(12)
+            ->take($favourites->count() > 0 ? 6 : 12)
             ->get();
 
         $homepageOptions = ['default', 'books', 'bookshelves', 'page'];
@@ -51,12 +53,13 @@ class HomeController extends Controller
             'recents' => $recents,
             'recentlyUpdatedPages' => $recentlyUpdatedPages,
             'draftPages' => $draftPages,
+            'favourites' => $favourites,
         ];
 
         // Add required list ordering & sorting for books & shelves views.
         if ($homepageOption === 'bookshelves' || $homepageOption === 'books') {
             $key = $homepageOption;
-            $view = setting()->getForCurrentUser($key . '_view_type', config('app.views.' . $key));
+            $view = setting()->getForCurrentUser($key . '_view_type');
             $sort = setting()->getForCurrentUser($key . '_sort', 'name');
             $order = setting()->getForCurrentUser($key . '_sort_order', 'asc');
 
@@ -105,7 +108,7 @@ class HomeController extends Controller
      */
     public function customHeadContent()
     {
-        return view('partials.custom-head-content');
+        return view('partials.custom-head');
     }
 
     /**
index ecc36bf67e24ad531f83326ed32d22bf4f97f63d..1eb8917b360edb2fb90ddaff26eff1e2028d137a 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Http\Controllers\Images;
 
 use BookStack\Exceptions\ImageUploadException;
+use BookStack\Exceptions\NotFoundException;
 use BookStack\Http\Controllers\Controller;
 use BookStack\Uploads\Image;
 use BookStack\Uploads\ImageRepo;
@@ -27,12 +28,15 @@ class ImageController extends Controller
 
     /**
      * Provide an image file from storage.
+     * @throws NotFoundException
      */
     public function showImage(string $path)
     {
         $path = storage_path('uploads/images/' . $path);
         if (!file_exists($path)) {
-            abort(404);
+            throw (new NotFoundException(trans('errors.image_not_found')))
+                ->setSubtitle(trans('errors.image_not_found_subtitle'))
+                ->setDetails(trans('errors.image_not_found_details'));
         }
 
         return response()->file($path);
index 2bfd55fa0cf07b1797a22d31060773d6e34ec758..616970a5d05758088d04e2ec034c46dda3766913 100644 (file)
@@ -1,5 +1,6 @@
 <?php namespace BookStack\Http\Controllers;
 
+use BookStack\Actions\View;
 use BookStack\Entities\Tools\BookContents;
 use BookStack\Entities\Tools\PageContent;
 use BookStack\Entities\Tools\PageEditActivity;
@@ -7,7 +8,6 @@ use BookStack\Entities\Models\Page;
 use BookStack\Entities\Repos\PageRepo;
 use BookStack\Entities\Tools\PermissionsUpdater;
 use BookStack\Exceptions\NotFoundException;
-use BookStack\Exceptions\NotifyException;
 use BookStack\Exceptions\PermissionsException;
 use Exception;
 use Illuminate\Http\Request;
@@ -174,7 +174,8 @@ class PageController extends Controller
         if($nextPage == "#"){
             $disableNxt = "disabled";
         }
-        Views::add($page);
+        
+        View::incrementFor($page);
         $this->setPageTitle($page->getShortName());
         return view('pages.show', [
             'page' => $page,
@@ -331,7 +332,6 @@ class PageController extends Controller
      * Remove the specified page from storage.
      * @throws NotFoundException
      * @throws Throwable
-     * @throws NotifyException
      */
     public function destroy(string $bookSlug, string $pageSlug)
     {
@@ -347,7 +347,6 @@ class PageController extends Controller
     /**
      * Remove the specified draft page from storage.
      * @throws NotFoundException
-     * @throws NotifyException
      * @throws Throwable
      */
     public function destroyDraft(string $bookSlug, int $pageId)
@@ -376,9 +375,9 @@ class PageController extends Controller
             ->paginate(20)
             ->setPath(url('/pages/recently-updated'));
 
-        return view('pages.detailed-listing', [
+        return view('common.detailed-listing-paginated', [
             'title' => trans('entities.recently_updated_pages'),
-            'pages' => $pages
+            'entities' => $pages
         ]);
     }
 
index 21ebea378c06c11f897046575a3fe76a3d0185f9..8598575004f04a5e1c679288e6f5d7060b57a54a 100644 (file)
@@ -1,9 +1,6 @@
 <?php namespace BookStack\Http\Controllers;
 
-use BookStack\Actions\ViewService;
-use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Bookshelf;
-use BookStack\Entities\Models\Entity;
+use BookStack\Entities\Queries\Popular;
 use BookStack\Entities\Tools\SearchRunner;
 use BookStack\Entities\Tools\ShelfContext;
 use BookStack\Entities\Tools\SearchOptions;
@@ -12,16 +9,13 @@ use Illuminate\Http\Request;
 
 class SearchController extends Controller
 {
-    protected $viewService;
     protected $searchRunner;
     protected $entityContextManager;
 
     public function __construct(
-        ViewService $viewService,
         SearchRunner $searchRunner,
         ShelfContext $entityContextManager
     ) {
-        $this->viewService = $viewService;
         $this->searchRunner = $searchRunner;
         $this->entityContextManager = $entityContextManager;
     }
@@ -85,7 +79,7 @@ class SearchController extends Controller
             $searchTerm .= ' {type:'. implode('|', $entityTypes) .'}';
             $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20, $permission)['results'];
         } else {
-            $entities = $this->viewService->getPopular(20, 0, $entityTypes, $permission);
+            $entities = (new Popular)->run(20, 0, $entityTypes, $permission);
         }
 
         return view('search.entity-ajax-list', ['entities' => $entities]);
index ab0e9069e7c2db27fd310308d39c2be6bc14d537..3949722ead098b1d7c7e0b82dc8ca02a27a73eb3 100644 (file)
@@ -140,5 +140,4 @@ class UserApiTokenController extends Controller
         $token = ApiToken::query()->where('user_id', '=', $user->id)->where('id', '=', $tokenId)->firstOrFail();
         return [$user, $token];
     }
-
 }
index 92e1cd8b7766864527f71641efd78cf4e1c6ec39..ba35904378a6f2ca04a76baf26f78cc94b56388a 100644 (file)
@@ -5,10 +5,13 @@ use BookStack\Auth\Access\SocialAuthService;
 use BookStack\Auth\Access\UserInviteService;
 use BookStack\Auth\User;
 use BookStack\Auth\UserRepo;
+use BookStack\Exceptions\ImageUploadException;
 use BookStack\Exceptions\UserUpdateException;
 use BookStack\Uploads\ImageRepo;
+use Exception;
 use Illuminate\Http\Request;
 use Illuminate\Support\Str;
+use Illuminate\Validation\ValidationException;
 
 class UserController extends Controller
 {
@@ -61,7 +64,7 @@ class UserController extends Controller
     /**
      * Store a newly created user in storage.
      * @throws UserUpdateException
-     * @throws \Illuminate\Validation\ValidationException
+     * @throws ValidationException
      */
     public function store(Request $request)
     {
@@ -90,6 +93,7 @@ class UserController extends Controller
             $user->external_auth_id = $request->get('external_auth_id');
         }
 
+        $user->refreshSlug();
         $user->save();
 
         if ($sendInvite) {
@@ -132,8 +136,8 @@ class UserController extends Controller
     /**
      * Update the specified user in storage.
      * @throws UserUpdateException
-     * @throws \BookStack\Exceptions\ImageUploadException
-     * @throws \Illuminate\Validation\ValidationException
+     * @throws ImageUploadException
+     * @throws ValidationException
      */
     public function update(Request $request, int $id)
     {
@@ -157,6 +161,11 @@ class UserController extends Controller
             $user->email = $request->get('email');
         }
 
+        // Refresh the slug if the user's name has changed
+        if ($user->isDirty('name')) {
+            $user->refreshSlug();
+        }
+
         // Role updates
         if (userCan('users-manage') && $request->filled('roles')) {
             $roles = $request->get('roles');
@@ -216,7 +225,7 @@ class UserController extends Controller
 
     /**
      * Remove the specified user from storage.
-     * @throws \Exception
+     * @throws Exception
      */
     public function destroy(Request $request, int $id)
     {
@@ -243,25 +252,6 @@ class UserController extends Controller
         return redirect('/settings/users');
     }
 
-    /**
-     * Show the user profile page
-     */
-    public function showProfilePage($id)
-    {
-        $user = $this->userRepo->getById($id);
-
-        $userActivity = $this->userRepo->getActivity($user);
-        $recentlyCreated = $this->userRepo->getRecentlyCreated($user, 5);
-        $assetCounts = $this->userRepo->getAssetCounts($user);
-
-        return view('users.profile', [
-            'user' => $user,
-            'activity' => $userActivity,
-            'recentlyCreated' => $recentlyCreated,
-            'assetCounts' => $assetCounts
-        ]);
-    }
-
     /**
      * Update the user's preferred book-list display setting.
      */
@@ -310,7 +300,7 @@ class UserController extends Controller
      */
     public function changeSort(Request $request, string $id, string $type)
     {
-        $validSortTypes = ['books', 'bookshelves'];
+        $validSortTypes = ['books', 'bookshelves', 'shelf_books'];
         if (!in_array($type, $validSortTypes)) {
             return redirect()->back(500);
         }
@@ -353,7 +343,7 @@ class UserController extends Controller
         $this->checkPermissionOrCurrentUser('users-manage', $userId);
 
         $sort = $request->get('sort');
-        if (!in_array($sort, ['name', 'created_at', 'updated_at'])) {
+        if (!in_array($sort, ['name', 'created_at', 'updated_at', 'default'])) {
             $sort = 'name';
         }
 
diff --git a/app/Http/Controllers/UserProfileController.php b/app/Http/Controllers/UserProfileController.php
new file mode 100644 (file)
index 0000000..95e68cb
--- /dev/null
@@ -0,0 +1,25 @@
+<?php namespace BookStack\Http\Controllers;
+
+use BookStack\Auth\UserRepo;
+
+class UserProfileController extends Controller
+{
+    /**
+     * Show the user profile page
+     */
+    public function show(UserRepo $repo, string $slug)
+    {
+        $user = $repo->getBySlug($slug);
+
+        $userActivity = $repo->getActivity($user);
+        $recentlyCreated = $repo->getRecentlyCreated($user, 5);
+        $assetCounts = $repo->getAssetCounts($user);
+
+        return view('users.profile', [
+            'user' => $user,
+            'activity' => $userActivity,
+            'recentlyCreated' => $recentlyCreated,
+            'assetCounts' => $assetCounts
+        ]);
+    }
+}
index 075c98ec77b7509087d1f66a2981d7ed26a86328..694036ab48c87ecb2cfe62417e95eef23b15d563 100644 (file)
@@ -28,6 +28,7 @@ class Kernel extends HttpKernel
             \Illuminate\Session\Middleware\StartSession::class,
             \Illuminate\View\Middleware\ShareErrorsFromSession::class,
             \BookStack\Http\Middleware\VerifyCsrfToken::class,
+            \BookStack\Http\Middleware\RunThemeActions::class,
             \BookStack\Http\Middleware\Localization::class,
         ],
         'api' => [
index 4b1732810d79fc6c4dcdd2b39949354f535660ac..cbf55040a5bbeba8cb0bf8b3c9dc6fef48fe2e12 100644 (file)
@@ -33,4 +33,4 @@ trait ChecksForEmailConfirmation
 
         return false;
     }
-}
\ No newline at end of file
+}
index 597d2836548286ac8afb20f5677119f5b8ca2be3..cd084746c5ca2b6ea432d5d2ab863e807bc94581 100644 (file)
@@ -18,6 +18,8 @@ class Localization
     protected $localeMap = [
         'ar' => 'ar',
         'bg' => 'bg_BG',
+        'bs' => 'bs_BA',
+        'ca' => 'ca',
         'da' => 'da_DK',
         'de' => 'de_DE',
         'de_informal' => 'de_DE',
@@ -26,13 +28,15 @@ class Localization
         'es_AR' => 'es_AR',
         'fr' => 'fr_FR',
         'he' => 'he_IL',
+        'id' => 'id_ID',
         'it' => 'it_IT',
         'ja' => 'ja',
         'ko' => 'ko_KR',
+        'lv' => 'lv_LV',
         'nl' => 'nl_NL',
         'nb' => 'nb_NO',
         'pl' => 'pl_PL',
-        'pt' => 'pl_PT',
+        'pt' => 'pt_PT',
         'pt_BR' => 'pt_BR',
         'ru' => 'ru',
         'sk' => 'sk_SK',
diff --git a/app/Http/Middleware/RunThemeActions.php b/app/Http/Middleware/RunThemeActions.php
new file mode 100644 (file)
index 0000000..d995f14
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
+use Closure;
+
+class RunThemeActions
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        $earlyResponse = Theme::dispatch(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $request);
+        if (!is_null($earlyResponse)) {
+            return $earlyResponse;
+        }
+
+        $response = $next($request);
+        $response = Theme::dispatch(ThemeEvents::WEB_MIDDLEWARE_AFTER, $request, $response) ?? $response;
+        return $response;
+    }
+}
index d08840cd1f857415e76a2496b200c22873c30d9f..4e03aed58d50113236f5fef509a89c43101efa8b 100644 (file)
@@ -14,5 +14,4 @@ class ThrottleApiRequests extends Middleware
     {
         return (int) config('api.requests_per_minute');
     }
-
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/Favouritable.php b/app/Interfaces/Favouritable.php
new file mode 100644 (file)
index 0000000..dd335fe
--- /dev/null
@@ -0,0 +1,11 @@
+<?php namespace BookStack\Interfaces;
+
+use Illuminate\Database\Eloquent\Relations\MorphMany;
+
+interface Favouritable
+{
+    /**
+     * Get the related favourite instances.
+     */
+    public function favourites(): MorphMany;
+}
\ No newline at end of file
index 33e1d7c95ad1299d7b671a10edff08658300ebdc..923f64b00359c571134eabeead93f6509a110254 100644 (file)
@@ -8,4 +8,4 @@ interface Loggable
      * Get the string descriptor for this item.
      */
     public function logDescriptor(): string;
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/Sluggable.php b/app/Interfaces/Sluggable.php
new file mode 100644 (file)
index 0000000..6aa94e6
--- /dev/null
@@ -0,0 +1,23 @@
+<?php namespace BookStack\Interfaces;
+
+use Illuminate\Database\Eloquent\Builder;
+
+/**
+ * Interface Sluggable
+ *
+ * Assigned to models that can have slugs.
+ * Must have the below properties.
+ *
+ * @property int $id
+ * @property string $name
+ * @method Builder newQuery
+ */
+interface Sluggable
+{
+
+    /**
+     * Regenerate the slug for this model.
+     */
+    public function refreshSlug(): string;
+
+}
\ No newline at end of file
diff --git a/app/Interfaces/Viewable.php b/app/Interfaces/Viewable.php
new file mode 100644 (file)
index 0000000..4a7b6a0
--- /dev/null
@@ -0,0 +1,11 @@
+<?php namespace BookStack\Interfaces;
+
+use Illuminate\Database\Eloquent\Relations\MorphMany;
+
+interface Viewable
+{
+    /**
+     * Get all view instances for this viewable model.
+     */
+    public function views(): MorphMany;
+}
\ No newline at end of file
index 1c6180a1f4b3c3329bbb28632b4bca076cd19b8e..333542c31e992771916faba7cca9da6f5760d3be 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace BookStack\Providers;
 
 use Blade;
+use BookStack\Auth\Access\SocialAuthService;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Bookshelf;
 use BookStack\Entities\BreadcrumbsViewComposer;
@@ -8,9 +9,11 @@ use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Models\Page;
 use BookStack\Settings\Setting;
 use BookStack\Settings\SettingService;
+use Illuminate\Contracts\Cache\Repository;
 use Illuminate\Database\Eloquent\Relations\Relation;
 use Illuminate\Support\Facades\View;
 use Illuminate\Support\ServiceProvider;
+use Laravel\Socialite\Contracts\Factory as SocialiteFactory;
 use Schema;
 use URL;
 
@@ -59,7 +62,11 @@ class AppServiceProvider extends ServiceProvider
     public function register()
     {
         $this->app->singleton(SettingService::class, function ($app) {
-            return new SettingService($app->make(Setting::class), $app->make('Illuminate\Contracts\Cache\Repository'));
+            return new SettingService($app->make(Setting::class), $app->make(Repository::class));
+        });
+
+        $this->app->singleton(SocialAuthService::class, function($app) {
+            return new SocialAuthService($app->make(SocialiteFactory::class));
         });
     }
 }
index b4158187cd5fe5225b68aa013d033d67d05edb30..ca86b6607e2d2875ad006f884821616c180f6201 100644 (file)
@@ -3,9 +3,8 @@
 namespace BookStack\Providers;
 
 use BookStack\Actions\ActivityService;
-use BookStack\Actions\ViewService;
 use BookStack\Auth\Permissions\PermissionService;
-use BookStack\Settings\SettingService;
+use BookStack\Theming\ThemeService;
 use BookStack\Uploads\ImageService;
 use Illuminate\Support\ServiceProvider;
 
@@ -32,14 +31,6 @@ class CustomFacadeProvider extends ServiceProvider
             return $this->app->make(ActivityService::class);
         });
 
-        $this->app->singleton('views', function () {
-            return $this->app->make(ViewService::class);
-        });
-
-        $this->app->singleton('setting', function () {
-            return $this->app->make(SettingService::class);
-        });
-
         $this->app->singleton('images', function () {
             return $this->app->make(ImageService::class);
         });
@@ -47,5 +38,9 @@ class CustomFacadeProvider extends ServiceProvider
         $this->app->singleton('permissions', function () {
             return $this->app->make(PermissionService::class);
         });
+
+        $this->app->singleton('theme', function () {
+            return $this->app->make(ThemeService::class);
+        });
     }
 }
index 4a5272b409afac6d196b18a947377504f1c7074f..b668a4cd22b4312f6f7ce17649177de25a09ce1e 100644 (file)
@@ -18,11 +18,6 @@ class CustomValidationServiceProvider extends ServiceProvider
             return in_array(strtolower($value->getClientOriginalExtension()), $validImageExtensions);
         });
 
-        Validator::extend('no_double_extension', function ($attribute, $value, $parameters, $validator) {
-            $uploadName = $value->getClientOriginalName();
-            return substr_count($uploadName, '.') < 2;
-        });
-
         Validator::extend('safe_url', function ($attribute, $value, $parameters, $validator) {
             $cleanLinkName = strtolower(trim($value));
             $isJs = strpos($cleanLinkName, 'javascript:') === 0;
diff --git a/app/Providers/ThemeServiceProvider.php b/app/Providers/ThemeServiceProvider.php
new file mode 100644 (file)
index 0000000..c41a15a
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace BookStack\Providers;
+
+use BookStack\Theming\ThemeEvents;
+use BookStack\Theming\ThemeService;
+use Illuminate\Support\ServiceProvider;
+
+class ThemeServiceProvider extends ServiceProvider
+{
+    /**
+     * Register services.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $this->app->singleton(ThemeService::class, function ($app) {
+            return new ThemeService;
+        });
+    }
+
+    /**
+     * Bootstrap services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $themeService = $this->app->make(ThemeService::class);
+        $themeService->readThemeActions();
+        $themeService->dispatch(ThemeEvents::APP_BOOT, $this->app);
+    }
+}
index 9ff607afe914a8bc0d6c2e6fea620a2bba092f90..b7fb9b117ac18896cbd8cebcc54c08d21350a563 100644 (file)
@@ -17,5 +17,4 @@ class TranslationServiceProvider extends BaseProvider
             return new FileLoader($app['files'], $app['path.lang']);
         });
     }
-
-}
\ No newline at end of file
+}
index 1c053b3848ea779d480adea01834dce0059d629d..310e0ccfff83d2898cb6b675d5924da233c4a0f0 100644 (file)
@@ -1,5 +1,6 @@
 <?php namespace BookStack\Settings;
 
+use BookStack\Auth\User;
 use Illuminate\Contracts\Cache\Repository as Cache;
 
 /**
@@ -9,7 +10,6 @@ use Illuminate\Contracts\Cache\Repository as Cache;
  */
 class SettingService
 {
-
     protected $setting;
     protected $cache;
     protected $localCache = [];
@@ -18,8 +18,6 @@ class SettingService
 
     /**
      * SettingService constructor.
-     * @param Setting $setting
-     * @param Cache   $cache
      */
     public function __construct(Setting $setting, Cache $cache)
     {
@@ -30,13 +28,10 @@ class SettingService
     /**
      * Gets a setting from the database,
      * If not found, Returns default, Which is false by default.
-     * @param             $key
-     * @param string|bool $default
-     * @return bool|string
      */
-    public function get($key, $default = false)
+    public function get(string $key, $default = null)
     {
-        if ($default === false) {
+        if (is_null($default)) {
             $default = config('setting-defaults.' . $key, false);
         }
 
@@ -44,7 +39,7 @@ class SettingService
             return $this->localCache[$key];
         }
 
-        $value = $this->getValueFromStore($key, $default);
+        $value = $this->getValueFromStore($key) ?? $default;
         $formatted = $this->formatValue($value, $default);
         $this->localCache[$key] = $formatted;
         return $formatted;
@@ -52,26 +47,22 @@ class SettingService
 
     /**
      * Get a value from the session instead of the main store option.
-     * @param $key
-     * @param bool $default
-     * @return mixed
      */
-    protected function getFromSession($key, $default = false)
+    protected function getFromSession(string $key, $default = false)
     {
         $value = session()->get($key, $default);
-        $formatted = $this->formatValue($value, $default);
-        return $formatted;
+        return $this->formatValue($value, $default);
     }
 
     /**
      * Get a user-specific setting from the database or cache.
-     * @param \BookStack\Auth\User $user
-     * @param $key
-     * @param bool $default
-     * @return bool|string
      */
-    public function getUser($user, $key, $default = false)
+    public function getUser(User $user, string $key, $default = null)
     {
+        if (is_null($default)) {
+            $default = config('setting-defaults.user.' . $key, false);
+        }
+
         if ($user->isDefault()) {
             return $this->getFromSession($key, $default);
         }
@@ -80,11 +71,8 @@ class SettingService
 
     /**
      * Get a value for the current logged-in user.
-     * @param $key
-     * @param bool $default
-     * @return bool|string
      */
-    public function getForCurrentUser($key, $default = false)
+    public function getForCurrentUser(string $key, $default = null)
     {
         return $this->getUser(user(), $key, $default);
     }
@@ -92,11 +80,9 @@ class SettingService
     /**
      * Gets a setting value from the cache or database.
      * Looks at the system defaults if not cached or in database.
-     * @param $key
-     * @param $default
-     * @return mixed
+     * Returns null if nothing is found.
      */
-    protected function getValueFromStore($key, $default)
+    protected function getValueFromStore(string $key)
     {
         // Check the cache
         $cacheKey = $this->cachePrefix . $key;
@@ -109,18 +95,22 @@ class SettingService
         $settingObject = $this->getSettingObjectByKey($key);
         if ($settingObject !== null) {
             $value = $settingObject->value;
+
+            if ($settingObject->type === 'array') {
+                $value = json_decode($value, true) ?? [];
+            }
+
             $this->cache->forever($cacheKey, $value);
             return $value;
         }
 
-        return $default;
+        return null;
     }
 
     /**
      * Clear an item from the cache completely.
-     * @param $key
      */
-    protected function clearFromCache($key)
+    protected function clearFromCache(string $key)
     {
         $cacheKey = $this->cachePrefix . $key;
         $this->cache->forget($cacheKey);
@@ -131,17 +121,13 @@ class SettingService
 
     /**
      * Format a settings value
-     * @param $value
-     * @param $default
-     * @return mixed
      */
     protected function formatValue($value, $default)
     {
         // Change string booleans to actual booleans
         if ($value === 'true') {
             $value = true;
-        }
-        if ($value === 'false') {
+        } else if ($value === 'false') {
             $value = false;
         }
 
@@ -154,99 +140,97 @@ class SettingService
 
     /**
      * Checks if a setting exists.
-     * @param $key
-     * @return bool
      */
-    public function has($key)
+    public function has(string $key): bool
     {
         $setting = $this->getSettingObjectByKey($key);
         return $setting !== null;
     }
 
-    /**
-     * Check if a user setting is in the database.
-     * @param $key
-     * @return bool
-     */
-    public function hasUser($key)
-    {
-        return $this->has($this->userKey($key));
-    }
-
     /**
      * Add a setting to the database.
-     * @param $key
-     * @param $value
-     * @return bool
+     * Values can be an array or a string.
      */
-    public function put($key, $value)
+    public function put(string $key, $value): bool
     {
-        $setting = $this->setting->firstOrNew([
+        $setting = $this->setting->newQuery()->firstOrNew([
             'setting_key' => $key
         ]);
+        $setting->type = 'string';
+
+        if (is_array($value)) {
+            $setting->type = 'array';
+            $value = $this->formatArrayValue($value);
+        }
+
         $setting->value = $value;
         $setting->save();
         $this->clearFromCache($key);
         return true;
     }
 
+    /**
+     * Format an array to be stored as a setting.
+     * Array setting types are expected to be a flat array of child key=>value array items.
+     * This filters out any child items that are empty.
+     */
+    protected function formatArrayValue(array $value): string
+    {
+        $values = collect($value)->values()->filter(function (array $item) {
+            return count(array_filter($item)) > 0;
+        });
+        return json_encode($values);
+    }
+
     /**
      * Put a user-specific setting into the database.
-     * @param \BookStack\Auth\User $user
-     * @param $key
-     * @param $value
-     * @return bool
      */
-    public function putUser($user, $key, $value)
+    public function putUser(User $user, string $key, string $value): bool
     {
         if ($user->isDefault()) {
-            return session()->put($key, $value);
+            session()->put($key, $value);
+            return true;
         }
+
         return $this->put($this->userKey($user->id, $key), $value);
     }
 
     /**
      * Convert a setting key into a user-specific key.
-     * @param $key
-     * @return string
      */
-    protected function userKey($userId, $key = '')
+    protected function userKey(string $userId, string $key = ''): string
     {
         return 'user:' . $userId . ':' . $key;
     }
 
     /**
      * Removes a setting from the database.
-     * @param $key
-     * @return bool
      */
-    public function remove($key)
+    public function remove(string $key): void
     {
         $setting = $this->getSettingObjectByKey($key);
         if ($setting) {
             $setting->delete();
         }
         $this->clearFromCache($key);
-        return true;
     }
 
     /**
      * Delete settings for a given user id.
-     * @param $userId
-     * @return mixed
      */
-    public function deleteUserSettings($userId)
+    public function deleteUserSettings(string $userId)
     {
-        return $this->setting->where('setting_key', 'like', $this->userKey($userId) . '%')->delete();
+        return $this->setting->newQuery()
+            ->where('setting_key', 'like', $this->userKey($userId) . '%')
+            ->delete();
     }
 
     /**
      * Gets a setting model from the database for the given key.
-     * @param $key
-     * @return mixed
      */
-    protected function getSettingObjectByKey($key)
+    protected function getSettingObjectByKey(string $key): ?Setting
     {
-        return $this->setting->where('setting_key', '=', $key)->first();
+        return $this->setting->newQuery()
+            ->where('setting_key', '=', $key)->first();
     }
 }
diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php
new file mode 100644 (file)
index 0000000..56e1fba
--- /dev/null
@@ -0,0 +1,73 @@
+<?php namespace BookStack\Theming;
+
+/**
+ * The ThemeEvents used within BookStack.
+ *
+ * This file details the events that BookStack may fire via the custom
+ * theme system, including event names, parameters and expected return types.
+ *
+ * This system is regarded as semi-stable.
+ * We'll look to fix issues with it or migrate old event types but
+ * events and their signatures may change in new versions of BookStack.
+ * We'd advise testing any usage of these events upon upgrade.
+ */
+class ThemeEvents
+{
+    /**
+     * Application boot-up.
+     * After main services are registered.
+     * @param \BookStack\Application $app
+     */
+    const APP_BOOT = 'app_boot';
+
+    /**
+     * Web before middleware action.
+     * Runs before the request is handled but after all other middleware apart from those
+     * that depend on the current session user (Localization for example).
+     * Provides the original request to use.
+     * Return values, if provided, will be used as a new response to use.
+     * @param \Illuminate\Http\Request $request
+     * @returns \Illuminate\Http\Response|null
+     */
+    const WEB_MIDDLEWARE_BEFORE = 'web_middleware_before';
+
+    /**
+     * Web after middleware action.
+     * Runs after the request is handled but before the response is sent.
+     * Provides both the original request and the currently resolved response.
+     * Return values, if provided, will be used as a new response to use.
+     * @param \Illuminate\Http\Request $request
+     * @returns \Illuminate\Http\Response|null
+     */
+    const WEB_MIDDLEWARE_AFTER = 'web_middleware_after';
+
+    /**
+     * Auth login event.
+     * Runs right after a user is logged-in to the application by any authentication
+     * system as a standard app user. This includes a user becoming logged in
+     * after registration. This is not emitted upon API usage.
+     * @param string $authSystem
+     * @param \BookStack\Auth\User $user
+     */
+    const AUTH_LOGIN = 'auth_login';
+
+    /**
+     * Auth register event.
+     * Runs right after a user is newly registered to the application by any authentication
+     * system as a standard app user. This includes auto-registration systems used
+     * by LDAP, SAML and social systems. It only includes self-registrations.
+     * @param string $authSystem
+     * @param \BookStack\Auth\User $user
+     */
+    const AUTH_REGISTER = 'auth_register';
+
+    /**
+     * Commonmark environment configure.
+     * Provides the commonmark library environment for customization
+     * before its used to render markdown content.
+     * If the listener returns a non-null value, that will be used as an environment instead.
+     * @param \League\CommonMark\ConfigurableEnvironmentInterface $environment
+     * @returns \League\CommonMark\ConfigurableEnvironmentInterface|null
+     */
+    const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
+}
\ No newline at end of file
diff --git a/app/Theming/ThemeService.php b/app/Theming/ThemeService.php
new file mode 100644 (file)
index 0000000..895108e
--- /dev/null
@@ -0,0 +1,61 @@
+<?php namespace BookStack\Theming;
+
+use BookStack\Auth\Access\SocialAuthService;
+
+class ThemeService
+{
+    protected $listeners = [];
+
+    /**
+     * Listen to a given custom theme event,
+     * setting up the action to be ran when the event occurs.
+     */
+    public function listen(string $event, callable $action)
+    {
+        if (!isset($this->listeners[$event])) {
+            $this->listeners[$event] = [];
+        }
+
+        $this->listeners[$event][] = $action;
+    }
+
+    /**
+     * Dispatch the given event name.
+     * Runs any registered listeners for that event name,
+     * passing all additional variables to the listener action.
+     *
+     * If a callback returns a non-null value, this method will
+     * stop and return that value itself.
+     * @return mixed
+     */
+    public function dispatch(string $event, ...$args)
+    {
+        foreach ($this->listeners[$event] ?? [] as $action) {
+            $result = call_user_func_array($action, $args);
+            if (!is_null($result)) {
+                return $result;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Read any actions from the set theme path if the 'functions.php' file exists.
+     */
+    public function readThemeActions()
+    {
+        $themeActionsFile = theme_path('functions.php');
+        if (file_exists($themeActionsFile)) {
+            require $themeActionsFile;
+        }
+    }
+
+    /**
+     * @see SocialAuthService::addSocialDriver
+     */
+    public function addSocialDriver(string $driverName, array $config, string $socialiteHandler, callable $configureForRedirect = null)
+    {
+        $socialAuthService = app()->make(SocialAuthService::class);
+        $socialAuthService->addSocialDriver($driverName, $config, $socialiteHandler, $configureForRedirect);
+    }
+}
\ No newline at end of file
index ad6c3035fe2a743845b84c2d36ad42a4508b5446..ace1fa12cb47781550afa38a682b1b4bf12fc2eb 100644 (file)
@@ -24,5 +24,4 @@ trait HasCreatorAndUpdater
     {
         return $this->belongsTo(User::class, 'updated_by');
     }
-
 }
index 9d1eb3df79d23bbb54d4e970ee5b2bc29e6c5167..ff4b8c18ec9d1b9e4bb43b24cdea1c09f29446f5 100644 (file)
@@ -15,5 +15,4 @@ trait HasOwner
     {
         return $this->belongsTo(User::class, 'owned_by');
     }
-
 }
index f0f895da55c05ca8598a5d8f9bc6eb39d13b44b0..0b4a93de6bc8dc84b9145378a2830f2645160c88 100644 (file)
@@ -27,4 +27,4 @@ class FileLoader extends BaseLoader
 
         return $this->loadNamespaced($locale, $group, $namespace);
     }
-}
\ No newline at end of file
+}
index b14f49473709647e23fd03afeea406d39c752361..4437897c711e49527035d78478e0a95625b2bcbb 100644 (file)
@@ -202,6 +202,7 @@ class AttachmentService
         try {
             $storage->put($attachmentPath, $attachmentData);
         } catch (Exception $e) {
+            \Log::error('Error when attempting file upload:' . $e->getMessage());
             throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentPath]));
         }
 
index dc26af002ab5e29de70d3679d9a56282b39bb458..ca1df3c6c5e209da107bfaaa9a0829e34cb8190c 100644 (file)
@@ -3,7 +3,6 @@
 use BookStack\Entities\Models\Page;
 use BookStack\Model;
 use BookStack\Traits\HasCreatorAndUpdater;
-use Images;
 
 class Image extends Model
 {
@@ -14,23 +13,18 @@ class Image extends Model
 
     /**
      * Get a thumbnail for this image.
-     * @param  int $width
-     * @param  int $height
-     * @param bool|false $keepRatio
-     * @return string
      * @throws \Exception
      */
-    public function getThumb($width, $height, $keepRatio = false)
+    public function getThumb(int $width, int $height, bool $keepRatio = false): string
     {
-        return Images::getThumbnail($this, $width, $height, $keepRatio);
+        return app()->make(ImageService::class)->getThumbnail($this, $width, $height, $keepRatio);
     }
 
     /**
      * Get the page this image has been uploaded to.
      * Only applicable to gallery or drawio image types.
-     * @return Page|null
      */
-    public function getPage()
+    public function getPage(): ?Page
     {
         return $this->belongsTo(Page::class, 'uploaded_to')->first();
     }
index b4d743b73447a2cc99dade9b367f78e489020279..e6f7668241dafa4dcfad7a0b852bfc6d214904c8 100644 (file)
@@ -70,8 +70,7 @@ class ImageRepo
         int $uploadedTo = null,
         string $search = null,
         callable $whereClause = null
-    ): array
-    {
+    ): array {
         $imageQuery = $this->image->newQuery()->where('type', '=', strtolower($type));
 
         if ($uploadedTo !== null) {
@@ -83,7 +82,7 @@ class ImageRepo
         }
 
         // Filter by page access
-        $imageQuery = $this->restrictionService->filterRelatedEntity('page', $imageQuery, 'images', 'uploaded_to');
+        $imageQuery = $this->restrictionService->filterRelatedEntity(Page::class, $imageQuery, 'images', 'uploaded_to');
 
         if ($whereClause !== null) {
             $imageQuery = $imageQuery->where($whereClause);
@@ -102,8 +101,7 @@ class ImageRepo
         int $pageSize = 24,
         int $uploadedTo = null,
         string $search = null
-    ): array
-    {
+    ): array {
         $contextPage = $this->page->findOrFail($uploadedTo);
         $parentFilter = null;
 
index 92c3994a71e0386a792951dac75b6ad5542b9ec7..293049f4f8c35e0b55edd76c772d425386079703 100644 (file)
@@ -8,6 +8,7 @@ use Illuminate\Contracts\Cache\Repository as Cache;
 use Illuminate\Contracts\Filesystem\Factory as FileSystem;
 use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance;
 use Illuminate\Contracts\Filesystem\FileNotFoundException;
+use Illuminate\Contracts\Filesystem\Filesystem as Storage;
 use Illuminate\Support\Str;
 use Intervention\Image\Exception\NotSupportedException;
 use Intervention\Image\ImageManager;
@@ -106,9 +107,9 @@ class ImageService
         }
 
         try {
-            $storage->put($fullPath, $imageData);
-            $storage->setVisibility($fullPath, 'public');
+            $this->saveImageDataInPublicSpace($storage, $fullPath, $imageData);
         } catch (Exception $e) {
+            \Log::error('Error when attempting image upload:' . $e->getMessage());
             throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $fullPath]));
         }
 
@@ -131,6 +132,25 @@ class ImageService
         return $image;
     }
 
+    /**
+     * Save image data for the given path in the public space, if possible,
+     * for the provided storage mechanism.
+     */
+    protected function saveImageDataInPublicSpace(Storage $storage, string $path, string $data)
+    {
+        $storage->put($path, $data);
+
+        // Set visibility when a non-AWS-s3, s3-like storage option is in use.
+        // Done since this call can break s3-like services but desired for other image stores.
+        // Attempting to set ACL during above put request requires different permissions
+        // hence would technically be a breaking change for actual s3 usage.
+        $usingS3 = strtolower(config('filesystems.images')) === 's3';
+        $usingS3Like = $usingS3 && !is_null(config('filesystems.disks.s3.endpoint'));
+        if (!$usingS3Like) {
+            $storage->setVisibility($path, 'public');
+        }
+    }
+
     /**
      * Clean up an image file name to be both URL and storage safe.
      */
@@ -139,7 +159,7 @@ class ImageService
         $name = str_replace(' ', '-', $name);
         $nameParts = explode('.', $name);
         $extension = array_pop($nameParts);
-        $name = implode('.', $nameParts);
+        $name = implode('-', $nameParts);
         $name = Str::slug($name);
 
         if (strlen($name) === 0) {
@@ -190,8 +210,7 @@ class ImageService
 
         $thumbData = $this->resizeImage($storage->get($imagePath), $width, $height, $keepRatio);
 
-        $storage->put($thumbFilePath, $thumbData);
-        $storage->setVisibility($thumbFilePath, 'public');
+        $this->saveImageDataInPublicSpace($storage, $thumbFilePath, $thumbData);
         $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 60 * 72);
 
 
index b3b9d59515c336ac85893684ec5007167099112a..e98c1cfcaf5fa19488415f9237b00d4b46d12afc 100644 (file)
@@ -26,6 +26,7 @@ class UserAvatars
         }
 
         try {
+            $this->destroyAllForUser($user);
             $avatar = $this->saveAvatarImage($user);
             $user->avatar()->associate($avatar);
             $user->save();
@@ -34,6 +35,35 @@ class UserAvatars
         }
     }
 
+    /**
+     * Assign a new avatar image to the given user using the given image data.
+     */
+    public function assignToUserFromExistingData(User $user, string $imageData, string $extension): void
+    {
+        try {
+            $this->destroyAllForUser($user);
+            $avatar = $this->createAvatarImageFromData($user, $imageData, $extension);
+            $user->avatar()->associate($avatar);
+            $user->save();
+        } catch (Exception $e) {
+            Log::error('Failed to save user avatar image');
+        }
+    }
+
+    /**
+     * Destroy all user avatars uploaded to the given user.
+     */
+    public function destroyAllForUser(User $user)
+    {
+        $profileImages = Image::query()->where('type', '=', 'user')
+            ->where('uploaded_to', '=', $user->id)
+            ->get();
+
+        foreach ($profileImages as $image) {
+            $this->imageService->destroy($image);
+        }
+    }
+
     /**
      * Save an avatar image from an external service.
      * @throws Exception
@@ -50,8 +80,16 @@ class UserAvatars
         ];
 
         $userAvatarUrl = strtr($avatarUrl, $replacements);
-        $imageName = str_replace(' ', '-', $user->id . '-avatar.png');
         $imageData = $this->getAvatarImageData($userAvatarUrl);
+        return $this->createAvatarImageFromData($user, $imageData, 'png');
+    }
+
+    /**
+     * Creates a new image instance and saves it in the system as a new user avatar image.
+     */
+    protected function createAvatarImageFromData(User $user, string $imageData, string $extension): Image
+    {
+        $imageName = str_replace(' ', '-', $user->id . '-avatar.' . $extension);
 
         $image = $this->imageService->saveNew($imageName, $imageData, 'user', $user->id);
         $image->created_by = $user->id;
@@ -97,5 +135,4 @@ class UserAvatars
 
         return $url;
     }
-
-}
\ No newline at end of file
+}
diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php
new file mode 100644 (file)
index 0000000..cec927a
--- /dev/null
@@ -0,0 +1,71 @@
+<?php namespace BookStack\Util;
+
+use DOMDocument;
+use DOMNode;
+use DOMNodeList;
+use DOMXPath;
+
+class HtmlContentFilter
+{
+    /**
+     * Remove all of the script elements from the given HTML.
+     */
+    public static function removeScripts(string $html): string
+    {
+        if (empty($html)) {
+            return $html;
+        }
+
+        libxml_use_internal_errors(true);
+        $doc = new DOMDocument();
+        $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
+        $xPath = new DOMXPath($doc);
+
+        // Remove standard script tags
+        $scriptElems = $xPath->query('//script');
+        static::removeNodes($scriptElems);
+
+        // Remove clickable links to JavaScript URI
+        $badLinks = $xPath->query('//*[contains(@href, \'javascript:\')]');
+        static::removeNodes($badLinks);
+
+        // Remove forms with calls to JavaScript URI
+        $badForms = $xPath->query('//*[contains(@action, \'javascript:\')] | //*[contains(@formaction, \'javascript:\')]');
+        static::removeNodes($badForms);
+
+        // Remove meta tag to prevent external redirects
+        $metaTags = $xPath->query('//meta[contains(@content, \'url\')]');
+        static::removeNodes($metaTags);
+
+        // Remove data or JavaScript iFrames
+        $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
+        static::removeNodes($badIframes);
+
+        // Remove 'on*' attributes
+        $onAttributes = $xPath->query('//@*[starts-with(name(), \'on\')]');
+        foreach ($onAttributes as $attr) {
+            /** @var \DOMAttr $attr*/
+            $attrName = $attr->nodeName;
+            $attr->parentNode->removeAttribute($attrName);
+        }
+
+        $html = '';
+        $topElems = $doc->documentElement->childNodes->item(0)->childNodes;
+        foreach ($topElems as $child) {
+            $html .= $doc->saveHTML($child);
+        }
+
+        return $html;
+    }
+
+    /**
+     * Removed all of the given DOMNodes.
+     */
+    static protected function removeNodes(DOMNodeList $nodes): void
+    {
+        foreach ($nodes as $node) {
+            $node->parentNode->removeChild($node);
+        }
+    }
+
+}
\ No newline at end of file
index c090bfd055acc400e1ad4c46e7059b6c19820f60..c1d72b91da4fb7f5bb3efba3a2de46490ec3dce9 100644 (file)
@@ -79,9 +79,9 @@ function userCanOnAny(string $permission, string $entityClass = null): bool
 
 /**
  * Helper to access system settings.
- * @return bool|string|SettingService
+ * @return mixed|SettingService
  */
-function setting(string $key = null, $default = false)
+function setting(string $key = null, $default = null)
 {
     $settingService = resolve(SettingService::class);
 
index 8c762cf236e30dc0c5dbd4251d89d08ff06a7ae5..3e604b8fdfb29dd13cef6be681971ecdad0a9a9b 100644 (file)
@@ -5,25 +5,25 @@
     "license": "MIT",
     "type": "project",
     "require": {
-        "php": "^7.2.5",
+        "php": "^7.3|^8.0",
         "ext-curl": "*",
         "ext-dom": "*",
         "ext-gd": "*",
         "ext-json": "*",
         "ext-mbstring": "*",
         "ext-xml": "*",
-        "barryvdh/laravel-dompdf": "^0.8.7",
+        "barryvdh/laravel-dompdf": "^0.9.0",
         "barryvdh/laravel-snappy": "^0.4.8",
-        "doctrine/dbal": "^2.9",
+        "doctrine/dbal": "^2.12.1",
         "facade/ignition": "^1.16.4",
         "fideloper/proxy": "^4.4.1",
         "intervention/image": "^2.5.1",
-        "laravel/framework": "^6.20.12",
+        "laravel/framework": "^6.20.16",
         "laravel/socialite": "^5.1",
         "league/commonmark": "^1.5",
         "league/flysystem-aws-s3-v3": "^1.0.29",
         "nunomaduro/collision": "^3.1",
-        "onelogin/php-saml": "^3.3",
+        "onelogin/php-saml": "^4.0",
         "predis/predis": "^1.1.6",
         "socialiteproviders/discord": "^4.1",
         "socialiteproviders/gitlab": "^4.1",
         "socialiteproviders/okta": "^4.1",
         "socialiteproviders/slack": "^4.1",
         "socialiteproviders/twitch": "^5.3",
-        "ssddanbrown/htmldiff": "^1.0"
+        "ssddanbrown/htmldiff": "^v1.0.1"
     },
     "require-dev": {
         "barryvdh/laravel-debugbar": "^3.5.1",
         "barryvdh/laravel-ide-helper": "^2.8.2",
-        "fakerphp/faker": "^1.9.1",
+        "fakerphp/faker": "^1.13.0",
         "laravel/browser-kit-testing": "^5.2",
         "mockery/mockery": "^1.3.3",
-        "phpunit/phpunit": "^8.0",
+        "phpunit/phpunit": "^9.5.3",
         "squizlabs/php_codesniffer": "^3.5.8"
     },
     "autoload": {
@@ -87,7 +87,7 @@
         "preferred-install": "dist",
         "sort-packages": true,
         "platform": {
-            "php": "7.2.5"
+            "php": "7.3.0"
         }
     },
     "extra": {
index 61fad9d8c2586d75c14aba6cca92c6042c796b90..b458a1b59b693c5f33f186c9067d6b01059641a7 100644 (file)
@@ -4,20 +4,20 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "733852116205eef580c11a9f5c0a044f",
+    "content-hash": "b26d29958d84c91b164a8234d1a7e9e9",
     "packages": [
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.171.19",
+            "version": "3.180.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "e786e4a8b2ec85b258833d0570ff6b61348cbdb6"
+                "reference": "948a4defbe2a571cc4460725015b8e98b7060f2d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e786e4a8b2ec85b258833d0570ff6b61348cbdb6",
-                "reference": "e786e4a8b2ec85b258833d0570ff6b61348cbdb6",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/948a4defbe2a571cc4460725015b8e98b7060f2d",
+                "reference": "948a4defbe2a571cc4460725015b8e98b7060f2d",
                 "shasum": ""
             },
             "require": {
@@ -25,9 +25,9 @@
                 "ext-pcre": "*",
                 "ext-simplexml": "*",
                 "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0",
-                "guzzlehttp/promises": "^1.0",
-                "guzzlehttp/psr7": "^1.4.1",
-                "mtdowling/jmespath.php": "^2.5",
+                "guzzlehttp/promises": "^1.4.0",
+                "guzzlehttp/psr7": "^1.7.0",
+                "mtdowling/jmespath.php": "^2.6",
                 "php": ">=5.5"
             },
             "require-dev": {
             "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.171.19"
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.180.5"
             },
-            "time": "2021-01-15T19:26:11+00:00"
+            "time": "2021-05-07T18:12:43+00:00"
         },
         {
             "name": "barryvdh/laravel-dompdf",
-            "version": "v0.8.7",
+            "version": "v0.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/barryvdh/laravel-dompdf.git",
-                "reference": "30310e0a675462bf2aa9d448c8dcbf57fbcc517d"
+                "reference": "5b99e1f94157d74e450f4c97e8444fcaffa2144b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/30310e0a675462bf2aa9d448c8dcbf57fbcc517d",
-                "reference": "30310e0a675462bf2aa9d448c8dcbf57fbcc517d",
+                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/5b99e1f94157d74e450f4c97e8444fcaffa2144b",
+                "reference": "5b99e1f94157d74e450f4c97e8444fcaffa2144b",
                 "shasum": ""
             },
             "require": {
-                "dompdf/dompdf": "^0.8",
+                "dompdf/dompdf": "^1",
                 "illuminate/support": "^5.5|^6|^7|^8",
-                "php": ">=7"
+                "php": "^7.1 || ^8.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "0.8-dev"
+                    "dev-master": "0.9-dev"
                 },
                 "laravel": {
                     "providers": [
             ],
             "support": {
                 "issues": "https://github.com/barryvdh/laravel-dompdf/issues",
-                "source": "https://github.com/barryvdh/laravel-dompdf/tree/master"
+                "source": "https://github.com/barryvdh/laravel-dompdf/tree/v0.9.0"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-09-07T11:50:18+00:00"
+            "time": "2020-12-27T12:05:53+00:00"
         },
         {
             "name": "barryvdh/laravel-snappy",
         },
         {
             "name": "doctrine/cache",
-            "version": "1.10.2",
+            "version": "1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/cache.git",
-                "reference": "13e3381b25847283a91948d04640543941309727"
+                "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727",
-                "reference": "13e3381b25847283a91948d04640543941309727",
+                "url": "https://api.github.com/repos/doctrine/cache/zipball/a9c1b59eba5a08ca2770a76eddb88922f504e8e0",
+                "reference": "a9c1b59eba5a08ca2770a76eddb88922f504e8e0",
                 "shasum": ""
             },
             "require": {
                 "php": "~7.1 || ^8.0"
             },
             "conflict": {
-                "doctrine/common": ">2.2,<2.4"
+                "doctrine/common": ">2.2,<2.4",
+                "psr/cache": ">=3"
             },
             "require-dev": {
                 "alcaeus/mongo-php-adapter": "^1.1",
-                "doctrine/coding-standard": "^6.0",
+                "cache/integration-tests": "dev-master",
+                "doctrine/coding-standard": "^8.0",
                 "mongodb/mongodb": "^1.1",
-                "phpunit/phpunit": "^7.0",
-                "predis/predis": "~1.0"
+                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
+                "predis/predis": "~1.0",
+                "psr/cache": "^1.0 || ^2.0",
+                "symfony/cache": "^4.4 || ^5.2"
             },
             "suggest": {
                 "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.9.x-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
                     "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
             ],
             "support": {
                 "issues": "https://github.com/doctrine/cache/issues",
-                "source": "https://github.com/doctrine/cache/tree/1.10.x"
+                "source": "https://github.com/doctrine/cache/tree/1.11.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-07-07T18:54:01+00:00"
+            "time": "2021-04-13T14:46:17+00:00"
         },
         {
             "name": "doctrine/dbal",
-            "version": "2.10.4",
+            "version": "2.13.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/dbal.git",
-                "reference": "47433196b6390d14409a33885ee42b6208160643"
+                "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/dbal/zipball/47433196b6390d14409a33885ee42b6208160643",
-                "reference": "47433196b6390d14409a33885ee42b6208160643",
+                "url": "https://api.github.com/repos/doctrine/dbal/zipball/c800380457948e65bbd30ba92cc17cda108bf8c9",
+                "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9",
                 "shasum": ""
             },
             "require": {
                 "doctrine/cache": "^1.0",
+                "doctrine/deprecations": "^0.5.3",
                 "doctrine/event-manager": "^1.0",
                 "ext-pdo": "*",
-                "php": "^7.2"
+                "php": "^7.1 || ^8"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^8.1",
-                "jetbrains/phpstorm-stubs": "^2019.1",
-                "nikic/php-parser": "^4.4",
-                "phpstan/phpstan": "^0.12.40",
-                "phpunit/phpunit": "^8.5.5",
-                "psalm/plugin-phpunit": "^0.10.0",
+                "doctrine/coding-standard": "8.2.0",
+                "jetbrains/phpstorm-stubs": "2020.2",
+                "phpstan/phpstan": "0.12.81",
+                "phpunit/phpunit": "^7.5.20|^8.5|9.5.0",
+                "squizlabs/php_codesniffer": "3.6.0",
                 "symfony/console": "^2.0.5|^3.0|^4.0|^5.0",
-                "vimeo/psalm": "^3.14.2"
+                "vimeo/psalm": "4.6.4"
             },
             "suggest": {
                 "symfony/console": "For helpful console commands such as SQL execution and import of files."
                 "bin/doctrine-dbal"
             ],
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "2.10.x-dev",
-                    "dev-develop": "3.0.x-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
                     "Doctrine\\DBAL\\": "lib/Doctrine/DBAL"
             ],
             "support": {
                 "issues": "https://github.com/doctrine/dbal/issues",
-                "source": "https://github.com/doctrine/dbal/tree/2.10.4"
+                "source": "https://github.com/doctrine/dbal/tree/2.13.1"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-09-12T21:20:41+00:00"
+            "time": "2021-04-17T17:30:19+00:00"
+        },
+        {
+            "name": "doctrine/deprecations",
+            "version": "v0.5.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/deprecations.git",
+                "reference": "9504165960a1f83cc1480e2be1dd0a0478561314"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314",
+                "reference": "9504165960a1f83cc1480e2be1dd0a0478561314",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1|^8.0"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^6.0|^7.0|^8.0",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0",
+                "psr/log": "^1.0"
+            },
+            "suggest": {
+                "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
+            "homepage": "https://www.doctrine-project.org/",
+            "support": {
+                "issues": "https://github.com/doctrine/deprecations/issues",
+                "source": "https://github.com/doctrine/deprecations/tree/v0.5.3"
+            },
+            "time": "2021-03-21T12:59:47+00:00"
         },
         {
             "name": "doctrine/event-manager",
         },
         {
             "name": "dompdf/dompdf",
-            "version": "v0.8.6",
+            "version": "v1.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/dompdf/dompdf.git",
-                "reference": "db91d81866c69a42dad1d2926f61515a1e3f42c5"
+                "reference": "8768448244967a46d6e67b891d30878e0e15d25c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db91d81866c69a42dad1d2926f61515a1e3f42c5",
-                "reference": "db91d81866c69a42dad1d2926f61515a1e3f42c5",
+                "url": "https://api.github.com/repos/dompdf/dompdf/zipball/8768448244967a46d6e67b891d30878e0e15d25c",
+                "reference": "8768448244967a46d6e67b891d30878e0e15d25c",
                 "shasum": ""
             },
             "require": {
                 "ext-mbstring": "*",
                 "phenx/php-font-lib": "^0.5.2",
                 "phenx/php-svg-lib": "^0.3.3",
-                "php": "^7.1"
+                "php": "^7.1 || ^8.0"
             },
             "require-dev": {
                 "mockery/mockery": "^1.3",
-                "phpunit/phpunit": "^7.5",
+                "phpunit/phpunit": "^7.5 || ^8 || ^9",
                 "squizlabs/php_codesniffer": "^3.5"
             },
             "suggest": {
             "homepage": "https://github.com/dompdf/dompdf",
             "support": {
                 "issues": "https://github.com/dompdf/dompdf/issues",
-                "source": "https://github.com/dompdf/dompdf/tree/master"
+                "source": "https://github.com/dompdf/dompdf/tree/v1.0.2"
             },
-            "time": "2020-08-30T22:54:22+00:00"
+            "time": "2021-01-08T14:18:52+00:00"
         },
         {
             "name": "dragonmantank/cron-expression",
         },
         {
             "name": "facade/flare-client-php",
-            "version": "1.3.7",
+            "version": "1.8.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/facade/flare-client-php.git",
-                "reference": "fd688d3c06658f2b3b5f7bb19f051ee4ddf02492"
+                "reference": "69742118c037f34ee1ef86dc605be4a105d9e984"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/facade/flare-client-php/zipball/fd688d3c06658f2b3b5f7bb19f051ee4ddf02492",
-                "reference": "fd688d3c06658f2b3b5f7bb19f051ee4ddf02492",
+                "url": "https://api.github.com/repos/facade/flare-client-php/zipball/69742118c037f34ee1ef86dc605be4a105d9e984",
+                "reference": "69742118c037f34ee1ef86dc605be4a105d9e984",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/facade/flare-client-php/issues",
-                "source": "https://github.com/facade/flare-client-php/tree/1.3.7"
+                "source": "https://github.com/facade/flare-client-php/tree/1.8.0"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-10-21T16:02:39+00:00"
+            "time": "2021-04-30T11:11:50+00:00"
         },
         {
             "name": "facade/ignition",
-            "version": "1.16.4",
+            "version": "1.16.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/facade/ignition.git",
-                "reference": "1da1705e7f6b24ed45af05461463228da424e14f"
+                "reference": "b6aea4a99303d9d32afd486a285162a89af8a8a3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/facade/ignition/zipball/1da1705e7f6b24ed45af05461463228da424e14f",
-                "reference": "1da1705e7f6b24ed45af05461463228da424e14f",
+                "url": "https://api.github.com/repos/facade/ignition/zipball/b6aea4a99303d9d32afd486a285162a89af8a8a3",
+                "reference": "b6aea4a99303d9d32afd486a285162a89af8a8a3",
                 "shasum": ""
             },
             "require": {
                 "issues": "https://github.com/facade/ignition/issues",
                 "source": "https://github.com/facade/ignition"
             },
-            "time": "2020-10-30T13:40:01+00:00"
+            "time": "2021-02-15T10:21:49+00:00"
         },
         {
             "name": "facade/ignition-contracts",
-            "version": "1.0.1",
+            "version": "1.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/facade/ignition-contracts.git",
-                "reference": "aeab1ce8b68b188a43e81758e750151ad7da796b"
+                "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/aeab1ce8b68b188a43e81758e750151ad7da796b",
-                "reference": "aeab1ce8b68b188a43e81758e750151ad7da796b",
+                "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
+                "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1"
+                "php": "^7.3|^8.0"
             },
             "require-dev": {
-                "friendsofphp/php-cs-fixer": "^2.14",
-                "phpunit/phpunit": "^7.5|^8.0",
-                "vimeo/psalm": "^3.12"
+                "friendsofphp/php-cs-fixer": "^v2.15.8",
+                "phpunit/phpunit": "^9.3.11",
+                "vimeo/psalm": "^3.17.1"
             },
             "type": "library",
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/facade/ignition-contracts/issues",
-                "source": "https://github.com/facade/ignition-contracts/tree/1.0.1"
+                "source": "https://github.com/facade/ignition-contracts/tree/1.0.2"
             },
-            "time": "2020-07-14T10:10:28+00:00"
+            "time": "2020-10-16T08:27:54+00:00"
         },
         {
             "name": "fideloper/proxy",
         },
         {
             "name": "filp/whoops",
-            "version": "2.9.1",
+            "version": "2.12.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/filp/whoops.git",
-                "reference": "307fb34a5ab697461ec4c9db865b20ff2fd40771"
+                "reference": "c13c0be93cff50f88bbd70827d993026821914dd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/filp/whoops/zipball/307fb34a5ab697461ec4c9db865b20ff2fd40771",
-                "reference": "307fb34a5ab697461ec4c9db865b20ff2fd40771",
+                "url": "https://api.github.com/repos/filp/whoops/zipball/c13c0be93cff50f88bbd70827d993026821914dd",
+                "reference": "c13c0be93cff50f88bbd70827d993026821914dd",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/filp/whoops/issues",
-                "source": "https://github.com/filp/whoops/tree/2.9.1"
+                "source": "https://github.com/filp/whoops/tree/2.12.1"
             },
-            "time": "2020-11-01T12:00:00+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/denis-sokolov",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-04-25T12:00:00+00:00"
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "7.2.0",
+            "version": "7.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79"
+                "reference": "7008573787b430c1c1f650e3722d9bba59967628"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0aa74dfb41ae110835923ef10a9d803a22d50e79",
-                "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628",
+                "reference": "7008573787b430c1c1f650e3722d9bba59967628",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
                 "guzzlehttp/promises": "^1.4",
-                "guzzlehttp/psr7": "^1.7",
+                "guzzlehttp/psr7": "^1.7 || ^2.0",
                 "php": "^7.2.5 || ^8.0",
                 "psr/http-client": "^1.0"
             },
                 "psr/http-client-implementation": "1.0"
             },
             "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.4.1",
                 "ext-curl": "*",
                 "php-http/client-integration-tests": "^3.0",
                 "phpunit/phpunit": "^8.5.5 || ^9.3.5",
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "7.1-dev"
+                    "dev-master": "7.3-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/guzzle/guzzle/issues",
-                "source": "https://github.com/guzzle/guzzle/tree/7.2.0"
+                "source": "https://github.com/guzzle/guzzle/tree/7.3.0"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-10-10T11:47:56+00:00"
+            "time": "2021-03-23T11:33:13+00:00"
         },
         {
             "name": "guzzlehttp/promises",
-            "version": "1.4.0",
+            "version": "1.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/promises.git",
-                "reference": "60d379c243457e073cff02bc323a2a86cb355631"
+                "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631",
-                "reference": "60d379c243457e073cff02bc323a2a86cb355631",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
+                "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/guzzle/promises/issues",
-                "source": "https://github.com/guzzle/promises/tree/1.4.0"
+                "source": "https://github.com/guzzle/promises/tree/1.4.1"
             },
-            "time": "2020-09-30T07:37:28+00:00"
+            "time": "2021-03-07T09:25:29+00:00"
         },
         {
             "name": "guzzlehttp/psr7",
-            "version": "1.7.0",
+            "version": "1.8.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/psr7.git",
-                "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3"
+                "reference": "dc960a912984efb74d0a90222870c72c87f10c91"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3",
-                "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91",
+                "reference": "dc960a912984efb74d0a90222870c72c87f10c91",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/guzzle/psr7/issues",
-                "source": "https://github.com/guzzle/psr7/tree/1.7.0"
+                "source": "https://github.com/guzzle/psr7/tree/1.8.2"
             },
-            "time": "2020-09-30T07:37:11+00:00"
+            "time": "2021-04-26T09:17:50+00:00"
         },
         {
             "name": "intervention/image",
             },
             "time": "2019-11-02T09:15:47+00:00"
         },
-        {
-            "name": "jakub-onderka/php-console-color",
-            "version": "v0.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/JakubOnderka/PHP-Console-Color.git",
-                "reference": "d5deaecff52a0d61ccb613bb3804088da0307191"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191",
-                "reference": "d5deaecff52a0d61ccb613bb3804088da0307191",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.4.0"
-            },
-            "require-dev": {
-                "jakub-onderka/php-code-style": "1.0",
-                "jakub-onderka/php-parallel-lint": "1.0",
-                "jakub-onderka/php-var-dump-check": "0.*",
-                "phpunit/phpunit": "~4.3",
-                "squizlabs/php_codesniffer": "1.*"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "JakubOnderka\\PhpConsoleColor\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-2-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Jakub Onderka",
-                    "email": "[email protected]"
-                }
-            ],
-            "support": {
-                "issues": "https://github.com/JakubOnderka/PHP-Console-Color/issues",
-                "source": "https://github.com/JakubOnderka/PHP-Console-Color/tree/master"
-            },
-            "abandoned": "php-parallel-lint/php-console-color",
-            "time": "2018-09-29T17:23:10+00:00"
-        },
-        {
-            "name": "jakub-onderka/php-console-highlighter",
-            "version": "v0.4",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git",
-                "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547",
-                "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547",
-                "shasum": ""
-            },
-            "require": {
-                "ext-tokenizer": "*",
-                "jakub-onderka/php-console-color": "~0.2",
-                "php": ">=5.4.0"
-            },
-            "require-dev": {
-                "jakub-onderka/php-code-style": "~1.0",
-                "jakub-onderka/php-parallel-lint": "~1.0",
-                "jakub-onderka/php-var-dump-check": "~0.1",
-                "phpunit/phpunit": "~4.0",
-                "squizlabs/php_codesniffer": "~1.5"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "JakubOnderka\\PhpConsoleHighlighter\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Jakub Onderka",
-                    "email": "[email protected]",
-                    "homepage": "http://www.acci.cz/"
-                }
-            ],
-            "description": "Highlight PHP code in terminal",
-            "support": {
-                "issues": "https://github.com/JakubOnderka/PHP-Console-Highlighter/issues",
-                "source": "https://github.com/JakubOnderka/PHP-Console-Highlighter/tree/master"
-            },
-            "abandoned": "php-parallel-lint/php-console-highlighter",
-            "time": "2018-09-29T18:48:56+00:00"
-        },
         {
             "name": "knplabs/knp-snappy",
             "version": "v1.2.1",
         },
         {
             "name": "laravel/framework",
-            "version": "v6.20.12",
+            "version": "v6.20.26",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/framework.git",
-                "reference": "49f9db00a754517f07d365ee5b0806df82e37029"
+                "reference": "0117d797dc1ab64b1f88d4f6b966380ea7def091"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/framework/zipball/49f9db00a754517f07d365ee5b0806df82e37029",
-                "reference": "49f9db00a754517f07d365ee5b0806df82e37029",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/0117d797dc1ab64b1f88d4f6b966380ea7def091",
+                "reference": "0117d797dc1ab64b1f88d4f6b966380ea7def091",
                 "shasum": ""
             },
             "require": {
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2021-01-15T15:35:47+00:00"
+            "time": "2021-04-28T14:38:32+00:00"
         },
         {
             "name": "laravel/socialite",
-            "version": "v5.1.3",
+            "version": "v5.2.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/socialite.git",
-                "reference": "2e6beafe911a09f2300353c102d882e9d63f1f72"
+                "reference": "1960802068f81e44b2ae9793932181cf1cb91b5c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/socialite/zipball/2e6beafe911a09f2300353c102d882e9d63f1f72",
-                "reference": "2e6beafe911a09f2300353c102d882e9d63f1f72",
+                "url": "https://api.github.com/repos/laravel/socialite/zipball/1960802068f81e44b2ae9793932181cf1cb91b5c",
+                "reference": "1960802068f81e44b2ae9793932181cf1cb91b5c",
                 "shasum": ""
             },
             "require": {
                 "issues": "https://github.com/laravel/socialite/issues",
                 "source": "https://github.com/laravel/socialite"
             },
-            "time": "2021-01-05T17:02:09+00:00"
+            "time": "2021-04-06T14:38:16+00:00"
         },
         {
             "name": "league/commonmark",
-            "version": "1.5.7",
+            "version": "1.6.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/commonmark.git",
-                "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54"
+                "reference": "2651c497f005de305c7ba3f232cbd87b8c00ee8c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/11df9b36fd4f1d2b727a73bf14931d81373b9a54",
-                "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/2651c497f005de305c7ba3f232cbd87b8c00ee8c",
+                "reference": "2651c497f005de305c7ba3f232cbd87b8c00ee8c",
                 "shasum": ""
             },
             "require": {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-10-31T13:49:32+00:00"
+            "time": "2021-05-08T16:08:00+00:00"
         },
         {
             "name": "league/flysystem",
         },
         {
             "name": "league/mime-type-detection",
-            "version": "1.5.1",
+            "version": "1.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/mime-type-detection.git",
-                "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa"
+                "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/353f66d7555d8a90781f6f5e7091932f9a4250aa",
-                "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa",
+                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
+                "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.2 || ^8.0"
             },
             "require-dev": {
-                "phpstan/phpstan": "^0.12.36",
-                "phpunit/phpunit": "^8.5.8"
+                "friendsofphp/php-cs-fixer": "^2.18",
+                "phpstan/phpstan": "^0.12.68",
+                "phpunit/phpunit": "^8.5.8 || ^9.3"
             },
             "type": "library",
             "autoload": {
             "description": "Mime-type detection for Flysystem",
             "support": {
                 "issues": "https://github.com/thephpleague/mime-type-detection/issues",
-                "source": "https://github.com/thephpleague/mime-type-detection/tree/1.5.1"
+                "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-10-18T11:50:25+00:00"
+            "time": "2021-01-18T20:58:21+00:00"
         },
         {
             "name": "league/oauth1-client",
-            "version": "v1.8.2",
+            "version": "v1.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/thephpleague/oauth1-client.git",
-                "reference": "159c3d2bf27568f9af87d6c3f4bb616a251eb12b"
+                "reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/159c3d2bf27568f9af87d6c3f4bb616a251eb12b",
-                "reference": "159c3d2bf27568f9af87d6c3f4bb616a251eb12b",
+                "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/1e7e6be2dc543bf466236fb171e5b20e1b06aee6",
+                "reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
                 "ext-openssl": "*",
                 "guzzlehttp/guzzle": "^6.0|^7.0",
-                "php": ">=7.1"
+                "php": ">=7.1||>=8.0"
             },
             "require-dev": {
                 "ext-simplexml": "*",
-                "friendsofphp/php-cs-fixer": "^2.16.1",
-                "mockery/mockery": "^1.3",
+                "friendsofphp/php-cs-fixer": "^2.17",
+                "mockery/mockery": "^1.3.3",
                 "phpstan/phpstan": "^0.12.42",
-                "phpunit/phpunit": "^7.5"
+                "phpunit/phpunit": "^7.5||9.5"
             },
             "suggest": {
                 "ext-simplexml": "For decoding XML-based responses."
             ],
             "support": {
                 "issues": "https://github.com/thephpleague/oauth1-client/issues",
-                "source": "https://github.com/thephpleague/oauth1-client/tree/v1.8.2"
+                "source": "https://github.com/thephpleague/oauth1-client/tree/v1.9.0"
             },
-            "time": "2020-09-28T09:39:08+00:00"
+            "time": "2021-01-20T01:40:53+00:00"
         },
         {
             "name": "monolog/monolog",
         },
         {
             "name": "nesbot/carbon",
-            "version": "2.43.0",
+            "version": "2.47.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
-                "reference": "d32c57d8389113742f4a88725a170236470012e2"
+                "reference": "606262fd8888b75317ba9461825a24fc34001e1e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/d32c57d8389113742f4a88725a170236470012e2",
-                "reference": "d32c57d8389113742f4a88725a170236470012e2",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/606262fd8888b75317ba9461825a24fc34001e1e",
+                "reference": "606262fd8888b75317ba9461825a24fc34001e1e",
                 "shasum": ""
             },
             "require": {
                 "phpmd/phpmd": "^2.9",
                 "phpstan/extension-installer": "^1.0",
                 "phpstan/phpstan": "^0.12.54",
-                "phpunit/phpunit": "^7.5 || ^8.0",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.14",
                 "squizlabs/php_codesniffer": "^3.4"
             },
             "bin": [
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-17T20:55:32+00:00"
+            "time": "2021-04-13T21:54:02+00:00"
         },
         {
             "name": "nunomaduro/collision",
-            "version": "v3.1.0",
+            "version": "v3.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nunomaduro/collision.git",
-                "reference": "88b58b5bd9bdcc54756480fb3ce87234696544ee"
+                "reference": "f7c45764dfe4ba5f2618d265a6f1f9c72732e01d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/88b58b5bd9bdcc54756480fb3ce87234696544ee",
-                "reference": "88b58b5bd9bdcc54756480fb3ce87234696544ee",
+                "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f7c45764dfe4ba5f2618d265a6f1f9c72732e01d",
+                "reference": "f7c45764dfe4ba5f2618d265a6f1f9c72732e01d",
                 "shasum": ""
             },
             "require": {
                 "filp/whoops": "^2.1.4",
-                "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*",
-                "php": "^7.1 || ^8.0",
+                "php": "^7.2.5 || ^8.0",
+                "php-parallel-lint/php-console-highlighter": "0.5.*",
                 "symfony/console": "~2.8|~3.3|~4.0"
             },
             "require-dev": {
                     "type": "patreon"
                 }
             ],
-            "time": "2020-10-29T16:05:21+00:00"
+            "time": "2021-02-11T09:01:42+00:00"
         },
         {
             "name": "onelogin/php-saml",
-            "version": "3.5.1",
+            "version": "4.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/onelogin/php-saml.git",
-                "reference": "593aca859b67d607923fe50d8ad7315373f5b6dd"
+                "reference": "f30f5062f3653c4d2082892d207f4dc3e577d979"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/onelogin/php-saml/zipball/593aca859b67d607923fe50d8ad7315373f5b6dd",
-                "reference": "593aca859b67d607923fe50d8ad7315373f5b6dd",
+                "url": "https://api.github.com/repos/onelogin/php-saml/zipball/f30f5062f3653c4d2082892d207f4dc3e577d979",
+                "reference": "f30f5062f3653c4d2082892d207f4dc3e577d979",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.4",
+                "php": ">=7.3",
                 "robrichards/xmlseclibs": ">=3.1.1"
             },
             "require-dev": {
-                "pdepend/pdepend": "^2.5.0",
-                "php-coveralls/php-coveralls": "^1.0.2 || ^2.0",
-                "phploc/phploc": "^2.1 || ^3.0 || ^4.0",
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1",
-                "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0",
-                "squizlabs/php_codesniffer": "^3.1.1"
+                "pdepend/pdepend": "^2.8.0",
+                "php-coveralls/php-coveralls": "^2.0",
+                "phploc/phploc": "^4.0 || ^5.0 || ^6.0 || ^7.0",
+                "phpunit/phpunit": "^9.5",
+                "sebastian/phpcpd": "^4.0 || ^5.0 || ^6.0 ",
+                "squizlabs/php_codesniffer": "^3.5.8"
             },
             "suggest": {
                 "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs",
-                "ext-gettext": "Install gettext and php5-gettext libs to handle translations",
-                "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)"
+                "ext-dom": "Install xml lib",
+                "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)",
+                "ext-zlib": "Install zlib"
             },
             "type": "library",
             "autoload": {
                 "issues": "https://github.com/onelogin/php-saml/issues",
                 "source": "https://github.com/onelogin/php-saml/"
             },
-            "time": "2020-12-03T20:08:41+00:00"
+            "time": "2021-03-02T10:19:19+00:00"
         },
         {
             "name": "opis/closure",
-            "version": "3.6.1",
+            "version": "3.6.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/opis/closure.git",
-                "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5"
+                "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
-                "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
+                "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6",
+                "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/opis/closure/issues",
-                "source": "https://github.com/opis/closure/tree/3.6.1"
+                "source": "https://github.com/opis/closure/tree/3.6.2"
             },
-            "time": "2020-11-07T02:01:34+00:00"
+            "time": "2021-04-09T13:42:10+00:00"
         },
         {
             "name": "paragonie/random_compat",
             },
             "time": "2019-09-11T20:02:13+00:00"
         },
+        {
+            "name": "php-parallel-lint/php-console-color",
+            "version": "v0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-parallel-lint/PHP-Console-Color.git",
+                "reference": "b6af326b2088f1ad3b264696c9fd590ec395b49e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Color/zipball/b6af326b2088f1ad3b264696c9fd590ec395b49e",
+                "reference": "b6af326b2088f1ad3b264696c9fd590ec395b49e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "replace": {
+                "jakub-onderka/php-console-color": "*"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-code-style": "1.0",
+                "php-parallel-lint/php-parallel-lint": "1.0",
+                "php-parallel-lint/php-var-dump-check": "0.*",
+                "phpunit/phpunit": "~4.3",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "JakubOnderka\\PhpConsoleColor\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jakub Onderka",
+                    "email": "[email protected]"
+                }
+            ],
+            "support": {
+                "issues": "https://github.com/php-parallel-lint/PHP-Console-Color/issues",
+                "source": "https://github.com/php-parallel-lint/PHP-Console-Color/tree/master"
+            },
+            "time": "2020-05-14T05:47:14+00:00"
+        },
+        {
+            "name": "php-parallel-lint/php-console-highlighter",
+            "version": "v0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-parallel-lint/PHP-Console-Highlighter.git",
+                "reference": "21bf002f077b177f056d8cb455c5ed573adfdbb8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Console-Highlighter/zipball/21bf002f077b177f056d8cb455c5ed573adfdbb8",
+                "reference": "21bf002f077b177f056d8cb455c5ed573adfdbb8",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.4.0",
+                "php-parallel-lint/php-console-color": "~0.2"
+            },
+            "replace": {
+                "jakub-onderka/php-console-highlighter": "*"
+            },
+            "require-dev": {
+                "php-parallel-lint/php-code-style": "~1.0",
+                "php-parallel-lint/php-parallel-lint": "~1.0",
+                "php-parallel-lint/php-var-dump-check": "~0.1",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~1.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "JakubOnderka\\PhpConsoleHighlighter\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jakub Onderka",
+                    "email": "[email protected]",
+                    "homepage": "http://www.acci.cz/"
+                }
+            ],
+            "description": "Highlight PHP code in terminal",
+            "support": {
+                "issues": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/issues",
+                "source": "https://github.com/php-parallel-lint/PHP-Console-Highlighter/tree/master"
+            },
+            "time": "2020-05-13T07:37:49+00:00"
+        },
         {
             "name": "phpoption/phpoption",
             "version": "1.7.5",
         },
         {
             "name": "predis/predis",
-            "version": "v1.1.6",
+            "version": "v1.1.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/predis/predis.git",
-                "reference": "9930e933c67446962997b05201c69c2319bf26de"
+                "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/predis/predis/zipball/9930e933c67446962997b05201c69c2319bf26de",
-                "reference": "9930e933c67446962997b05201c69c2319bf26de",
+                "url": "https://api.github.com/repos/predis/predis/zipball/b240daa106d4e02f0c5b7079b41e31ddf66fddf8",
+                "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.9"
             },
             "require-dev": {
-                "cweagans/composer-patches": "^1.6",
                 "phpunit/phpunit": "~4.8"
             },
             "suggest": {
                 "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
             },
             "type": "library",
-            "extra": {
-                "composer-exit-on-patch-failure": true,
-                "patches": {
-                    "phpunit/phpunit-mock-objects": {
-                        "Fix PHP 7 and 8 compatibility": "./tests/phpunit_mock_objects.patch"
-                    },
-                    "phpunit/phpunit": {
-                        "Fix PHP 7 compatibility": "./tests/phpunit_php7.patch",
-                        "Fix PHP 8 compatibility": "./tests/phpunit_php8.patch"
-                    }
-                }
-            },
             "autoload": {
                 "psr-4": {
                     "Predis\\": "src/"
             ],
             "support": {
                 "issues": "https://github.com/predis/predis/issues",
-                "source": "https://github.com/predis/predis/tree/v1.1.6"
+                "source": "https://github.com/predis/predis/tree/v1.1.7"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-09-11T19:18:05+00:00"
+            "time": "2021-04-04T19:34:46+00:00"
         },
         {
             "name": "psr/container",
-            "version": "1.0.0",
+            "version": "1.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/container.git",
-                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+                "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
-                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf",
+                "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0"
+                "php": ">=7.2.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "1.0.x-dev"
-                }
-            },
             "autoload": {
                 "psr-4": {
                     "Psr\\Container\\": "src/"
             "authors": [
                 {
                     "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
+                    "homepage": "https://www.php-fig.org/"
                 }
             ],
             "description": "Common Container Interface (PHP FIG PSR-11)",
             ],
             "support": {
                 "issues": "https://github.com/php-fig/container/issues",
-                "source": "https://github.com/php-fig/container/tree/master"
+                "source": "https://github.com/php-fig/container/tree/1.1.1"
             },
-            "time": "2017-02-14T16:28:37+00:00"
+            "time": "2021-03-05T17:36:06+00:00"
         },
         {
             "name": "psr/http-client",
         },
         {
             "name": "psr/log",
-            "version": "1.1.3",
+            "version": "1.1.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+                "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
-                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
+                "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
                 "shasum": ""
             },
             "require": {
             "authors": [
                 {
                     "name": "PHP-FIG",
-                    "homepage": "http://www.php-fig.org/"
+                    "homepage": "https://www.php-fig.org/"
                 }
             ],
             "description": "Common interface for logging libraries",
                 "psr-3"
             ],
             "support": {
-                "source": "https://github.com/php-fig/log/tree/1.1.3"
+                "source": "https://github.com/php-fig/log/tree/1.1.4"
             },
-            "time": "2020-03-23T09:12:05+00:00"
+            "time": "2021-05-03T11:20:27+00:00"
         },
         {
             "name": "psr/simple-cache",
         },
         {
             "name": "socialiteproviders/okta",
-            "version": "4.1.0",
+            "version": "4.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/SocialiteProviders/Okta.git",
-                "reference": "60f88b8e8c88508889c61346af83290131b72fe7"
+                "reference": "e3ef9f23c7d2f86b3b16a174b82333cf4e2459e8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/60f88b8e8c88508889c61346af83290131b72fe7",
-                "reference": "60f88b8e8c88508889c61346af83290131b72fe7",
+                "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/e3ef9f23c7d2f86b3b16a174b82333cf4e2459e8",
+                "reference": "e3ef9f23c7d2f86b3b16a174b82333cf4e2459e8",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Okta OAuth2 Provider for Laravel Socialite",
             "support": {
-                "source": "https://github.com/SocialiteProviders/Okta/tree/4.1.0"
+                "source": "https://github.com/SocialiteProviders/Okta/tree/4.1.1"
             },
-            "time": "2020-12-01T23:10:59+00:00"
+            "time": "2021-01-12T23:51:01+00:00"
         },
         {
             "name": "socialiteproviders/slack",
-            "version": "4.1.0",
+            "version": "4.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/SocialiteProviders/Slack.git",
-                "reference": "8efb25c71d98bedf4010a829d1e41ff9fe449bcc"
+                "reference": "2b781c95daf06ec87a8f3deba2ab613d6bea5e8d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/SocialiteProviders/Slack/zipball/8efb25c71d98bedf4010a829d1e41ff9fe449bcc",
-                "reference": "8efb25c71d98bedf4010a829d1e41ff9fe449bcc",
+                "url": "https://api.github.com/repos/SocialiteProviders/Slack/zipball/2b781c95daf06ec87a8f3deba2ab613d6bea5e8d",
+                "reference": "2b781c95daf06ec87a8f3deba2ab613d6bea5e8d",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
-                "php": "^7.2|^8.0",
+                "php": "^7.2 || ^8.0",
                 "socialiteproviders/manager": "~4.0"
             },
             "type": "library",
             ],
             "description": "Slack OAuth2 Provider for Laravel Socialite",
             "support": {
-                "source": "https://github.com/SocialiteProviders/Slack/tree/4.1.0"
+                "source": "https://github.com/SocialiteProviders/Slack/tree/4.1.1"
             },
-            "time": "2020-11-26T17:57:15+00:00"
+            "time": "2021-03-26T04:10:10+00:00"
         },
         {
             "name": "socialiteproviders/twitch",
         },
         {
             "name": "ssddanbrown/htmldiff",
-            "version": "v1.0.0",
+            "version": "v1.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/ssddanbrown/HtmlDiff.git",
-                "reference": "d1978c7d1c685800997f982a0ae9cff1e45df70c"
+                "reference": "f60d5cc278b60305ab980a6665f46117c5b589c0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/ssddanbrown/HtmlDiff/zipball/d1978c7d1c685800997f982a0ae9cff1e45df70c",
-                "reference": "d1978c7d1c685800997f982a0ae9cff1e45df70c",
+                "url": "https://api.github.com/repos/ssddanbrown/HtmlDiff/zipball/f60d5cc278b60305ab980a6665f46117c5b589c0",
+                "reference": "f60d5cc278b60305ab980a6665f46117c5b589c0",
                 "shasum": ""
             },
             "require": {
             "homepage": "https://github.com/ssddanbrown/htmldiff",
             "support": {
                 "issues": "https://github.com/ssddanbrown/HtmlDiff/issues",
-                "source": "https://github.com/ssddanbrown/HtmlDiff/tree/v1.0.0"
+                "source": "https://github.com/ssddanbrown/HtmlDiff/tree/v1.0.1"
             },
-            "time": "2020-11-29T18:38:45+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/ssddanbrown",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-01-24T18:51:30+00:00"
         },
         {
             "name": "swiftmailer/swiftmailer",
-            "version": "v6.2.5",
+            "version": "v6.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/swiftmailer/swiftmailer.git",
-                "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7"
+                "reference": "15f7faf8508e04471f666633addacf54c0ab5933"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/698a6a9f54d7eb321274de3ad19863802c879fb7",
-                "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7",
+                "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/15f7faf8508e04471f666633addacf54c0ab5933",
+                "reference": "15f7faf8508e04471f666633addacf54c0ab5933",
                 "shasum": ""
             },
             "require": {
-                "egulias/email-validator": "^2.0",
+                "egulias/email-validator": "^2.0|^3.1",
                 "php": ">=7.0.0",
                 "symfony/polyfill-iconv": "^1.0",
                 "symfony/polyfill-intl-idn": "^1.10",
             ],
             "support": {
                 "issues": "https://github.com/swiftmailer/swiftmailer/issues",
-                "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.5"
+                "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.7"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-01-12T09:35:59+00:00"
+            "time": "2021-03-09T12:30:35+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "12e071278e396cc3e1c149857337e9e192deca0b"
+                "reference": "36bbd079b69b94bcc9c9c9e1e37ca3b1e7971625"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/12e071278e396cc3e1c149857337e9e192deca0b",
-                "reference": "12e071278e396cc3e1c149857337e9e192deca0b",
+                "url": "https://api.github.com/repos/symfony/console/zipball/36bbd079b69b94bcc9c9c9e1e37ca3b1e7971625",
+                "reference": "36bbd079b69b94bcc9c9c9e1e37ca3b1e7971625",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Console Component",
+            "description": "Eases the creation of beautiful and testable command line interfaces",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/console/tree/v4.4.18"
+                "source": "https://github.com/symfony/console/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-18T07:41:31+00:00"
+            "time": "2021-04-16T17:32:19+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "74bd82e75da256ad20851af6ded07823332216c7"
+                "reference": "01c77324d1d47efbfd7891f62a7c256c69330115"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/74bd82e75da256ad20851af6ded07823332216c7",
-                "reference": "74bd82e75da256ad20851af6ded07823332216c7",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/01c77324d1d47efbfd7891f62a7c256c69330115",
+                "reference": "01c77324d1d47efbfd7891f62a7c256c69330115",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony CssSelector Component",
+            "description": "Converts CSS selectors to XPath expressions",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/css-selector/tree/v4.4.18"
+                "source": "https://github.com/symfony/css-selector/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-08T16:59:59+00:00"
+            "time": "2021-04-07T15:47:03+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "5dfc7825f3bfe9bb74b23d8b8ce0e0894e32b544"
+                "reference": "45b2136377cca5f10af858968d6079a482bca473"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/5dfc7825f3bfe9bb74b23d8b8ce0e0894e32b544",
-                "reference": "5dfc7825f3bfe9bb74b23d8b8ce0e0894e32b544",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/45b2136377cca5f10af858968d6079a482bca473",
+                "reference": "45b2136377cca5f10af858968d6079a482bca473",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Debug Component",
+            "description": "Provides tools to ease debugging PHP code",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/debug/tree/v4.4.18"
+                "source": "https://github.com/symfony/debug/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-10T16:34:26+00:00"
+            "time": "2021-04-02T07:50:12+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
-            "version": "v2.2.0",
+            "version": "v2.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/deprecation-contracts.git",
-                "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665"
+                "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665",
-                "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
+                "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.2-dev"
+                    "dev-main": "2.4-dev"
                 },
                 "thanks": {
                     "name": "symfony/contracts",
             "description": "A generic function and convention to trigger deprecation notices",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/deprecation-contracts/tree/master"
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-09-07T11:33:47+00:00"
+            "time": "2021-03-23T23:28:01+00:00"
         },
         {
             "name": "symfony/error-handler",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/error-handler.git",
-                "reference": "ef2f7ddd3b9177bbf8ff2ecd8d0e970ed48da0c3"
+                "reference": "76603a8df8e001436df80758eb03a8baa5324175"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/error-handler/zipball/ef2f7ddd3b9177bbf8ff2ecd8d0e970ed48da0c3",
-                "reference": "ef2f7ddd3b9177bbf8ff2ecd8d0e970ed48da0c3",
+                "url": "https://api.github.com/repos/symfony/error-handler/zipball/76603a8df8e001436df80758eb03a8baa5324175",
+                "reference": "76603a8df8e001436df80758eb03a8baa5324175",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony ErrorHandler Component",
+            "description": "Provides tools to manage errors and ease debugging PHP code",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/error-handler/tree/v4.4.18"
+                "source": "https://github.com/symfony/error-handler/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-09T11:15:38+00:00"
+            "time": "2021-04-02T07:50:12+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v4.4.18",
+            "version": "v4.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "5d4c874b0eb1c32d40328a09dbc37307a5a910b0"
+                "reference": "c352647244bd376bf7d31efbd5401f13f50dad0c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5d4c874b0eb1c32d40328a09dbc37307a5a910b0",
-                "reference": "5d4c874b0eb1c32d40328a09dbc37307a5a910b0",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c352647244bd376bf7d31efbd5401f13f50dad0c",
+                "reference": "c352647244bd376bf7d31efbd5401f13f50dad0c",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony EventDispatcher Component",
+            "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.18"
+                "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.20"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-18T07:41:31+00:00"
+            "time": "2021-01-27T09:09:26+00:00"
         },
         {
             "name": "symfony/event-dispatcher-contracts",
         },
         {
             "name": "symfony/finder",
-            "version": "v4.4.18",
+            "version": "v4.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "ebd0965f2dc2d4e0f11487c16fbb041e50b5c09b"
+                "reference": "2543795ab1570df588b9bbd31e1a2bd7037b94f6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/ebd0965f2dc2d4e0f11487c16fbb041e50b5c09b",
-                "reference": "ebd0965f2dc2d4e0f11487c16fbb041e50b5c09b",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/2543795ab1570df588b9bbd31e1a2bd7037b94f6",
+                "reference": "2543795ab1570df588b9bbd31e1a2bd7037b94f6",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Finder Component",
+            "description": "Finds files and directories via an intuitive fluent interface",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/finder/tree/v4.4.18"
+                "source": "https://github.com/symfony/finder/tree/v4.4.20"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-08T16:59:59+00:00"
+            "time": "2021-02-12T10:48:09+00:00"
         },
         {
             "name": "symfony/http-client-contracts",
-            "version": "v2.3.1",
+            "version": "v2.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-client-contracts.git",
-                "reference": "41db680a15018f9c1d4b23516059633ce280ca33"
+                "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41db680a15018f9c1d4b23516059633ce280ca33",
-                "reference": "41db680a15018f9c1d4b23516059633ce280ca33",
+                "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
+                "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4",
                 "shasum": ""
             },
             "require": {
             },
             "type": "library",
             "extra": {
-                "branch-version": "2.3",
                 "branch-alias": {
-                    "dev-main": "2.3-dev"
+                    "dev-main": "2.4-dev"
                 },
                 "thanks": {
                     "name": "symfony/contracts",
                 "standards"
             ],
             "support": {
-                "source": "https://github.com/symfony/http-client-contracts/tree/v2.3.1"
+                "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-10-14T17:08:19+00:00"
+            "time": "2021-04-11T23:07:08+00:00"
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "5ebda66b51612516bf338d5f87da2f37ff74cf34"
+                "reference": "1a6f87ef99d05b1bf5c865b4ef7992263e1cb081"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5ebda66b51612516bf338d5f87da2f37ff74cf34",
-                "reference": "5ebda66b51612516bf338d5f87da2f37ff74cf34",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1a6f87ef99d05b1bf5c865b4ef7992263e1cb081",
+                "reference": "1a6f87ef99d05b1bf5c865b4ef7992263e1cb081",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony HttpFoundation Component",
+            "description": "Defines an object-oriented layer for the HTTP specification",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-foundation/tree/v4.4.18"
+                "source": "https://github.com/symfony/http-foundation/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-18T07:41:31+00:00"
+            "time": "2021-04-30T12:05:50+00:00"
         },
         {
             "name": "symfony/http-kernel",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-kernel.git",
-                "reference": "eaff9a43e74513508867ecfa66ef94fbb96ab128"
+                "reference": "cd2e325fc34a4a5bbec91eecf69dda8ee8c5ea4f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/eaff9a43e74513508867ecfa66ef94fbb96ab128",
-                "reference": "eaff9a43e74513508867ecfa66ef94fbb96ab128",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cd2e325fc34a4a5bbec91eecf69dda8ee8c5ea4f",
+                "reference": "cd2e325fc34a4a5bbec91eecf69dda8ee8c5ea4f",
                 "shasum": ""
             },
             "require": {
                 "symfony/console": ">=5",
                 "symfony/dependency-injection": "<4.3",
                 "symfony/translation": "<4.2",
-                "twig/twig": "<1.34|<2.4,>=2"
+                "twig/twig": "<1.43|<2.13,>=2"
             },
             "provide": {
                 "psr/log-implementation": "1.0"
             },
             "require-dev": {
-                "psr/cache": "~1.0",
+                "psr/cache": "^1.0|^2.0|^3.0",
                 "symfony/browser-kit": "^4.3|^5.0",
                 "symfony/config": "^3.4|^4.0|^5.0",
                 "symfony/console": "^3.4|^4.0",
                 "symfony/templating": "^3.4|^4.0|^5.0",
                 "symfony/translation": "^4.2|^5.0",
                 "symfony/translation-contracts": "^1.1|^2",
-                "twig/twig": "^1.34|^2.4|^3.0"
+                "twig/twig": "^1.43|^2.13|^3.0.4"
             },
             "suggest": {
                 "symfony/browser-kit": "",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony HttpKernel Component",
+            "description": "Provides a structured process for converting a Request into a Response",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-kernel/tree/v4.4.18"
+                "source": "https://github.com/symfony/http-kernel/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-18T13:32:33+00:00"
+            "time": "2021-05-01T14:38:48+00:00"
         },
         {
             "name": "symfony/mime",
-            "version": "v5.2.1",
+            "version": "v5.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/mime.git",
-                "reference": "de97005aef7426ba008c46ba840fc301df577ada"
+                "reference": "7af452bf51c46f18da00feb32e1ad36db9426515"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/mime/zipball/de97005aef7426ba008c46ba840fc301df577ada",
-                "reference": "de97005aef7426ba008c46ba840fc301df577ada",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/7af452bf51c46f18da00feb32e1ad36db9426515",
+                "reference": "7af452bf51c46f18da00feb32e1ad36db9426515",
                 "shasum": ""
             },
             "require": {
                 "symfony/polyfill-php80": "^1.15"
             },
             "conflict": {
+                "egulias/email-validator": "~3.0.0",
+                "phpdocumentor/reflection-docblock": "<3.2.2",
+                "phpdocumentor/type-resolver": "<1.4.0",
                 "symfony/mailer": "<4.4"
             },
             "require-dev": {
-                "egulias/email-validator": "^2.1.10",
+                "egulias/email-validator": "^2.1.10|^3.1",
                 "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
                 "symfony/dependency-injection": "^4.4|^5.0",
                 "symfony/property-access": "^4.4|^5.1",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "A library to manipulate MIME messages",
+            "description": "Allows manipulating MIME messages",
             "homepage": "https://symfony.com",
             "keywords": [
                 "mime",
                 "mime-type"
             ],
             "support": {
-                "source": "https://github.com/symfony/mime/tree/v5.2.1"
+                "source": "https://github.com/symfony/mime/tree/v5.2.7"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-09T18:54:12+00:00"
+            "time": "2021-04-29T20:47:09+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
                 "portable"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/polyfill-iconv",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-iconv.git",
-                "reference": "b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6"
+                "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6",
-                "reference": "b34bfb8c4c22650ac080d2662ae3502e5f2f4ae6",
+                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/06fb361659649bcfd6a208a0f1fcaf4e827ad342",
+                "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342",
                 "shasum": ""
             },
             "require": {
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.1"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-01-07T16:49:33+00:00"
+            "time": "2021-01-22T09:19:47+00:00"
         },
         {
             "name": "symfony/polyfill-intl-idn",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-idn.git",
-                "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44"
+                "reference": "2d63434d922daf7da8dd863e7907e67ee3031483"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44",
-                "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/2d63434d922daf7da8dd863e7907e67ee3031483",
+                "reference": "2d63434d922daf7da8dd863e7907e67ee3031483",
                 "shasum": ""
             },
             "require": {
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.1"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-01-07T16:49:33+00:00"
+            "time": "2021-01-22T09:19:47+00:00"
         },
         {
             "name": "symfony/polyfill-intl-normalizer",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
-                "reference": "6e971c891537eb617a00bb07a43d182a6915faba"
+                "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba",
-                "reference": "6e971c891537eb617a00bb07a43d182a6915faba",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248",
+                "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248",
                 "shasum": ""
             },
             "require": {
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-01-07T17:09:11+00:00"
+            "time": "2021-01-22T09:19:47+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13"
+                "reference": "5232de97ee3b75b0360528dae24e73db49566ab1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13",
-                "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1",
+                "reference": "5232de97ee3b75b0360528dae24e73db49566ab1",
                 "shasum": ""
             },
             "require": {
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-01-07T16:49:33+00:00"
+            "time": "2021-01-22T09:19:47+00:00"
         },
         {
             "name": "symfony/polyfill-php72",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php72.git",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/polyfill-php73",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php73.git",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/polyfill-php80",
-            "version": "v1.22.0",
+            "version": "v1.22.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php80.git",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.0"
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/process",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "075316ff72233ce3d04a9743414292e834f2cb4a"
+                "reference": "f5481b22729d465acb1cea3455fc04ce84b0148b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/075316ff72233ce3d04a9743414292e834f2cb4a",
-                "reference": "075316ff72233ce3d04a9743414292e834f2cb4a",
+                "url": "https://api.github.com/repos/symfony/process/zipball/f5481b22729d465acb1cea3455fc04ce84b0148b",
+                "reference": "f5481b22729d465acb1cea3455fc04ce84b0148b",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Process Component",
+            "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v4.4.18"
+                "source": "https://github.com/symfony/process/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-08T16:59:59+00:00"
+            "time": "2021-04-07T16:22:29+00:00"
         },
         {
             "name": "symfony/routing",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/routing.git",
-                "reference": "80b042c20b035818daec844723e23b9825134ba0"
+                "reference": "049e7c5c41f98511959668791b4adc0898a821b3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/routing/zipball/80b042c20b035818daec844723e23b9825134ba0",
-                "reference": "80b042c20b035818daec844723e23b9825134ba0",
+                "url": "https://api.github.com/repos/symfony/routing/zipball/049e7c5c41f98511959668791b4adc0898a821b3",
+                "reference": "049e7c5c41f98511959668791b4adc0898a821b3",
                 "shasum": ""
             },
             "require": {
                 "symfony/yaml": "<3.4"
             },
             "require-dev": {
-                "doctrine/annotations": "~1.2",
+                "doctrine/annotations": "^1.10.4",
                 "psr/log": "~1.0",
                 "symfony/config": "^4.2|^5.0",
                 "symfony/dependency-injection": "^3.4|^4.0|^5.0",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Routing Component",
+            "description": "Maps an HTTP request to a set of configuration variables",
             "homepage": "https://symfony.com",
             "keywords": [
                 "router",
                 "url"
             ],
             "support": {
-                "source": "https://github.com/symfony/routing/tree/v4.4.18"
+                "source": "https://github.com/symfony/routing/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-08T16:59:59+00:00"
+            "time": "2021-04-11T12:59:39+00:00"
         },
         {
             "name": "symfony/service-contracts",
-            "version": "v2.2.0",
+            "version": "v2.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/service-contracts.git",
-                "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
+                "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
-                "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
+                "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
                 "shasum": ""
             },
             "require": {
                 "php": ">=7.2.5",
-                "psr/container": "^1.0"
+                "psr/container": "^1.1"
             },
             "suggest": {
                 "symfony/service-implementation": ""
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.2-dev"
+                    "dev-main": "2.4-dev"
                 },
                 "thanks": {
                     "name": "symfony/contracts",
                 "standards"
             ],
             "support": {
-                "source": "https://github.com/symfony/service-contracts/tree/master"
+                "source": "https://github.com/symfony/service-contracts/tree/v2.4.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-09-07T11:33:47+00:00"
+            "time": "2021-04-01T10:43:52+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v4.4.18",
+            "version": "v4.4.21",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "c1001b7d75b3136648f94b245588209d881c6939"
+                "reference": "eb8f5428cc3b40d6dffe303b195b084f1c5fbd14"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/c1001b7d75b3136648f94b245588209d881c6939",
-                "reference": "c1001b7d75b3136648f94b245588209d881c6939",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/eb8f5428cc3b40d6dffe303b195b084f1c5fbd14",
+                "reference": "eb8f5428cc3b40d6dffe303b195b084f1c5fbd14",
                 "shasum": ""
             },
             "require": {
                 "symfony/yaml": "<3.4"
             },
             "provide": {
-                "symfony/translation-implementation": "1.0"
+                "symfony/translation-implementation": "1.0|2.0"
             },
             "require-dev": {
                 "psr/log": "~1.0",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Translation Component",
+            "description": "Provides tools to internationalize your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/translation/tree/v4.4.18"
+                "source": "https://github.com/symfony/translation/tree/v4.4.21"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-08T16:59:59+00:00"
+            "time": "2021-03-23T16:25:01+00:00"
         },
         {
             "name": "symfony/translation-contracts",
-            "version": "v2.3.0",
+            "version": "v2.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation-contracts.git",
-                "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105"
+                "reference": "95c812666f3e91db75385749fe219c5e494c7f95"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105",
-                "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95",
+                "reference": "95c812666f3e91db75385749fe219c5e494c7f95",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.3-dev"
+                    "dev-main": "2.4-dev"
                 },
                 "thanks": {
                     "name": "symfony/contracts",
                 "standards"
             ],
             "support": {
-                "source": "https://github.com/symfony/translation-contracts/tree/v2.3.0"
+                "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-09-28T13:05:58+00:00"
+            "time": "2021-03-23T23:28:01+00:00"
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v4.4.18",
+            "version": "v4.4.22",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "4f31364bbc8177f2a6dbc125ac3851634ebe2a03"
+                "reference": "c194bcedde6295f3ec3e9eba1f5d484ea97c41a7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4f31364bbc8177f2a6dbc125ac3851634ebe2a03",
-                "reference": "4f31364bbc8177f2a6dbc125ac3851634ebe2a03",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c194bcedde6295f3ec3e9eba1f5d484ea97c41a7",
+                "reference": "c194bcedde6295f3ec3e9eba1f5d484ea97c41a7",
                 "shasum": ""
             },
             "require": {
                 "ext-iconv": "*",
                 "symfony/console": "^3.4|^4.0|^5.0",
                 "symfony/process": "^4.4|^5.0",
-                "twig/twig": "^1.34|^2.4|^3.0"
+                "twig/twig": "^1.43|^2.13|^3.0.4"
             },
             "suggest": {
                 "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony mechanism for exploring and dumping PHP variables",
+            "description": "Provides mechanisms for walking through any arbitrary PHP variable",
             "homepage": "https://symfony.com",
             "keywords": [
                 "debug",
                 "dump"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v4.4.18"
+                "source": "https://github.com/symfony/var-dumper/tree/v4.4.22"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-08T16:59:59+00:00"
+            "time": "2021-04-19T13:36:17+00:00"
         },
         {
             "name": "tijsverkoyen/css-to-inline-styles",
         },
         {
             "name": "vlucas/phpdotenv",
-            "version": "v3.6.7",
+            "version": "v3.6.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/vlucas/phpdotenv.git",
-                "reference": "2065beda6cbe75e2603686907b2e45f6f3a5ad82"
+                "reference": "5e679f7616db829358341e2d5cccbd18773bdab8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2065beda6cbe75e2603686907b2e45f6f3a5ad82",
-                "reference": "2065beda6cbe75e2603686907b2e45f6f3a5ad82",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/5e679f7616db829358341e2d5cccbd18773bdab8",
+                "reference": "5e679f7616db829358341e2d5cccbd18773bdab8",
                 "shasum": ""
             },
             "require": {
             "require-dev": {
                 "ext-filter": "*",
                 "ext-pcre": "*",
-                "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0"
+                "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20"
             },
             "suggest": {
                 "ext-filter": "Required to use the boolean validator.",
             ],
             "support": {
                 "issues": "https://github.com/vlucas/phpdotenv/issues",
-                "source": "https://github.com/vlucas/phpdotenv/tree/v3.6.7"
+                "source": "https://github.com/vlucas/phpdotenv/tree/v3.6.8"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-07-14T19:04:52+00:00"
+            "time": "2021-01-20T14:39:46+00:00"
         }
     ],
     "packages-dev": [
         {
             "name": "barryvdh/laravel-debugbar",
-            "version": "v3.5.2",
+            "version": "v3.5.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/barryvdh/laravel-debugbar.git",
-                "reference": "cae0a8d1cb89b0f0522f65e60465e16d738e069b"
+                "reference": "6420113d90bb746423fa70b9940e9e7c26ebc121"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/cae0a8d1cb89b0f0522f65e60465e16d738e069b",
-                "reference": "cae0a8d1cb89b0f0522f65e60465e16d738e069b",
+                "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/6420113d90bb746423fa70b9940e9e7c26ebc121",
+                "reference": "6420113d90bb746423fa70b9940e9e7c26ebc121",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/barryvdh/laravel-debugbar/issues",
-                "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.5.2"
+                "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.5.5"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2021-01-06T14:21:44+00:00"
+            "time": "2021-04-07T11:19:20+00:00"
         },
         {
             "name": "barryvdh/laravel-ide-helper",
         },
         {
             "name": "composer/composer",
-            "version": "2.0.8",
+            "version": "2.0.13",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/composer.git",
-                "reference": "62139b2806178adb979d76bd3437534a1a9fd490"
+                "reference": "986e8b86b7b570632ad0a905c3726c33dd4c0efb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/composer/zipball/62139b2806178adb979d76bd3437534a1a9fd490",
-                "reference": "62139b2806178adb979d76bd3437534a1a9fd490",
+                "url": "https://api.github.com/repos/composer/composer/zipball/986e8b86b7b570632ad0a905c3726c33dd4c0efb",
+                "reference": "986e8b86b7b570632ad0a905c3726c33dd4c0efb",
                 "shasum": ""
             },
             "require": {
                 "composer/ca-bundle": "^1.0",
+                "composer/metadata-minifier": "^1.0",
                 "composer/semver": "^3.0",
                 "composer/spdx-licenses": "^1.2",
                 "composer/xdebug-handler": "^1.1",
             "support": {
                 "irc": "irc://irc.freenode.org/composer",
                 "issues": "https://github.com/composer/composer/issues",
-                "source": "https://github.com/composer/composer/tree/2.0.8"
+                "source": "https://github.com/composer/composer/tree/2.0.13"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-03T16:20:39+00:00"
+            "time": "2021-04-27T11:11:08+00:00"
         },
         {
-            "name": "composer/semver",
-            "version": "3.2.4",
+            "name": "composer/metadata-minifier",
+            "version": "1.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/composer/semver.git",
-                "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464"
+                "url": "https://github.com/composer/metadata-minifier.git",
+                "reference": "c549d23829536f0d0e984aaabbf02af91f443207"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464",
-                "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464",
+                "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207",
+                "reference": "c549d23829536f0d0e984aaabbf02af91f443207",
                 "shasum": ""
             },
             "require": {
                 "php": "^5.3.2 || ^7.0 || ^8.0"
             },
             "require-dev": {
-                "phpstan/phpstan": "^0.12.54",
+                "composer/composer": "^2",
+                "phpstan/phpstan": "^0.12.55",
                 "symfony/phpunit-bridge": "^4.2 || ^5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "3.x-dev"
+                    "dev-main": "1.x-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Composer\\Semver\\": "src"
+                    "Composer\\MetadataMinifier\\": "src"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
             ],
             "authors": [
                 {
-                    "name": "Nils Adermann",
-                    "email": "[email protected]",
-                    "homepage": "http://www.naderman.de"
-                },
+                    "name": "Jordi Boggiano",
+                    "email": "[email protected]",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "Small utility library that handles metadata minification and expansion.",
+            "keywords": [
+                "composer",
+                "compression"
+            ],
+            "support": {
+                "issues": "https://github.com/composer/metadata-minifier/issues",
+                "source": "https://github.com/composer/metadata-minifier/tree/1.0.0"
+            },
+            "funding": [
+                {
+                    "url": "https://packagist.com",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/composer",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-04-07T13:37:33+00:00"
+        },
+        {
+            "name": "composer/semver",
+            "version": "3.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/composer/semver.git",
+                "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/composer/semver/zipball/a02fdf930a3c1c3ed3a49b5f63859c0c20e10464",
+                "reference": "a02fdf930a3c1c3ed3a49b5f63859c0c20e10464",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.2 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "phpstan/phpstan": "^0.12.54",
+                "symfony/phpunit-bridge": "^4.2 || ^5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Composer\\Semver\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nils Adermann",
+                    "email": "[email protected]",
+                    "homepage": "http://www.naderman.de"
+                },
                 {
                     "name": "Jordi Boggiano",
                     "email": "[email protected]",
         },
         {
             "name": "composer/xdebug-handler",
-            "version": "1.4.5",
+            "version": "1.4.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "f28d44c286812c714741478d968104c5e604a1d4"
+                "reference": "f27e06cd9675801df441b3656569b328e04aa37c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f28d44c286812c714741478d968104c5e604a1d4",
-                "reference": "f28d44c286812c714741478d968104c5e604a1d4",
+                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/f27e06cd9675801df441b3656569b328e04aa37c",
+                "reference": "f27e06cd9675801df441b3656569b328e04aa37c",
                 "shasum": ""
             },
             "require": {
                 "psr/log": "^1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8"
+                "phpstan/phpstan": "^0.12.55",
+                "symfony/phpunit-bridge": "^4.2 || ^5"
             },
             "type": "library",
             "autoload": {
             "support": {
                 "irc": "irc://irc.freenode.org/composer",
                 "issues": "https://github.com/composer/xdebug-handler/issues",
-                "source": "https://github.com/composer/xdebug-handler/tree/1.4.5"
+                "source": "https://github.com/composer/xdebug-handler/tree/1.4.6"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-11-13T08:04:11+00:00"
+            "time": "2021-03-25T17:01:18+00:00"
         },
         {
             "name": "doctrine/instantiator",
         },
         {
             "name": "fakerphp/faker",
-            "version": "v1.13.0",
+            "version": "v1.14.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FakerPHP/Faker.git",
-                "reference": "ab3f5364d01f2c2c16113442fb987d26e4004913"
+                "reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ab3f5364d01f2c2c16113442fb987d26e4004913",
-                "reference": "ab3f5364d01f2c2c16113442fb987d26e4004913",
+                "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1",
+                "reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.1 || ^8.0"
+                "php": "^7.1 || ^8.0",
+                "psr/container": "^1.0",
+                "symfony/deprecation-contracts": "^2.2"
             },
             "conflict": {
                 "fzaninotto/faker": "*"
             "require-dev": {
                 "bamarni/composer-bin-plugin": "^1.4.1",
                 "ext-intl": "*",
-                "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.4.2"
+                "symfony/phpunit-bridge": "^4.4 || ^5.2"
+            },
+            "suggest": {
+                "ext-curl": "Required by Faker\\Provider\\Image to download images.",
+                "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
+                "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
+                "ext-mbstring": "Required for multibyte Unicode string functionality."
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "v1.15-dev"
+                }
+            },
             "autoload": {
                 "psr-4": {
                     "Faker\\": "src/Faker/"
             ],
             "support": {
                 "issues": "https://github.com/FakerPHP/Faker/issues",
-                "source": "https://github.com/FakerPHP/Faker/tree/v1.13.0"
+                "source": "https://github.com/FakerPHP/Faker/tree/v.1.14.1"
             },
-            "time": "2020-12-18T16:50:48+00:00"
+            "time": "2021-03-30T06:27:33+00:00"
         },
         {
             "name": "hamcrest/hamcrest-php",
         },
         {
             "name": "maximebf/debugbar",
-            "version": "v1.16.4",
+            "version": "v1.16.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/maximebf/php-debugbar.git",
-                "reference": "c86c717e4bf3c6d98422da5c38bfa7b0f494b04c"
+                "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/c86c717e4bf3c6d98422da5c38bfa7b0f494b04c",
-                "reference": "c86c717e4bf3c6d98422da5c38bfa7b0f494b04c",
+                "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/6d51ee9e94cff14412783785e79a4e7ef97b9d62",
+                "reference": "6d51ee9e94cff14412783785e79a4e7ef97b9d62",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/maximebf/php-debugbar/issues",
-                "source": "https://github.com/maximebf/php-debugbar/tree/v1.16.4"
+                "source": "https://github.com/maximebf/php-debugbar/tree/v1.16.5"
             },
-            "time": "2020-12-07T10:48:48+00:00"
+            "time": "2020-12-07T11:07:24+00:00"
         },
         {
             "name": "mockery/mockery",
-            "version": "1.3.3",
+            "version": "1.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/mockery/mockery.git",
-                "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d"
+                "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/mockery/mockery/zipball/60fa2f67f6e4d3634bb4a45ff3171fa52215800d",
-                "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d",
+                "url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea",
+                "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea",
                 "shasum": ""
             },
             "require": {
                 "hamcrest/hamcrest-php": "^2.0.1",
                 "lib-pcre": ">=7.0",
-                "php": ">=5.6.0"
+                "php": "^7.3 || ^8.0"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<8.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^5.7.10|^6.5|^7.5|^8.5|^9.3"
+                "phpunit/phpunit": "^8.5 || ^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.3.x-dev"
+                    "dev-master": "1.4.x-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/mockery/mockery/issues",
-                "source": "https://github.com/mockery/mockery/tree/1.3.3"
+                "source": "https://github.com/mockery/mockery/tree/1.4.3"
             },
-            "time": "2020-08-11T18:10:21+00:00"
+            "time": "2021-02-24T09:51:49+00:00"
         },
         {
             "name": "myclabs/deep-copy",
             ],
             "time": "2020-11-13T09:40:50+00:00"
         },
+        {
+            "name": "nikic/php-parser",
+            "version": "v4.10.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f",
+                "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "ircmaxell/php-yacc": "^0.0.7",
+                "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+            },
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/nikic/PHP-Parser/issues",
+                "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.5"
+            },
+            "time": "2021-05-03T19:11:20+00:00"
+        },
         {
             "name": "phar-io/manifest",
             "version": "2.0.1",
         },
         {
             "name": "phar-io/version",
-            "version": "3.0.4",
+            "version": "3.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phar-io/version.git",
-                "reference": "e4782611070e50613683d2b9a57730e9a3ba5451"
+                "reference": "bae7c545bef187884426f042434e561ab1ddb182"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phar-io/version/zipball/e4782611070e50613683d2b9a57730e9a3ba5451",
-                "reference": "e4782611070e50613683d2b9a57730e9a3ba5451",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182",
+                "reference": "bae7c545bef187884426f042434e561ab1ddb182",
                 "shasum": ""
             },
             "require": {
             "description": "Library for handling version information and constraints",
             "support": {
                 "issues": "https://github.com/phar-io/version/issues",
-                "source": "https://github.com/phar-io/version/tree/3.0.4"
+                "source": "https://github.com/phar-io/version/tree/3.1.0"
             },
-            "time": "2020-12-13T23:18:30+00:00"
+            "time": "2021-02-23T14:00:09+00:00"
         },
         {
             "name": "phpdocumentor/reflection-common",
         },
         {
             "name": "phpspec/prophecy",
-            "version": "1.12.2",
+            "version": "1.13.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "245710e971a030f42e08f4912863805570f23d39"
+                "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39",
-                "reference": "245710e971a030f42e08f4912863805570f23d39",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
+                "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/phpspec/prophecy/issues",
-                "source": "https://github.com/phpspec/prophecy/tree/1.12.2"
+                "source": "https://github.com/phpspec/prophecy/tree/1.13.0"
             },
-            "time": "2020-12-19T10:15:11+00:00"
+            "time": "2021-03-17T13:42:18+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "7.0.14",
+            "version": "9.2.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c"
+                "reference": "f6293e1b30a2354e8428e004689671b83871edde"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bb7c9a210c72e4709cdde67f8b7362f672f2225c",
-                "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde",
+                "reference": "f6293e1b30a2354e8428e004689671b83871edde",
                 "shasum": ""
             },
             "require": {
                 "ext-dom": "*",
+                "ext-libxml": "*",
                 "ext-xmlwriter": "*",
-                "php": ">=7.2",
-                "phpunit/php-file-iterator": "^2.0.2",
-                "phpunit/php-text-template": "^1.2.1",
-                "phpunit/php-token-stream": "^3.1.1 || ^4.0",
-                "sebastian/code-unit-reverse-lookup": "^1.0.1",
-                "sebastian/environment": "^4.2.2",
-                "sebastian/version": "^2.0.1",
-                "theseer/tokenizer": "^1.1.3"
+                "nikic/php-parser": "^4.10.2",
+                "php": ">=7.3",
+                "phpunit/php-file-iterator": "^3.0.3",
+                "phpunit/php-text-template": "^2.0.2",
+                "sebastian/code-unit-reverse-lookup": "^2.0.2",
+                "sebastian/complexity": "^2.0",
+                "sebastian/environment": "^5.1.2",
+                "sebastian/lines-of-code": "^1.0.3",
+                "sebastian/version": "^3.0.1",
+                "theseer/tokenizer": "^1.2.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.2.2"
+                "phpunit/phpunit": "^9.3"
             },
             "suggest": {
-                "ext-xdebug": "^2.7.2"
+                "ext-pcov": "*",
+                "ext-xdebug": "*"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "7.0-dev"
+                    "dev-master": "9.2-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.14"
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-12-02T13:39:03+00:00"
+            "time": "2021-03-28T07:26:59+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
-            "version": "2.0.3",
+            "version": "3.0.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357"
+                "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357",
-                "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8",
+                "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.5"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0.x-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
-                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.3"
+                "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T08:25:21+00:00"
+            "time": "2020-09-28T05:57:25+00:00"
         },
         {
-            "name": "phpunit/php-text-template",
-            "version": "1.2.1",
+            "name": "phpunit/php-invoker",
+            "version": "3.1.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-text-template.git",
-                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+                "url": "https://github.com/sebastianbergmann/php-invoker.git",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
-                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+                "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3"
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "ext-pcntl": "*",
+                "phpunit/phpunit": "^9.3"
+            },
+            "suggest": {
+                "ext-pcntl": "*"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
             "autoload": {
                 "classmap": [
                     "src/"
                     "role": "lead"
                 }
             ],
-            "description": "Simple template engine.",
-            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "description": "Invoke callables with a timeout",
+            "homepage": "https://github.com/sebastianbergmann/php-invoker/",
             "keywords": [
-                "template"
+                "process"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
-                "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
+                "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+                "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
             },
-            "time": "2015-06-21T13:50:34+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T05:58:55+00:00"
         },
         {
-            "name": "phpunit/php-timer",
-            "version": "2.1.3",
+            "name": "phpunit/php-text-template",
+            "version": "2.0.4",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
-                "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+                "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.5"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.1-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
                     "role": "lead"
                 }
             ],
-            "description": "Utility class for timing",
-            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
             "keywords": [
-                "timer"
+                "template"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
-                "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3"
+                "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+                "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T08:20:02+00:00"
+            "time": "2020-10-26T05:33:50+00:00"
         },
         {
-            "name": "phpunit/php-token-stream",
-            "version": "3.1.2",
+            "name": "phpunit/php-timer",
+            "version": "5.0.3",
             "source": {
                 "type": "git",
-                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "472b687829041c24b25f475e14c2f38a09edf1c2"
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/472b687829041c24b25f475e14c2f38a09edf1c2",
-                "reference": "472b687829041c24b25f475e14c2f38a09edf1c2",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+                "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
                 "shasum": ""
             },
             "require": {
-                "ext-tokenizer": "*",
-                "php": ">=7.1"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^7.0"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "[email protected]"
+                    "email": "[email protected]",
+                    "role": "lead"
                 }
             ],
-            "description": "Wrapper around PHP's tokenizer extension.",
-            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
             "keywords": [
-                "tokenizer"
+                "timer"
             ],
             "support": {
-                "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
-                "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.2"
+                "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+                "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "abandoned": true,
-            "time": "2020-11-30T08:38:46+00:00"
+            "time": "2020-10-26T13:16:10+00:00"
         },
         {
             "name": "phpunit/phpunit",
-            "version": "8.5.13",
+            "version": "9.5.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "8e86be391a58104ef86037ba8a846524528d784e"
+                "reference": "c73c6737305e779771147af66c96ca6a7ed8a741"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e86be391a58104ef86037ba8a846524528d784e",
-                "reference": "8e86be391a58104ef86037ba8a846524528d784e",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741",
+                "reference": "c73c6737305e779771147af66c96ca6a7ed8a741",
                 "shasum": ""
             },
             "require": {
                 "ext-mbstring": "*",
                 "ext-xml": "*",
                 "ext-xmlwriter": "*",
-                "myclabs/deep-copy": "^1.10.0",
+                "myclabs/deep-copy": "^1.10.1",
                 "phar-io/manifest": "^2.0.1",
                 "phar-io/version": "^3.0.2",
-                "php": ">=7.2",
-                "phpspec/prophecy": "^1.10.3",
-                "phpunit/php-code-coverage": "^7.0.12",
-                "phpunit/php-file-iterator": "^2.0.2",
-                "phpunit/php-text-template": "^1.2.1",
-                "phpunit/php-timer": "^2.1.2",
-                "sebastian/comparator": "^3.0.2",
-                "sebastian/diff": "^3.0.2",
-                "sebastian/environment": "^4.2.3",
-                "sebastian/exporter": "^3.1.2",
-                "sebastian/global-state": "^3.0.0",
-                "sebastian/object-enumerator": "^3.0.3",
-                "sebastian/resource-operations": "^2.0.1",
-                "sebastian/type": "^1.1.3",
-                "sebastian/version": "^2.0.1"
+                "php": ">=7.3",
+                "phpspec/prophecy": "^1.12.1",
+                "phpunit/php-code-coverage": "^9.2.3",
+                "phpunit/php-file-iterator": "^3.0.5",
+                "phpunit/php-invoker": "^3.1.1",
+                "phpunit/php-text-template": "^2.0.3",
+                "phpunit/php-timer": "^5.0.2",
+                "sebastian/cli-parser": "^1.0.1",
+                "sebastian/code-unit": "^1.0.6",
+                "sebastian/comparator": "^4.0.5",
+                "sebastian/diff": "^4.0.3",
+                "sebastian/environment": "^5.1.3",
+                "sebastian/exporter": "^4.0.3",
+                "sebastian/global-state": "^5.0.1",
+                "sebastian/object-enumerator": "^4.0.3",
+                "sebastian/resource-operations": "^3.0.3",
+                "sebastian/type": "^2.3",
+                "sebastian/version": "^3.0.2"
             },
             "require-dev": {
-                "ext-pdo": "*"
+                "ext-pdo": "*",
+                "phpspec/prophecy-phpunit": "^2.0.1"
             },
             "suggest": {
                 "ext-soap": "*",
-                "ext-xdebug": "*",
-                "phpunit/php-invoker": "^2.0.0"
+                "ext-xdebug": "*"
             },
             "bin": [
                 "phpunit"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "8.5-dev"
+                    "dev-master": "9.5-dev"
                 }
             },
             "autoload": {
                 "classmap": [
                     "src/"
+                ],
+                "files": [
+                    "src/Framework/Assert/Functions.php"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.13"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.4"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-12-01T04:53:52+00:00"
+            "time": "2021-03-23T07:16:29+00:00"
         },
         {
             "name": "react/promise",
             },
             "time": "2020-05-12T15:16:56+00:00"
         },
+        {
+            "name": "sebastian/cli-parser",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/cli-parser.git",
+                "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+                "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for parsing CLI options",
+            "homepage": "https://github.com/sebastianbergmann/cli-parser",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+                "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T06:08:49+00:00"
+        },
+        {
+            "name": "sebastian/code-unit",
+            "version": "1.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit.git",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/code-unit",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+                "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T13:08:54+00:00"
+        },
         {
             "name": "sebastian/code-unit-reverse-lookup",
-            "version": "1.0.2",
+            "version": "2.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
-                "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
-                "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+                "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.6"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.5"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
             "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
-                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
+                "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T08:15:22+00:00"
+            "time": "2020-09-28T05:30:19+00:00"
         },
         {
             "name": "sebastian/comparator",
-            "version": "3.0.3",
+            "version": "4.0.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
+                "reference": "55f4261989e546dc112258c7a75935a81a7ce382"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
-                "reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
+                "reference": "55f4261989e546dc112258c7a75935a81a7ce382",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1",
-                "sebastian/diff": "^3.0",
-                "sebastian/exporter": "^3.1"
+                "php": ">=7.3",
+                "sebastian/diff": "^4.0",
+                "sebastian/exporter": "^4.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.5"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "4.0-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/comparator/issues",
-                "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3"
+                "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T08:04:30+00:00"
+            "time": "2020-10-26T15:49:45+00:00"
+        },
+        {
+            "name": "sebastian/complexity",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/complexity.git",
+                "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
+                "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^4.7",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for calculating the complexity of PHP code units",
+            "homepage": "https://github.com/sebastianbergmann/complexity",
+            "support": {
+                "issues": "https://github.com/sebastianbergmann/complexity/issues",
+                "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-26T15:52:27+00:00"
         },
         {
             "name": "sebastian/diff",
-            "version": "3.0.3",
+            "version": "4.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/diff.git",
-                "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
+                "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
-                "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+                "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^7.5 || ^8.0",
-                "symfony/process": "^2 || ^3.3 || ^4"
+                "phpunit/phpunit": "^9.3",
+                "symfony/process": "^4.2 || ^5"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "4.0-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/diff/issues",
-                "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3"
+                "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:59:04+00:00"
+            "time": "2020-10-26T13:10:38+00:00"
         },
         {
             "name": "sebastian/environment",
-            "version": "4.2.4",
+            "version": "5.1.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
+                "reference": "388b6ced16caa751030f6a69e588299fa09200ac"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
-                "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
+                "reference": "388b6ced16caa751030f6a69e588299fa09200ac",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^7.5"
+                "phpunit/phpunit": "^9.3"
             },
             "suggest": {
                 "ext-posix": "*"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "4.2-dev"
+                    "dev-master": "5.1-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/environment/issues",
-                "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4"
+                "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:53:42+00:00"
+            "time": "2020-09-28T05:52:38+00:00"
         },
         {
             "name": "sebastian/exporter",
-            "version": "3.1.3",
+            "version": "4.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
+                "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
-                "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65",
+                "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0",
-                "sebastian/recursion-context": "^3.0"
+                "php": ">=7.3",
+                "sebastian/recursion-context": "^4.0"
             },
             "require-dev": {
                 "ext-mbstring": "*",
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.1.x-dev"
+                    "dev-master": "4.0-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/exporter/issues",
-                "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
+                "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:47:53+00:00"
+            "time": "2020-09-28T05:24:23+00:00"
         },
         {
             "name": "sebastian/global-state",
-            "version": "3.0.1",
+            "version": "5.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b"
+                "reference": "a90ccbddffa067b51f574dea6eb25d5680839455"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b",
-                "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455",
+                "reference": "a90ccbddffa067b51f574dea6eb25d5680839455",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2",
-                "sebastian/object-reflector": "^1.1.1",
-                "sebastian/recursion-context": "^3.0"
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
             },
             "require-dev": {
                 "ext-dom": "*",
-                "phpunit/phpunit": "^8.0"
+                "phpunit/phpunit": "^9.3"
             },
             "suggest": {
                 "ext-uopz": "*"
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "5.0-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1"
+                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:43:24+00:00"
+            "time": "2020-10-26T15:55:19+00:00"
+        },
+        {
+            "name": "sebastian/lines-of-code",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+                "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+                "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^4.6",
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "[email protected]",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library for counting the lines of code in PHP source code",
+            "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"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-11-28T06:42:11+00:00"
         },
         {
             "name": "sebastian/object-enumerator",
-            "version": "3.0.4",
+            "version": "4.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/object-enumerator.git",
-                "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
-                "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+                "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0",
-                "sebastian/object-reflector": "^1.1.1",
-                "sebastian/recursion-context": "^3.0"
+                "php": ">=7.3",
+                "sebastian/object-reflector": "^2.0",
+                "sebastian/recursion-context": "^4.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0.x-dev"
+                    "dev-master": "4.0-dev"
                 }
             },
             "autoload": {
             "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
-                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
+                "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:40:27+00:00"
+            "time": "2020-10-26T13:12:34+00:00"
         },
         {
             "name": "sebastian/object-reflector",
-            "version": "1.1.2",
+            "version": "2.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/object-reflector.git",
-                "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
-                "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+                "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1-dev"
+                    "dev-master": "2.0-dev"
                 }
             },
             "autoload": {
             "homepage": "https://github.com/sebastianbergmann/object-reflector/",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
-                "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
+                "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:37:18+00:00"
+            "time": "2020-10-26T13:14:26+00:00"
         },
         {
             "name": "sebastian/recursion-context",
-            "version": "3.0.1",
+            "version": "4.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/recursion-context.git",
-                "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
+                "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
-                "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
+                "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.0"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^6.0"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0.x-dev"
+                    "dev-master": "4.0-dev"
                 }
             },
             "autoload": {
             "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
-                "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
+                "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:34:24+00:00"
+            "time": "2020-10-26T13:17:30+00:00"
         },
         {
             "name": "sebastian/resource-operations",
-            "version": "2.0.2",
+            "version": "3.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/resource-operations.git",
-                "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
+                "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
-                "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+                "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
             "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
-                "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2"
+                "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:30:19+00:00"
+            "time": "2020-09-28T06:45:17+00:00"
         },
         {
             "name": "sebastian/type",
-            "version": "1.1.4",
+            "version": "2.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/type.git",
-                "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4"
+                "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4",
-                "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2",
+                "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2"
+                "php": ">=7.3"
             },
             "require-dev": {
-                "phpunit/phpunit": "^8.2"
+                "phpunit/phpunit": "^9.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.1-dev"
+                    "dev-master": "2.3-dev"
                 }
             },
             "autoload": {
             "homepage": "https://github.com/sebastianbergmann/type",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/type/issues",
-                "source": "https://github.com/sebastianbergmann/type/tree/1.1.4"
+                "source": "https://github.com/sebastianbergmann/type/tree/2.3.1"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2020-11-30T07:25:11+00:00"
+            "time": "2020-10-26T13:18:59+00:00"
         },
         {
             "name": "sebastian/version",
-            "version": "2.0.1",
+            "version": "3.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/version.git",
-                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+                "reference": "c6c1022351a901512170118436c764e473f6de8c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
-                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+                "reference": "c6c1022351a901512170118436c764e473f6de8c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.6"
+                "php": ">=7.3"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.0.x-dev"
+                    "dev-master": "3.0-dev"
                 }
             },
             "autoload": {
             "homepage": "https://github.com/sebastianbergmann/version",
             "support": {
                 "issues": "https://github.com/sebastianbergmann/version/issues",
-                "source": "https://github.com/sebastianbergmann/version/tree/master"
+                "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
             },
-            "time": "2016-10-03T07:35:21+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/sebastianbergmann",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-28T06:39:44+00:00"
         },
         {
             "name": "seld/jsonlint",
         },
         {
             "name": "squizlabs/php_codesniffer",
-            "version": "3.5.8",
+            "version": "3.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
-                "reference": "9d583721a7157ee997f235f327de038e7ea6dac4"
+                "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4",
-                "reference": "9d583721a7157ee997f235f327de038e7ea6dac4",
+                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625",
+                "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625",
                 "shasum": ""
             },
             "require": {
                 "source": "https://github.com/squizlabs/PHP_CodeSniffer",
                 "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
             },
-            "time": "2020-10-23T02:01:07+00:00"
+            "time": "2021-04-09T00:54:41+00:00"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v4.4.18",
+            "version": "v4.4.20",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "d44fbb02b458fe18d00fea18f24c97cefb87577e"
+                "reference": "be133557f1b0e6672367325b508e65da5513a311"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d44fbb02b458fe18d00fea18f24c97cefb87577e",
-                "reference": "d44fbb02b458fe18d00fea18f24c97cefb87577e",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/be133557f1b0e6672367325b508e65da5513a311",
+                "reference": "be133557f1b0e6672367325b508e65da5513a311",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony DomCrawler Component",
+            "description": "Eases DOM navigation for HTML and XML documents",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/dom-crawler/tree/v4.4.18"
+                "source": "https://github.com/symfony/dom-crawler/tree/v4.4.20"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-12-18T07:41:31+00:00"
+            "time": "2021-02-14T12:29:41+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v5.2.1",
+            "version": "v5.2.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d"
+                "reference": "056e92acc21d977c37e6ea8e97374b2a6c8551b0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/fa8f8cab6b65e2d99a118e082935344c5ba8c60d",
-                "reference": "fa8f8cab6b65e2d99a118e082935344c5ba8c60d",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/056e92acc21d977c37e6ea8e97374b2a6c8551b0",
+                "reference": "056e92acc21d977c37e6ea8e97374b2a6c8551b0",
                 "shasum": ""
             },
             "require": {
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony Filesystem Component",
+            "description": "Provides basic utilities for the filesystem",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/filesystem/tree/v5.2.1"
+                "source": "https://github.com/symfony/filesystem/tree/v5.2.7"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-11-30T17:05:38+00:00"
+            "time": "2021-04-01T10:42:13+00:00"
         },
         {
             "name": "theseer/tokenizer",
         },
         {
             "name": "webmozart/assert",
-            "version": "1.9.1",
+            "version": "1.10.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/webmozart/assert.git",
-                "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
+                "url": "https://github.com/webmozarts/assert.git",
+                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
-                "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
+                "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
+                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.3.3 || ^7.0 || ^8.0",
+                "php": "^7.2 || ^8.0",
                 "symfony/polyfill-ctype": "^1.8"
             },
             "conflict": {
                 "phpstan/phpstan": "<0.12.20",
-                "vimeo/psalm": "<3.9.1"
+                "vimeo/psalm": "<4.6.1 || 4.6.2"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.36 || ^7.5.13"
+                "phpunit/phpunit": "^8.5.13"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.10-dev"
+                }
+            },
             "autoload": {
                 "psr-4": {
                     "Webmozart\\Assert\\": "src/"
                 "validate"
             ],
             "support": {
-                "issues": "https://github.com/webmozart/assert/issues",
-                "source": "https://github.com/webmozart/assert/tree/master"
+                "issues": "https://github.com/webmozarts/assert/issues",
+                "source": "https://github.com/webmozarts/assert/tree/1.10.0"
             },
-            "time": "2020-07-08T17:02:28+00:00"
+            "time": "2021-03-09T10:59:23+00:00"
         }
     ],
     "aliases": [],
     "prefer-stable": true,
     "prefer-lowest": false,
     "platform": {
-        "php": "^7.2.5",
+        "php": "^7.3|^8.0",
         "ext-curl": "*",
         "ext-dom": "*",
         "ext-gd": "*",
     },
     "platform-dev": [],
     "platform-overrides": {
-        "php": "7.2.5"
+        "php": "7.3.0"
     },
     "plugin-api-version": "2.0.0"
 }
index 405e5fcf4490a062408dd79569fd41099847ead3..722f68a7c9fdbe758aaf0ebb8101ce4431f16922 100644 (file)
 */
 
 $factory->define(\BookStack\Auth\User::class, function ($faker) {
+    $name = $faker->name;
     return [
-        'name' => $faker->name,
+        'name' => $name,
         'email' => $faker->email,
+        'slug' => \Illuminate\Support\Str::slug($name . '-' . \Illuminate\Support\Str::random(5)),
         'password' => Str::random(10),
         'remember_token' => Str::random(10),
         'email_confirmed' => 1
index 7398ed39885cedde9655f9fb06783016e6c6b75e..a066fb1309445e378f7880457bfd57fceac28498 100644 (file)
@@ -15,7 +15,7 @@ class CreateSearchIndexTable extends Migration
     {
         Schema::create('search_terms', function (Blueprint $table) {
             $table->increments('id');
-            $table->string('term', 200);
+            $table->string('term', 180);
             $table->string('entity_type', 100);
             $table->integer('entity_id');
             $table->integer('score');
index 706a883a387d996055eebc7742ebb3e56f003fe3..0778e8762dd245e1855c675bf48e4de74bad1621 100644 (file)
@@ -14,7 +14,7 @@ class AddRoleExternalAuthId extends Migration
     public function up()
     {
         Schema::table('roles', function (Blueprint $table) {
-            $table->string('external_auth_id', 200)->default('');
+            $table->string('external_auth_id', 180)->default('');
             $table->index('external_auth_id');
         });
     }
index 9efba0071c3689c6fe5ce13cdc8faa59ae0f7142..488c6196830c4549b43b8a3d9fb4517eef7704fc 100644 (file)
@@ -37,8 +37,8 @@ class CreateBookshelvesTable extends Migration
 
         Schema::create('bookshelves', function (Blueprint $table) {
             $table->increments('id');
-            $table->string('name', 200);
-            $table->string('slug', 200);
+            $table->string('name', 180);
+            $table->string('slug', 180);
             $table->text('description');
             $table->integer('created_by')->nullable()->default(null);
             $table->integer('updated_by')->nullable()->default(null);
diff --git a/database/migrations/2021_01_30_225441_add_settings_type_column.php b/database/migrations/2021_01_30_225441_add_settings_type_column.php
new file mode 100644 (file)
index 0000000..61d9bda
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class AddSettingsTypeColumn extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('settings', function (Blueprint $table) {
+            $table->string('type', 50)->default('string');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('settings', function (Blueprint $table) {
+            $table->dropColumn('type');
+        });
+    }
+}
diff --git a/database/migrations/2021_03_08_215138_add_user_slug.php b/database/migrations/2021_03_08_215138_add_user_slug.php
new file mode 100644 (file)
index 0000000..dad1e42
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Str;
+
+class AddUserSlug extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->string('slug', 180);
+        });
+
+        $slugMap = [];
+        DB::table('users')->cursor()->each(function ($user) use (&$slugMap) {
+            $userSlug = Str::slug($user->name);
+            while (isset($slugMap[$userSlug])) {
+                $userSlug = Str::slug($user->name . Str::random(4));
+            }
+            $slugMap[$userSlug] = true;
+
+            DB::table('users')
+                ->where('id', $user->id)
+                ->update(['slug' => $userSlug]);
+        });
+
+        Schema::table('users', function (Blueprint $table) {
+            $table->unique('slug');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('slug');
+        });
+    }
+}
diff --git a/database/migrations/2021_05_15_173110_create_favourites_table.php b/database/migrations/2021_05_15_173110_create_favourites_table.php
new file mode 100644 (file)
index 0000000..783bf58
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateFavouritesTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('favourites', function (Blueprint $table) {
+            $table->increments('id');
+            $table->integer('user_id')->index();
+            $table->integer('favouritable_id');
+            $table->string('favouritable_type', 100);
+            $table->timestamps();
+
+            $table->index(['favouritable_id', 'favouritable_type'], 'favouritable_index');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('favourites');
+    }
+}
index 8816615cf69986b75a3713d02e80b5a283221d13..895ad595ae7b73cf4077a76bf717092c5146a2ff 100644 (file)
@@ -1,16 +1,23 @@
-FROM php:7.3-apache
+FROM php:7.4-apache
 
 ENV APACHE_DOCUMENT_ROOT /app/public
 WORKDIR /app
 
+# Install additional dependacnies and configure apache
 RUN apt-get update -y \
-    && apt-get install -y git zip unzip libtidy-dev libpng-dev libldap2-dev libxml++2.6-dev wait-for-it \
+    && apt-get install -y git zip unzip libpng-dev libldap2-dev wait-for-it \
     && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu \
-    && docker-php-ext-install pdo pdo_mysql tidy dom xml mbstring gd ldap \
+    && docker-php-ext-install pdo_mysql gd ldap \
     && a2enmod rewrite \
     && sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
-    && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
-    && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
+    && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
+
+# Install composer
+RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
     && php composer-setup.php \
     && mv composer.phar /usr/bin/composer \
     && php -r "unlink('composer-setup.php');"
+
+# Use the default production configuration and update it as required
+RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
+    && sed -i 's/memory_limit = 128M/memory_limit = 512M/g' "$PHP_INI_DIR/php.ini"
\ No newline at end of file
diff --git a/dev/docker/init.db/01.sql b/dev/docker/init.db/01.sql
new file mode 100644 (file)
index 0000000..2536c3f
--- /dev/null
@@ -0,0 +1,5 @@
+# create test database
+CREATE DATABASE IF NOT EXISTS `bookstack-test`;
+
+# grant rights
+GRANT ALL PRIVILEGES ON `bookstack-test`.* TO 'bookstack-test'@'%';
diff --git a/dev/docs/logical-theme-system.md b/dev/docs/logical-theme-system.md
new file mode 100644 (file)
index 0000000..b950d7d
--- /dev/null
@@ -0,0 +1,112 @@
+# Logical Theme System
+
+BookStack allows logical customization via the theme system which enables you to add, or extend, functionality within the PHP side of the system without needing to alter the core application files.
+
+WARNING: This system is currently in alpha so may incur changes. Once we've gathered some feedback on usage we'll look to removing this warning. This system will be considered semi-stable in the future. The `Theme::` system will be kept maintained but specific customizations or deeper app/framework usage using this system will not be supported nor considered in any way stable. Customizations using this system should be checked after updates.
+
+## Getting Started
+
+This makes use of the theme system. Create a folder for your theme within your BookStack `themes` directory. As an example we'll use `my_theme`, so we'd create a `themes/my_theme` folder.
+You'll need to tell BookStack to use your theme via the `APP_THEME` option in your `.env` file. For example: `APP_THEME=my_theme`.
+
+Within your theme folder create a `functions.php` file. BookStack will look for this and run it during app boot-up. Within this file you can use the `Theme` facade API, described below, to hook into certain app events.
+
+## `Theme` Facade API
+
+Below details the public methods of the `Theme` facade that are considered stable:
+
+### `Theme::listen`
+
+This method listens to a system event and runs the given action when that event occurs. The arguments passed to the action depend on the event. Event names are exposed as static properties on the `\BookStack\Theming\ThemeEvents` class. 
+
+It is possible to listen to a single event using multiple actions. When dispatched, BookStack will loop over and run each action for that event.
+If an action returns a non-null value then BookStack will stop cycling through actions at that point and make use of the non-null return value if possible (Depending on the event).
+
+**Arguments**
+- string $event
+- callable $action
+
+**Example**
+
+```php
+Theme::listen(
+    \BookStack\Theming\ThemeEvents::AUTH_LOGIN,
+    function($service, $user) {
+        \Log::info("Login by {$user->name} via {$service}");
+    }
+);
+```
+
+### `Theme::addSocialDriver`
+
+This method allows you to register a custom social authentication driver within the system. This is primarily intended to use with [Socialite Providers](https://socialiteproviders.com/).
+
+**Arguments**
+- string $driverName
+- array $config
+- string $socialiteHandler
+
+**Example**
+
+*See "Custom Socialite Service Example" below.*
+
+## Available Events
+
+All available events dispatched by BookStack are exposed as static properties on the `\BookStack\Theming\ThemeEvents` class, which can be found within the file `app/Theming/ThemeEvents.php` relative to your root BookStack folder. Alternatively, the events for the latest release can be [seen on GitHub here](https://github.com/BookStackApp/BookStack/blob/release/app/Theming/ThemeEvents.php).
+
+The comments above each constant with the `ThemeEvents.php` file describe the dispatch conditions of the event, in addition to the arguments the action will receive. The comments may also describe any ways the return value of the action may be used. 
+
+## Example `functions.php` file
+
+```php
+<?php
+
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
+
+// Logs custom message on user login
+Theme::listen(ThemeEvents::AUTH_LOGIN, function($method, $user) {
+    Log::info("Login via {$method} for {$user->name}");
+});
+
+// Adds a `/info` public URL endpoint that emits php debug details
+Theme::listen(ThemeEvents::APP_BOOT, function($app) {
+    \Route::get('info', function() {
+        phpinfo(); // Don't do this on a production instance!
+    });
+});
+```
+
+## Custom Socialite Service Example
+
+The below shows an example of adding a custom reddit socialite service to BookStack. 
+BookStack exposes a helper function for this via `Theme::addSocialDriver` which sets the required config and event listeners in the platform.
+
+The require statements reference composer installed dependencies within the theme folder. They are required manually since they are not auto-loaded like other app files due to being outside the main BookStack dependency list. 
+
+```php
+require "vendor/socialiteproviders/reddit/Provider.php";
+require "vendor/socialiteproviders/reddit/RedditExtendSocialite.php";
+
+Theme::listen(ThemeEvents::APP_BOOT, function($app) {
+    Theme::addSocialDriver('reddit', [
+        'client_id' => 'abc123',
+        'client_secret' => 'def456789',
+        'name' => 'Reddit',
+    ], '\SocialiteProviders\Reddit\RedditExtendSocialite@handle');
+});
+```
+
+In some cases you may need to customize the driver before it performs a redirect. 
+This can be done by providing a callback as a fourth parameter like so:
+
+```php
+Theme::addSocialDriver('reddit', [
+    'client_id' => 'abc123',
+    'client_secret' => 'def456789',
+    'name' => 'Reddit',
+], '\SocialiteProviders\Reddit\RedditExtendSocialite@handle', function($driver) {
+    $driver->with(['prompt' => 'select_account']);
+    $driver->scopes(['open_id']);
+});
+```
\ No newline at end of file
diff --git a/dev/docs/visual-theme-system.md b/dev/docs/visual-theme-system.md
new file mode 100644 (file)
index 0000000..058bd28
--- /dev/null
@@ -0,0 +1,31 @@
+# Visual Theme System
+
+BookStack allows visual customization via the theme system which enables you to extensively customize views, translation text & icons.
+
+This theme system itself is maintained and supported but usages of this system, including the files you are able to override, are not considered stable and may change upon any update. You should test any customizations made after updates.
+
+## Getting Started
+
+This makes use of the theme system. Create a folder for your theme within your BookStack `themes` directory. As an example we'll use `my_theme`, so we'd create a `themes/my_theme` folder.
+You'll need to tell BookStack to use your theme via the `APP_THEME` option in your `.env` file. For example: `APP_THEME=my_theme`.
+
+## Customizing View Files
+
+Content placed in your `themes/<theme_name>/` folder will override the original view files found in the `resources/views` folder. These files are typically [Laravel Blade](https://laravel.com/docs/6.x/blade) files.
+
+## Customizing Icons
+
+SVG files placed in a `themes/<theme_name>/icons` folder will override any icons of the same name within `resources/icons`. You'd typically want to follow the format convention of the existing icons, where no XML deceleration is included and no width & height attributes are set, to ensure optimal compatibility. 
+
+## Customizing Text Content
+
+Folders with PHP translation files placed in a `themes/<theme_name>/lang` folder will override translations defined within the `resources/lang` folder. Custom translations are merged with the original files so you only need to override the select translations you want to override, you don't need to copy the whole original file. Note that you'll need to include the language folder.
+
+As an example, Say I wanted to change 'Search' to 'Find'; Within a `themes/<theme_name>/lang/en/common.php` file I'd set the following:
+
+```php
+<?php
+return [
+    'search' => 'find',
+];
+```
\ No newline at end of file
index ea7a61ab554aea7540a28655d0e9c61822550f8a..7920258944694e6b0d1af757ba9fd3bef5b45e57 100644 (file)
@@ -10,24 +10,27 @@ services:
   db:
     image: mysql:8
     environment:
-      MYSQL_DATABASE: bookstack-test
+      MYSQL_DATABASE: bookstack-dev
       MYSQL_USER: bookstack-test
       MYSQL_PASSWORD: bookstack-test
       MYSQL_RANDOM_ROOT_PASSWORD: 'true'
     command: --default-authentication-plugin=mysql_native_password
     volumes:
+      - ./dev/docker/init.db:/docker-entrypoint-initdb.d
       - db:/var/lib/mysql
   app:
     build:
       context: .
       dockerfile: ./dev/docker/Dockerfile
     environment:
+      APP_URL: http://localhost:${DEV_PORT:-8080}
       DB_CONNECTION: mysql
       DB_HOST: db
       DB_PORT: 3306
-      DB_DATABASE: bookstack-test
+      DB_DATABASE: bookstack-dev
       DB_USERNAME: bookstack-test
       DB_PASSWORD: bookstack-test
+      TEST_DATABASE_URL: mysql://bookstack-test:bookstack-test@db/bookstack-test
       MAIL_DRIVER: smtp
       MAIL_HOST: mailhog
       MAIL_PORT: 1025
@@ -39,6 +42,7 @@ services:
   node:
     image: node:alpine
     working_dir: /app
+    user: node
     volumes:
       - ./:/app
     entrypoint: /app/dev/docker/entrypoint.node.sh
index 06f13e8d296d5b89c7d5c5e0daf688f561ae8359..d25aae4daf10d6b6207cfb7aef596696806eab70 100644 (file)
 {
+  "name": "bookstack",
+  "lockfileVersion": 2,
   "requires": true,
-  "lockfileVersion": 1,
+  "packages": {
+    "": {
+      "dependencies": {
+        "clipboard": "^2.0.8",
+        "codemirror": "^5.60.0",
+        "dropzone": "^5.9.2",
+        "markdown-it": "^11.0.1",
+        "markdown-it-task-lists": "^2.1.1",
+        "sortablejs": "^1.13.0"
+      },
+      "devDependencies": {
+        "chokidar-cli": "^2.1.0",
+        "esbuild": "0.7.8",
+        "livereload": "^0.9.3",
+        "npm-run-all": "^4.1.5",
+        "punycode": "^2.1.1",
+        "sass": "^1.32.8"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+      "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dependencies": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
+      "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
+      "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
+      "dev": true,
+      "dependencies": {
+        "anymatch": "~3.1.1",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.0",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.4.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.1.2"
+      }
+    },
+    "node_modules/chokidar-cli": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-2.1.0.tgz",
+      "integrity": "sha512-6n21AVpW6ywuEPoxJcLXMA2p4T+SLjWsXKny/9yTWFz0kKxESI3eUylpeV97LylING/27T/RVTY0f2/0QaWq9Q==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^3.2.3",
+        "lodash.debounce": "^4.0.8",
+        "lodash.throttle": "^4.1.1",
+        "yargs": "^13.3.0"
+      },
+      "bin": {
+        "chokidar": "index.js"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      }
+    },
+    "node_modules/clipboard": {
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
+      "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
+      "dependencies": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
+    "node_modules/cliui": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+      "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^3.1.0",
+        "strip-ansi": "^5.2.0",
+        "wrap-ansi": "^5.1.0"
+      }
+    },
+    "node_modules/codemirror": {
+      "version": "5.60.0",
+      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.60.0.tgz",
+      "integrity": "sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA=="
+    },
+    "node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "node_modules/cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "dependencies": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      },
+      "engines": {
+        "node": ">=4.8"
+      }
+    },
+    "node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "dev": true,
+      "dependencies": {
+        "object-keys": "^1.0.12"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+    },
+    "node_modules/dropzone": {
+      "version": "5.9.2",
+      "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.2.tgz",
+      "integrity": "sha512-5t2z51DzIsWDbTpwcJIvUlwxBbvcwdCApz0yb9ecKJwG155Xm92KMEZmHW1B0MzoXOKvFwdd0nPu5cpeVcvPHQ=="
+    },
+    "node_modules/emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "node_modules/entities": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
+      "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ=="
+    },
+    "node_modules/error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "dependencies": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "node_modules/es-abstract": {
+      "version": "1.17.6",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+      "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+      "dev": true,
+      "dependencies": {
+        "es-to-primitive": "^1.2.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1",
+        "is-callable": "^1.2.0",
+        "is-regex": "^1.1.0",
+        "object-inspect": "^1.7.0",
+        "object-keys": "^1.1.1",
+        "object.assign": "^4.1.0",
+        "string.prototype.trimend": "^1.0.1",
+        "string.prototype.trimstart": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-to-primitive": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+      "dev": true,
+      "dependencies": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.7.8",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.7.8.tgz",
+      "integrity": "sha512-6UT1nZB+8ja5avctUC6d3kGOUAhy6/ZYHljL4nk3++1ipadghBhUCAcwsTHsmUvdu04CcGKzo13mE+ZQ2O3zrA==",
+      "dev": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/find-up": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+      "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "dev": true,
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+      "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+      "dependencies": {
+        "delegate": "^3.1.2"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+      "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+      "dev": true
+    },
+    "node_modules/has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "dependencies": {
+        "function-bind": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/hosted-git-info": {
+      "version": "2.8.9",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+      "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+      "dev": true
+    },
+    "node_modules/is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-callable": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz",
+      "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/is-date-object": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
+      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-regex": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+      "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+      "dev": true,
+      "dependencies": {
+        "has-symbols": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/is-symbol": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
+      "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+      "dev": true,
+      "dependencies": {
+        "has-symbols": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "node_modules/json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "node_modules/linkify-it": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz",
+      "integrity": "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==",
+      "dependencies": {
+        "uc.micro": "^1.0.1"
+      }
+    },
+    "node_modules/livereload": {
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
+      "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^3.5.0",
+        "livereload-js": "^3.3.1",
+        "opts": ">= 1.2.0",
+        "ws": "^7.4.3"
+      },
+      "bin": {
+        "livereload": "bin/livereload.js"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
+    "node_modules/livereload-js": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.3.2.tgz",
+      "integrity": "sha512-w677WnINxFkuixAoUEXOStewzLYGI76XVag+0JWMMEyjJQKs0ibWZMxkTlB96Lm3EjZ7IeOxVziBEbtxVQqQZA==",
+      "dev": true
+    },
+    "node_modules/livereload/node_modules/chokidar": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
+      "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
+      "dev": true,
+      "dependencies": {
+        "anymatch": "~3.1.1",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.0",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.5.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.1"
+      }
+    },
+    "node_modules/livereload/node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/livereload/node_modules/readdirp": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+      "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/load-json-file": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+      "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+      "dev": true,
+      "dependencies": {
+        "graceful-fs": "^4.1.2",
+        "parse-json": "^4.0.0",
+        "pify": "^3.0.0",
+        "strip-bom": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+      "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^3.0.0",
+        "path-exists": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/lodash.debounce": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+      "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+      "dev": true
+    },
+    "node_modules/lodash.throttle": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+      "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=",
+      "dev": true
+    },
+    "node_modules/markdown-it": {
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.1.tgz",
+      "integrity": "sha512-aU1TzmBKcWNNYvH9pjq6u92BML+Hz3h5S/QpfTFwiQF852pLT+9qHsrhM9JYipkOXZxGn+sGH8oyJE9FD9WezQ==",
+      "dependencies": {
+        "argparse": "^1.0.7",
+        "entities": "~2.0.0",
+        "linkify-it": "^3.0.1",
+        "mdurl": "^1.0.1",
+        "uc.micro": "^1.0.5"
+      },
+      "bin": {
+        "markdown-it": "bin/markdown-it.js"
+      }
+    },
+    "node_modules/markdown-it-task-lists": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz",
+      "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA=="
+    },
+    "node_modules/mdurl": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
+      "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
+    },
+    "node_modules/memorystream": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+      "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "node_modules/normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "dependencies": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      }
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/npm-run-all": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
+      "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "chalk": "^2.4.1",
+        "cross-spawn": "^6.0.5",
+        "memorystream": "^0.3.1",
+        "minimatch": "^3.0.4",
+        "pidtree": "^0.3.0",
+        "read-pkg": "^3.0.0",
+        "shell-quote": "^1.6.1",
+        "string.prototype.padend": "^3.0.0"
+      },
+      "bin": {
+        "npm-run-all": "bin/npm-run-all/index.js",
+        "run-p": "bin/run-p/index.js",
+        "run-s": "bin/run-s/index.js"
+      },
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
+      "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==",
+      "dev": true
+    },
+    "node_modules/object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/object.assign": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+      "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+      "dev": true,
+      "dependencies": {
+        "define-properties": "^1.1.2",
+        "function-bind": "^1.1.1",
+        "has-symbols": "^1.0.0",
+        "object-keys": "^1.0.11"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/opts": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz",
+      "integrity": "sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==",
+      "dev": true
+    },
+    "node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "dev": true,
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+      "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/parse-json": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+      "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+      "dev": true,
+      "dependencies": {
+        "error-ex": "^1.3.1",
+        "json-parse-better-errors": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "node_modules/path-type": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+      "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+      "dev": true,
+      "dependencies": {
+        "pify": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/picomatch": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+      "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/pidtree": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz",
+      "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==",
+      "dev": true,
+      "bin": {
+        "pidtree": "bin/pidtree.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/pify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/read-pkg": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+      "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+      "dev": true,
+      "dependencies": {
+        "load-json-file": "^4.0.0",
+        "normalize-package-data": "^2.3.2",
+        "path-type": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
+      "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "dev": true
+    },
+    "node_modules/resolve": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+      "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+      "dev": true,
+      "dependencies": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "node_modules/sass": {
+      "version": "1.32.8",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz",
+      "integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": ">=2.0.0 <4.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=8.9.0"
+      }
+    },
+    "node_modules/select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+      "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
+    },
+    "node_modules/semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver"
+      }
+    },
+    "node_modules/set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
+    },
+    "node_modules/shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/shell-quote": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
+      "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+      "dev": true
+    },
+    "node_modules/sortablejs": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz",
+      "integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg=="
+    },
+    "node_modules/spdx-correct": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+      "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+      "dev": true,
+      "dependencies": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "node_modules/spdx-exceptions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+      "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+      "dev": true
+    },
+    "node_modules/spdx-expression-parse": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+      "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+      "dev": true,
+      "dependencies": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "node_modules/spdx-license-ids": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+      "dev": true
+    },
+    "node_modules/sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
+    "node_modules/string-width": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+      "dev": true,
+      "dependencies": {
+        "emoji-regex": "^7.0.1",
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/string.prototype.padend": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz",
+      "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==",
+      "dev": true,
+      "dependencies": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/string.prototype.trimend": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
+      "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
+      "dev": true,
+      "dependencies": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "node_modules/string.prototype.trimstart": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
+      "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
+      "dev": true,
+      "dependencies": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/uc.micro": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
+      "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
+    },
+    "node_modules/validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "dependencies": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "node_modules/which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "which": "bin/which"
+      }
+    },
+    "node_modules/which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+      "dev": true
+    },
+    "node_modules/wrap-ansi": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+      "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^3.2.0",
+        "string-width": "^3.0.0",
+        "strip-ansi": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/ws": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
+      "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.3.0"
+      }
+    },
+    "node_modules/y18n": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
+      "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
+      "dev": true
+    },
+    "node_modules/yargs": {
+      "version": "13.3.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+      "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+      "dev": true,
+      "dependencies": {
+        "cliui": "^5.0.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^3.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^13.1.2"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "13.1.2",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+      "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+      "dev": true,
+      "dependencies": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    }
+  },
   "dependencies": {
     "ansi-regex": {
       "version": "4.1.0",
         "sprintf-js": "~1.0.2"
       }
     },
-    "async-limiter": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
-      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
-      "dev": true
-    },
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
       }
     },
     "clipboard": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
-      "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
+      "version": "2.0.8",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
+      "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
       "requires": {
         "good-listener": "^1.2.2",
         "select": "^1.1.2",
       }
     },
     "codemirror": {
-      "version": "5.58.1",
-      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.58.1.tgz",
-      "integrity": "sha512-UGb/ueu20U4xqWk8hZB3xIfV2/SFqnSLYONiM3wTMDqko0bsYrsAkGGhqUzbRkYm89aBKPyHtuNEbVWF9FTFzw=="
+      "version": "5.60.0",
+      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.60.0.tgz",
+      "integrity": "sha512-AEL7LhFOlxPlCL8IdTcJDblJm8yrAGib7I+DErJPdZd4l6imx8IMgKK3RblVgBQqz3TZJR4oknQ03bz+uNjBYA=="
     },
     "color-convert": {
       "version": "1.9.3",
       "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
     },
     "dropzone": {
-      "version": "5.7.2",
-      "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.7.2.tgz",
-      "integrity": "sha512-m217bJHtf0J1IiKn4Tv6mnu1h5QvQNBnKZ39gma7hzGQhIZMxYq1vYEHs4AVd4ThFwmALys+52NAOD4zdLTG4w=="
+      "version": "5.9.2",
+      "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.2.tgz",
+      "integrity": "sha512-5t2z51DzIsWDbTpwcJIvUlwxBbvcwdCApz0yb9ecKJwG155Xm92KMEZmHW1B0MzoXOKvFwdd0nPu5cpeVcvPHQ=="
     },
     "emoji-regex": {
       "version": "7.0.3",
       "dev": true
     },
     "hosted-git-info": {
-      "version": "2.8.8",
-      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
-      "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+      "version": "2.8.9",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+      "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
       "dev": true
     },
     "is-arrayish": {
       }
     },
     "livereload": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.1.tgz",
-      "integrity": "sha512-9g7sua11kkyZNo2hLRCG3LuZZwqexoyEyecSlV8cAsfAVVCZqLzVir6XDqmH0r+Vzgnd5LrdHDMyjtFnJQLAYw==",
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
+      "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==",
       "dev": true,
       "requires": {
-        "chokidar": "^3.3.0",
-        "livereload-js": "^3.1.0",
+        "chokidar": "^3.5.0",
+        "livereload-js": "^3.3.1",
         "opts": ">= 1.2.0",
-        "ws": "^6.2.1"
+        "ws": "^7.4.3"
+      },
+      "dependencies": {
+        "chokidar": {
+          "version": "3.5.1",
+          "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
+          "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
+          "dev": true,
+          "requires": {
+            "anymatch": "~3.1.1",
+            "braces": "~3.0.2",
+            "fsevents": "~2.3.1",
+            "glob-parent": "~5.1.0",
+            "is-binary-path": "~2.1.0",
+            "is-glob": "~4.0.1",
+            "normalize-path": "~3.0.0",
+            "readdirp": "~3.5.0"
+          }
+        },
+        "fsevents": {
+          "version": "2.3.2",
+          "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+          "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+          "dev": true,
+          "optional": true
+        },
+        "readdirp": {
+          "version": "3.5.0",
+          "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+          "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+          "dev": true,
+          "requires": {
+            "picomatch": "^2.2.1"
+          }
+        }
       }
     },
     "livereload-js": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.3.1.tgz",
-      "integrity": "sha512-CBu1gTEfzVhlOK1WASKAAJ9Qx1fHECTq0SUB67sfxwQssopTyvzqTlgl+c0h9pZ6V+Fzd2rc510ppuNusg9teQ==",
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.3.2.tgz",
+      "integrity": "sha512-w677WnINxFkuixAoUEXOStewzLYGI76XVag+0JWMMEyjJQKs0ibWZMxkTlB96Lm3EjZ7IeOxVziBEbtxVQqQZA==",
       "dev": true
     },
     "load-json-file": {
       }
     },
     "sass": {
-      "version": "1.26.11",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.26.11.tgz",
-      "integrity": "sha512-W1l/+vjGjIamsJ6OnTe0K37U2DBO/dgsv2Z4c89XQ8ZOO6l/VwkqwLSqoYzJeJs6CLuGSTRWc91GbQFL3lvrvw==",
+      "version": "1.32.8",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz",
+      "integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==",
       "dev": true,
       "requires": {
         "chokidar": ">=2.0.0 <4.0.0"
       "dev": true
     },
     "sortablejs": {
-      "version": "1.12.0",
-      "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.12.0.tgz",
-      "integrity": "sha512-bPn57rCjBRlt2sC24RBsu40wZsmLkSo2XeqG8k6DC1zru5eObQUIPPZAQG7W2SJ8FZQYq+BEJmvuw1Zxb3chqg=="
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.13.0.tgz",
+      "integrity": "sha512-RBJirPY0spWCrU5yCmWM1eFs/XgX2J5c6b275/YyxFRgnzPhKl/TDeU2hNR8Dt7ITq66NRPM4UlOt+e5O4CFHg=="
     },
     "spdx-correct": {
       "version": "3.1.1",
       }
     },
     "ws": {
-      "version": "6.2.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
-      "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
-      "dev": true,
-      "requires": {
-        "async-limiter": "~1.0.0"
-      }
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
+      "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
+      "dev": true
     },
     "y18n": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
-      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
+      "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
       "dev": true
     },
     "yargs": {
index d5e93a31e3dc4aaca1e4835196656da0c1f28d4f..503abd44fda5b9a12b15ca3987aee96367757f5e 100644 (file)
   "devDependencies": {
     "chokidar-cli": "^2.1.0",
     "esbuild": "0.7.8",
-    "livereload": "^0.9.1",
+    "livereload": "^0.9.3",
     "npm-run-all": "^4.1.5",
     "punycode": "^2.1.1",
-    "sass": "^1.26.11"
+    "sass": "^1.32.8"
   },
   "dependencies": {
-    "clipboard": "^2.0.6",
-    "codemirror": "^5.58.1",
-    "dropzone": "^5.7.2",
+    "clipboard": "^2.0.8",
+    "codemirror": "^5.60.0",
+    "dropzone": "^5.9.2",
     "markdown-it": "^11.0.1",
     "markdown-it-task-lists": "^2.1.1",
-    "sortablejs": "^1.12.0"
+    "sortablejs": "^1.13.0"
   }
 }
index ccde28033ab9a3675dbf0203c073e09c01108925..8d5157d9ea41d60c046dd538400308c8faabe0d3 100644 (file)
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -1,9 +1,12 @@
 <?xml version="1.0"?>
-<ruleset name="PHP_CodeSniffer">
+<ruleset name="BookStack Standard">
+    <!--    Format described at: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-Ruleset   -->
     <description>The coding standard for BookStack.</description>
-    <file>app</file>
+    <config name="php_version" value="70205"/>
+    <file>./app</file>
     <exclude-pattern>*/migrations/*</exclude-pattern>
     <exclude-pattern>*/tests/*</exclude-pattern>
     <arg value="np"/>
+    <arg name="colors"/>
     <rule ref="PSR2"/>
 </ruleset>
\ No newline at end of file
index 8d69a5fddd3a8eee35101cceff94c6e3acd567ec..75c89ec335fb8cb1e7e3b42d5a483edf0b77dd25 100644 (file)
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<phpunit backupGlobals="false"
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
+         backupGlobals="false"
          backupStaticAttributes="false"
          bootstrap="vendor/autoload.php"
          colors="true"
          convertWarningsToExceptions="true"
          processIsolation="false"
          stopOnFailure="false">
-    <testsuites>
-        <testsuite name="Application Test Suite">
-            <directory>./tests/</directory>
-        </testsuite>
-    </testsuites>
-    <filter>
-        <whitelist>
-            <directory suffix=".php">app/</directory>
-        </whitelist>
-    </filter>
-    <php>
-        <server name="APP_ENV" value="testing"/>
-        <server name="APP_DEBUG" value="false"/>
-        <server name="APP_LANG" value="en"/>
-        <server name="APP_THEME" value="none"/>
-        <server name="APP_AUTO_LANG_PUBLIC" value="true"/>
-        <server name="APP_URL" value="http://bookstack.dev"/>
-        <server name="ALLOWED_IFRAME_HOSTS" value=""/>
-        <server name="CACHE_DRIVER" value="array"/>
-        <server name="SESSION_DRIVER" value="array"/>
-        <server name="QUEUE_CONNECTION" value="sync"/>
-        <server name="DB_CONNECTION" value="mysql_testing"/>
-        <server name="BCRYPT_ROUNDS" value="4"/>
-        <server name="MAIL_DRIVER" value="array"/>
-        <server name="LOG_CHANNEL" value="single"/>
-        <server name="AUTH_METHOD" value="standard"/>
-        <server name="DISABLE_EXTERNAL_SERVICES" value="true"/>
-        <server name="AVATAR_URL" value=""/>
-        <server name="LDAP_VERSION" value="3"/>
-        <server name="SESSION_SECURE_COOKIE" value="null"/>
-        <server name="STORAGE_TYPE" value="local"/>
-        <server name="STORAGE_ATTACHMENT_TYPE" value="local"/>
-        <server name="STORAGE_IMAGE_TYPE" value="local"/>
-        <server name="GITHUB_APP_ID" value="aaaaaaaaaaaaaa"/>
-        <server name="GITHUB_APP_SECRET" value="aaaaaaaaaaaaaa"/>
-        <server name="GITHUB_AUTO_REGISTER" value=""/>
-        <server name="GITHUB_AUTO_CONFIRM_EMAIL" value=""/>
-        <server name="GOOGLE_APP_ID" value="aaaaaaaaaaaaaa"/>
-        <server name="GOOGLE_APP_SECRET" value="aaaaaaaaaaaaaa"/>
-        <server name="GOOGLE_AUTO_REGISTER" value=""/>
-        <server name="GOOGLE_AUTO_CONFIRM_EMAIL" value=""/>
-        <server name="GOOGLE_SELECT_ACCOUNT" value=""/>
-        <server name="DEBUGBAR_ENABLED" value="false"/>
-        <server name="SAML2_ENABLED" value="false"/>
-        <server name="API_REQUESTS_PER_MIN" value="180"/>
-        <server name="LOG_FAILED_LOGIN_MESSAGE" value=""/>
-        <server name="LOG_FAILED_LOGIN_CHANNEL" value="testing"/>
-    </php>
+  <coverage>
+    <include>
+      <directory suffix=".php">app/</directory>
+    </include>
+  </coverage>
+  <testsuites>
+    <testsuite name="Application Test Suite">
+      <directory>./tests/</directory>
+    </testsuite>
+  </testsuites>
+  <php>
+    <server name="APP_ENV" value="testing"/>
+    <server name="APP_DEBUG" value="false"/>
+    <server name="APP_LANG" value="en"/>
+    <server name="APP_THEME" value="none"/>
+    <server name="APP_AUTO_LANG_PUBLIC" value="true"/>
+    <server name="APP_URL" value="http://bookstack.dev"/>
+    <server name="ALLOWED_IFRAME_HOSTS" value=""/>
+    <server name="CACHE_DRIVER" value="array"/>
+    <server name="SESSION_DRIVER" value="array"/>
+    <server name="QUEUE_CONNECTION" value="sync"/>
+    <server name="DB_CONNECTION" value="mysql_testing"/>
+    <server name="BCRYPT_ROUNDS" value="4"/>
+    <server name="MAIL_DRIVER" value="array"/>
+    <server name="LOG_CHANNEL" value="single"/>
+    <server name="AUTH_METHOD" value="standard"/>
+    <server name="DISABLE_EXTERNAL_SERVICES" value="true"/>
+    <server name="AVATAR_URL" value=""/>
+    <server name="LDAP_START_TLS" value="false"/>
+    <server name="LDAP_VERSION" value="3"/>
+    <server name="SESSION_SECURE_COOKIE" value="null"/>
+    <server name="STORAGE_TYPE" value="local"/>
+    <server name="STORAGE_ATTACHMENT_TYPE" value="local"/>
+    <server name="STORAGE_IMAGE_TYPE" value="local"/>
+    <server name="GITHUB_APP_ID" value="aaaaaaaaaaaaaa"/>
+    <server name="GITHUB_APP_SECRET" value="aaaaaaaaaaaaaa"/>
+    <server name="GITHUB_AUTO_REGISTER" value=""/>
+    <server name="GITHUB_AUTO_CONFIRM_EMAIL" value=""/>
+    <server name="GOOGLE_APP_ID" value="aaaaaaaaaaaaaa"/>
+    <server name="GOOGLE_APP_SECRET" value="aaaaaaaaaaaaaa"/>
+    <server name="GOOGLE_AUTO_REGISTER" value=""/>
+    <server name="GOOGLE_AUTO_CONFIRM_EMAIL" value=""/>
+    <server name="GOOGLE_SELECT_ACCOUNT" value=""/>
+    <server name="DEBUGBAR_ENABLED" value="false"/>
+    <server name="SAML2_ENABLED" value="false"/>
+    <server name="API_REQUESTS_PER_MIN" value="180"/>
+    <server name="LOG_FAILED_LOGIN_MESSAGE" value=""/>
+    <server name="LOG_FAILED_LOGIN_CHANNEL" value="testing"/>
+    <server name="WKHTMLTOPDF" value="false"/>
+    <server name="APP_DEFAULT_DARK_MODE" value="false"/>
+  </php>
 </phpunit>
index fd61a62c7611d72701f84277e02388371d65f3a7..eb98ae6d47dc819cd2de34547b8e885e2b599456 100644 (file)
--- a/readme.md
+++ b/readme.md
@@ -4,7 +4,8 @@
 [![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/BookStackApp/BookStack/blob/master/LICENSE)
 [![Crowdin](https://badges.crowdin.net/bookstack/localized.svg)](https://crowdin.com/project/bookstack)
 [![Build Status](https://github.com/BookStackApp/BookStack/workflows/phpunit/badge.svg)](https://github.com/BookStackApp/BookStack/actions)
-[![Discord](https://img.shields.io/static/v1?label=Chat&message=Discord&color=738adb&logo=discord)](https://discord.gg/ztkBqR2)
+[![Discord](https://img.shields.io/static/v1?label=chat&message=discord&color=738adb&logo=discord)](https://discord.gg/ztkBqR2)
+[![Repo Stats](https://img.shields.io/static/v1?label=GitHub+project&message=stats&color=f27e3f)](https://gh-stats.bookstackapp.com/)
 
 A platform for storing and organising information and documentation. Details for BookStack can be found on the official website at https://www.bookstackapp.com/.
 
@@ -41,7 +42,7 @@ Below is a high-level road map view for BookStack to provide a sense of directio
 
 BookStack releases are each assigned a version number, such as "v0.25.2", in the format `v<phase>.<feature>.<patch>`. A change only in the `patch` number indicates a fairly minor release that mainly contains fixes and therefore is very unlikely to cause breakages upon update. A change in the `feature` number indicates a release which will generally bring new features in addition to fixes and enhancements. These releases have a small chance of introducing breaking changes upon update so it's worth checking for any notes in the [update guide](https://www.bookstackapp.com/docs/admin/updates/). A change in the `phase` indicates a much large change in BookStack that will likely incur breakages requiring manual intervention.
 
-Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed. 
+Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed.
 
 For feature releases, and some patch releases, the release will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [GitHub release page](https://github.com/BookStackApp/BookStack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blogs posts (once per week maximum) [at this link](https://updates.bookstackapp.com/signup/bookstack-news-and-updates).
 
@@ -101,12 +102,28 @@ If all the conditions are met, you can proceed with the following steps:
 
 If needed, You'll be able to run any artisan commands via docker-compose like so:
 
- ```shell script
-docker-compose run app php artisan list 
+```bash
+docker-compose run app php artisan list
 ```
 
 The docker-compose setup runs an instance of [MailHog](https://github.com/mailhog/MailHog) and sets environment variables to redirect any BookStack-sent emails to MailHog. You can view this mail via the MailHog web interface on `localhost:8025`. You can change the port MailHog is accessible on by setting a `DEV_MAIL_PORT` environment variable.
 
+#### Running tests
+
+After starting the general development Docker, migrate & seed the testing database:
+
+ ```bash
+# This only needs to be done once
+docker-compose run app php artisan migrate --database=mysql_testing
+docker-compose run app php artisan db:seed --class=DummyContentSeeder --database=mysql_testing
+```
+
+Once the database has been migrated & seeded, you can run the tests like so:
+
+ ```bash
+docker-compose run app php vendor/bin/phpunit
+```
+
 ## 🌎 Translations
 
 Translations for text within BookStack is managed through the [BookStack project on Crowdin](https://crowdin.com/project/bookstack). Some strings have colon-prefixed variables in such as `:userName`. Leave these values as they are as they will be replaced at run-time. Crowdin is the preferred way to provide translations, otherwise the raw translations files can be found within the `resources/lang` path.
@@ -121,7 +138,7 @@ Feel free to create issues to request new features or to report bugs & problems.
 
 Pull requests are welcome. Unless a small tweak or language update, It may be best to open the pull request early or create an issue for your intended change to discuss how it will fit in to the project and plan out the merge. Just because a feature request exists, or is tagged, does not mean that feature would be accepted into the core project.
 
-Pull requests should be created from the `master` branch since they will be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases. If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/assets`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
+Pull requests should be created from the `master` branch since they will be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases. If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
 
 The project's code of conduct [can be found here](https://github.com/BookStackApp/BookStack/blob/master/.github/CODE_OF_CONDUCT.md).
 
@@ -169,3 +186,5 @@ These are the great open-source projects used to help build BookStack:
 * [WKHTMLtoPDF](http://wkhtmltopdf.org/index.html)
 * [diagrams.net](https://github.com/jgraph/drawio)
 * [OneLogin's SAML PHP Toolkit](https://github.com/onelogin/php-saml)
+* [League/CommonMark](https://commonmark.thephpleague.com/)
+* [League/Flysystem](https://flysystem.thephpleague.com)
index 0e3f1687911a6316e4b7de75c05d85c16770659a..a7667e48f1a4c68e1168a0567912bf2279bac52f 100644 (file)
@@ -1,4 +1,3 @@
 <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
-    <path d="M0 0h24v24H0z" fill="none"/>
     <path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/>
 </svg>
\ No newline at end of file
diff --git a/resources/icons/star-outline.svg b/resources/icons/star-outline.svg
new file mode 100644 (file)
index 0000000..4e83ab4
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"/></svg>
\ No newline at end of file
index 73e328cee060851823187396a8bccc2aee41bcc0..1d92d2b1849ec6d9ea1d28f904c8644d0e2de055 100644 (file)
@@ -1,4 +1 @@
-<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
-    <path d="M0 0h24v24H0z" fill="none"/>
-    <path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16z"/>
-</svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24"><path d="M21.41,11.41l-8.83-8.83C12.21,2.21,11.7,2,11.17,2H4C2.9,2,2,2.9,2,4v7.17c0,0.53,0.21,1.04,0.59,1.41l8.83,8.83 c0.78,0.78,2.05,0.78,2.83,0l7.17-7.17C22.2,13.46,22.2,12.2,21.41,11.41z M6.5,8C5.67,8,5,7.33,5,6.5S5.67,5,6.5,5S8,5.67,8,6.5 S7.33,8,6.5,8z"/></svg>
\ No newline at end of file
index 22402d483902b2148918150e1d371611dd1abf9b..f761ecf011541590963caf66daf4b145f43c8cde 100644 (file)
@@ -12,6 +12,7 @@ class DropDown {
         this.menu = this.$refs.menu;
         this.toggle = this.$refs.toggle;
         this.moveMenu = this.$opts.moveMenu;
+        this.bubbleEscapes = this.$opts.bubbleEscapes === 'true';
 
         this.direction = (document.dir === 'rtl') ? 'right' : 'left';
         this.body = document.body;
@@ -137,7 +138,9 @@ class DropDown {
             } else if (event.key === 'Escape') {
                 this.hide();
                 this.toggle.focus();
-                event.stopPropagation();
+                if (!this.bubbleEscapes) {
+                    event.stopPropagation();
+                }
             }
         };
         this.container.addEventListener('keydown', keyboardNavigation);
index 58879a20c0c5d8c76534e3af2bfc5a2da59c9e46..6d9d06f860329b402c9166db51ffc646537ca988 100644 (file)
@@ -1,22 +1,32 @@
+import {onChildEvent} from "../services/dom";
 
+/**
+ * Entity Selector
+ * @extends {Component}
+ */
 class EntitySelector {
 
-    constructor(elem) {
-        this.elem = elem;
+    setup() {
+        this.elem = this.$el;
+        this.entityTypes = this.$opts.entityTypes || 'page,book,chapter';
+        this.entityPermission = this.$opts.entityPermission || 'view';
+
+        this.input = this.$refs.input;
+        this.searchInput = this.$refs.search;
+        this.loading = this.$refs.loading;
+        this.resultsContainer = this.$refs.results;
+        this.addButton = this.$refs.add;
+
         this.search = '';
         this.lastClick = 0;
         this.selectedItemData = null;
 
-        const entityTypes = elem.hasAttribute('entity-types') ? elem.getAttribute('entity-types') : 'page,book,chapter';
-        const entityPermission = elem.hasAttribute('entity-permission') ? elem.getAttribute('entity-permission') : 'view';
-        this.searchUrl = window.baseUrl(`/ajax/search/entities?types=${encodeURIComponent(entityTypes)}&permission=${encodeURIComponent(entityPermission)}`);
-
-        this.input = elem.querySelector('[entity-selector-input]');
-        this.searchInput = elem.querySelector('[entity-selector-search]');
-        this.loading = elem.querySelector('[entity-selector-loading]');
-        this.resultsContainer = elem.querySelector('[entity-selector-results]');
-        this.addButton = elem.querySelector('[entity-selector-add-button]');
+        this.setupListeners();
+        this.showLoading();
+        this.initialLoad();
+    }
 
+    setupListeners() {
         this.elem.addEventListener('click', this.onClick.bind(this));
 
         let lastSearch = 0;
@@ -42,8 +52,39 @@ class EntitySelector {
             });
         }
 
-        this.showLoading();
-        this.initialLoad();
+        // Keyboard navigation
+        onChildEvent(this.$el, '[data-entity-type]', 'keydown', (e, el) => {
+            if (e.ctrlKey && e.code === 'Enter') {
+                const form = this.$el.closest('form');
+                if (form) {
+                    form.submit();
+                    e.preventDefault();
+                    return;
+                }
+            }
+
+            if (e.code === 'ArrowDown') {
+                this.focusAdjacent(true);
+            }
+            if (e.code === 'ArrowUp') {
+                this.focusAdjacent(false);
+            }
+        });
+
+        this.searchInput.addEventListener('keydown', e => {
+            if (e.code === 'ArrowDown') {
+                this.focusAdjacent(true);
+            }
+        })
+    }
+
+    focusAdjacent(forward = true) {
+        const items = Array.from(this.resultsContainer.querySelectorAll('[data-entity-type]'));
+        const selectedIndex = items.indexOf(document.activeElement);
+        const newItem = items[selectedIndex+ (forward ? 1 : -1)] || items[0];
+        if (newItem) {
+            newItem.focus();
+        }
     }
 
     showLoading() {
@@ -57,15 +98,19 @@ class EntitySelector {
     }
 
     initialLoad() {
-        window.$http.get(this.searchUrl).then(resp => {
+        window.$http.get(this.searchUrl()).then(resp => {
             this.resultsContainer.innerHTML = resp.data;
             this.hideLoading();
         })
     }
 
+    searchUrl() {
+        return `/ajax/search/entities?types=${encodeURIComponent(this.entityTypes)}&permission=${encodeURIComponent(this.entityPermission)}`;
+    }
+
     searchEntities(searchTerm) {
         this.input.value = '';
-        let url = `${this.searchUrl}&term=${encodeURIComponent(searchTerm)}`;
+        const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`;
         window.$http.get(url).then(resp => {
             this.resultsContainer.innerHTML = resp.data;
             this.hideLoading();
@@ -73,8 +118,8 @@ class EntitySelector {
     }
 
     isDoubleClick() {
-        let now = Date.now();
-        let answer = now - this.lastClick < 300;
+        const now = Date.now();
+        const answer = now - this.lastClick < 300;
         this.lastClick = now;
         return answer;
     }
@@ -123,8 +168,8 @@ class EntitySelector {
     }
 
     unselectAll() {
-        let selected = this.elem.querySelectorAll('.selected');
-        for (let selectedElem of selected) {
+        const selected = this.elem.querySelectorAll('.selected');
+        for (const selectedElem of selected) {
             selectedElem.classList.remove('selected', 'primary-background');
         }
         this.selectedItemData = null;
index eccd4b8f0a6b25e722b1754cd6c611db1ed3ac64..99737bfb8b0fb741564a5c854d566fd3f774067a 100644 (file)
@@ -1,31 +1,41 @@
 
 class HeaderMobileToggle {
 
-    constructor(elem) {
-        this.elem = elem;
-        this.toggleButton = elem.querySelector('.mobile-menu-toggle');
-        this.menu = elem.querySelector('.header-links');
-        this.open = false;
+    setup() {
+        this.elem = this.$el;
+        this.toggleButton = this.$refs.toggle;
+        this.menu = this.$refs.menu;
 
+        this.open = false;
         this.toggleButton.addEventListener('click', this.onToggle.bind(this));
         this.onWindowClick = this.onWindowClick.bind(this);
+        this.onKeyDown = this.onKeyDown.bind(this);
     }
 
     onToggle(event) {
         this.open = !this.open;
         this.menu.classList.toggle('show', this.open);
+        this.toggleButton.setAttribute('aria-expanded', this.open ? 'true' : 'false');
         if (this.open) {
+            this.elem.addEventListener('keydown', this.onKeyDown);
             window.addEventListener('click', this.onWindowClick)
         } else {
+            this.elem.removeEventListener('keydown', this.onKeyDown);
             window.removeEventListener('click', this.onWindowClick)
         }
         event.stopPropagation();
     }
 
+    onKeyDown(event) {
+        if (event.code === 'Escape') {
+            this.onToggle(event);
+        }
+    }
+
     onWindowClick(event) {
         this.onToggle(event);
     }
 
 }
 
-module.exports = HeaderMobileToggle;
\ No newline at end of file
+export default HeaderMobileToggle;
\ No newline at end of file
index bd107f2bf7a00f53ed3404a27c95bcf3c1c7d1c3..a90f74e2746401562f59c03cea0b8824298f9dc8 100644 (file)
@@ -14,6 +14,7 @@ class MarkdownEditor {
         this.pageId = this.$opts.pageId;
         this.textDirection = this.$opts.textDirection;
         this.imageUploadErrorText = this.$opts.imageUploadErrorText;
+        this.serverUploadLimitText = this.$opts.serverUploadLimitText;
 
         this.markdown = new MarkdownIt({html: true});
         this.markdown.use(mdTasksLists, {label: true});
@@ -22,7 +23,6 @@ class MarkdownEditor {
 
         this.displayStylesLoaded = false;
         this.input = this.elem.querySelector('textarea');
-        this.htmlInput = this.elem.querySelector('input[name=html]');
         this.cm = code.markdownEditor(this.input);
 
         this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
@@ -125,7 +125,6 @@ class MarkdownEditor {
         // Set body content
         this.displayDoc.body.className = 'page-content';
         this.displayDoc.body.innerHTML = html;
-        this.htmlInput.value = html;
 
         // Copy styles from page head and set custom styles for editor
         this.loadStylesIntoDisplay();
@@ -448,8 +447,7 @@ class MarkdownEditor {
                 this.insertDrawing(resp.data, cursorPos);
                 DrawIO.close();
             }).catch(err => {
-                window.$events.emit('error', trans('errors.image_upload_error'));
-                console.log(err);
+                this.handleDrawingUploadError(err);
             });
         });
     }
@@ -493,12 +491,20 @@ class MarkdownEditor {
                 this.cm.focus();
                 DrawIO.close();
             }).catch(err => {
-                window.$events.emit('error', this.imageUploadErrorText);
-                console.log(err);
+                this.handleDrawingUploadError(err);
             });
         });
     }
 
+    handleDrawingUploadError(error) {
+        if (error.status === 413) {
+            window.$events.emit('error', this.serverUploadLimitText);
+        } else {
+            window.$events.emit('error', this.imageUploadErrorText);
+        }
+        console.log(error);
+    }
+
     // Make the editor full screen
     actionFullScreen() {
         const alreadyFullscreen = this.elem.classList.contains('fullscreen');
index 2be1c1c48b8cc93f5ac147b64023cf910eabc3fa..cc55fe35e1e38caae9de9f9cca013e0c7b1d14f6 100644 (file)
@@ -12,6 +12,7 @@ class PageDisplay {
         Code.highlight();
         this.setupPointer();
         this.setupNavHighlighting();
+        this.setupDetailsCodeBlockRefresh();
 
         // Check the hash on load
         if (window.location.hash) {
@@ -196,6 +197,16 @@ class PageDisplay {
             });
         }
     }
+
+    setupDetailsCodeBlockRefresh() {
+        const onToggle = event => {
+            const codeMirrors = [...event.target.querySelectorAll('.CodeMirror')];
+            codeMirrors.forEach(cm => cm.CodeMirror && cm.CodeMirror.refresh());
+        };
+
+        const details = [...this.elem.querySelectorAll('details')];
+        details.forEach(detail => detail.addEventListener('toggle', onToggle));
+    }
 }
 
 export default PageDisplay;
index 905ca03b1020d566859366d6e2ecc8e851edc784..f801e52a193715fdea427fa4eac03c2372ca9abf 100644 (file)
@@ -1,8 +1,9 @@
 
 class TriLayout {
 
-    constructor(elem) {
-        this.elem = elem;
+    setup() {
+        this.container = this.$refs.container;
+        this.tabs = this.$manyRefs.tab;
 
         this.lastLayoutType = 'none';
         this.onDestroy = null;
@@ -43,13 +44,12 @@ class TriLayout {
     }
 
     setupMobile() {
-        const layoutTabs = document.querySelectorAll('[tri-layout-mobile-tab]');
-        for (let tab of layoutTabs) {
+        for (const tab of this.tabs) {
             tab.addEventListener('click', this.mobileTabClick);
         }
 
         this.onDestroy = () => {
-            for (let tab of layoutTabs) {
+            for (const tab of this.tabs) {
                 tab.removeEventListener('click', this.mobileTabClick);
             }
         }
@@ -65,7 +65,7 @@ class TriLayout {
      * @param event
      */
     mobileTabClick(event) {
-        const tab = event.target.getAttribute('tri-layout-mobile-tab');
+        const tab = event.target.dataset.tab;
         this.showTab(tab);
     }
 
@@ -79,21 +79,21 @@ class TriLayout {
 
     /**
      * Show the given tab
-     * @param tabName
+     * @param {String} tabName
+     * @param {Boolean }scroll
      */
     showTab(tabName, scroll = true) {
         this.scrollCache[this.lastTabShown] = document.documentElement.scrollTop;
 
         // Set tab status
-        const tabs = document.querySelectorAll('.tri-layout-mobile-tab');
-        for (let tab of tabs) {
-            const isActive = (tab.getAttribute('tri-layout-mobile-tab') === tabName);
-            tab.classList.toggle('active', isActive);
+        for (const tab of this.tabs) {
+            const isActive = (tab.dataset.tab === tabName);
+            tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
         }
 
         // Toggle section
         const showInfo = (tabName === 'info');
-        this.elem.classList.toggle('show-info', showInfo);
+        this.container.classList.toggle('show-info', showInfo);
 
         // Set the scroll position from cache
         if (scroll) {
index 477c11d6b5d0e3184fad6ed5144fbb472bc9482e..c2c1f97c3aee5911fdb8c80ce4d63c5609370139 100644 (file)
@@ -13,9 +13,11 @@ class UserSelect {
     }
 
     selectUser(event, userEl) {
+        event.preventDefault();
         const id = userEl.getAttribute('data-id');
         this.input.value = id;
         this.userInfoContainer.innerHTML = userEl.innerHTML;
+        this.input.dispatchEvent(new Event('change', {bubbles: true}));
         this.hide();
     }
 
index 41b2273e2a0ad48918e5e50c334f5c1e29ef319f..bde73f4bfb12aa2e87e4f3d250e7b76d8831e314 100644 (file)
@@ -152,8 +152,8 @@ function codePlugin() {
             return;
         }
 
-        let lang = selectedNode.hasAttribute('data-lang') ? selectedNode.getAttribute('data-lang') : '';
-        let currentCode = selectedNode.querySelector('textarea').textContent;
+        const lang = selectedNode.hasAttribute('data-lang') ? selectedNode.getAttribute('data-lang') : '';
+        const currentCode = selectedNode.querySelector('textarea').textContent;
 
         window.components.first('code-editor').open(currentCode, lang, (code, lang) => {
             const editorElem = selectedNode.querySelector('.CodeMirror');
@@ -212,7 +212,7 @@ function codePlugin() {
             showPopup(editor);
         });
 
-        editor.on('SetContent', function () {
+        function parseCodeMirrorInstances() {
 
             // Recover broken codemirror instances
             $('.CodeMirrorContainer').filter((index ,elem) => {
@@ -225,12 +225,24 @@ function codePlugin() {
                 return elem.contentEditable !== "false";
             });
 
-            if (!codeSamples.length) return;
+            codeSamples.each((index, elem) => {
+                Code.wysiwygView(elem);
+            });
+        }
+
+        editor.on('init', function() {
+            // Parse code mirror instances on init, but delay a little so this runs after
+            // initial styles are fetched into the editor.
             editor.undoManager.transact(function () {
-                codeSamples.each((index, elem) => {
-                    Code.wysiwygView(elem);
-                });
+                parseCodeMirrorInstances();
             });
+            // Parsed code mirror blocks when content is set but wait before setting this handler
+            // to avoid any init 'SetContent' events.
+            setTimeout(() => {
+                editor.on('SetContent', () => {
+                    setTimeout(parseCodeMirrorInstances, 100);
+                });
+            }, 200);
         });
 
     });
@@ -271,6 +283,15 @@ function drawIoPlugin(drawioUrl, isDarkMode, pageId, wysiwygComponent) {
         const id = "image-" + Math.random().toString(16).slice(2);
         const loadingImage = window.baseUrl('/loading.gif');
 
+        const handleUploadError = (error) => {
+            if (error.status === 413) {
+                window.$events.emit('error', wysiwygComponent.serverUploadLimitText);
+            } else {
+                window.$events.emit('error', wysiwygComponent.imageUploadErrorText);
+            }
+            console.log(error);
+        };
+
         // Handle updating an existing image
         if (currentNode) {
             DrawIO.close();
@@ -280,8 +301,7 @@ function drawIoPlugin(drawioUrl, isDarkMode, pageId, wysiwygComponent) {
                 pageEditor.dom.setAttrib(imgElem, 'src', img.url);
                 pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', img.id);
             } catch (err) {
-                window.$events.emit('error', wysiwygComponent.imageUploadErrorText);
-                console.log(err);
+                handleUploadError(err);
             }
             return;
         }
@@ -295,8 +315,7 @@ function drawIoPlugin(drawioUrl, isDarkMode, pageId, wysiwygComponent) {
                 pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', img.id);
             } catch (err) {
                 pageEditor.dom.remove(id);
-                window.$events.emit('error', wysiwygComponent.imageUploadErrorText);
-                console.log(err);
+                handleUploadError(err);
             }
         }, 5);
     }
@@ -420,6 +439,7 @@ class WysiwygEditor {
         this.pageId = this.$opts.pageId;
         this.textDirection = this.$opts.textDirection;
         this.imageUploadErrorText = this.$opts.imageUploadErrorText;
+        this.serverUploadLimitText = this.$opts.serverUploadLimitText;
         this.isDarkMode = document.documentElement.classList.contains('dark-mode');
 
         this.plugins = "image imagetools table textcolor paste link autolink fullscreen code customhr autosave lists codeeditor media";
index e2aca1aad9e681d94a23763c95e550712e1bedfa..5727cd2b79a06f4e68b83eb68f1e1e34e2424300 100644 (file)
@@ -238,9 +238,7 @@ function wysiwygView(elem) {
         theme: getTheme(),
         readOnly: true
     });
-    setTimeout(() => {
-        cm.refresh();
-    }, 300);
+
     return {wrap: newWrap, editor: cm};
 }
 
index 17e57cd6b9165d567aa09f48197ea95e8b62551f..6e22919fb4d230556d99b6ce6a4382f077866af4 100644 (file)
@@ -1,5 +1,5 @@
 let iFrame = null;
-
+let lastApprovedOrigin;
 let onInit, onSave;
 
 /**
@@ -19,15 +19,22 @@ function show(drawioUrl, onInitCallback, onSaveCallback) {
     iFrame.setAttribute('class', 'fullscreen');
     iFrame.style.backgroundColor = '#FFFFFF';
     document.body.appendChild(iFrame);
+    lastApprovedOrigin = (new URL(drawioUrl)).origin;
 }
 
 function close() {
     drawEventClose();
 }
 
+/**
+ * Receive and handle a message event from the draw.io window.
+ * @param {MessageEvent} event
+ */
 function drawReceive(event) {
     if (!event.data || event.data.length < 1) return;
-    let message = JSON.parse(event.data);
+    if (event.origin !== lastApprovedOrigin) return;
+
+    const message = JSON.parse(event.data);
     if (message.event === 'init') {
         drawEventInit();
     } else if (message.event === 'exit') {
@@ -62,7 +69,7 @@ function drawEventClose() {
 }
 
 function drawPostMessage(data) {
-    iFrame.contentWindow.postMessage(JSON.stringify(data), '*');
+    iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin);
 }
 
 async function upload(imageData, pageUploadedToId) {
index b05dd23bfd7222834eade6395f655d5916b1b2c1..00865e69bd8ff83698864ed7c6d0dd0042ef465f 100644 (file)
@@ -149,8 +149,8 @@ async function getResponseContent(response) {
         return null;
     }
 
-    const responseContentType = response.headers.get('Content-Type');
-    const subType = responseContentType.split('/').pop();
+    const responseContentType = response.headers.get('Content-Type') || '';
+    const subType = responseContentType.split(';')[0].split('/').pop();
 
     if (subType === 'javascript' || subType === 'json') {
         return await response.json();
index 6dc8a83978dcc9e5b8f2c446fd368c68cf9f0722..c3422b8f51956e45e591a5199e5c45fd9cd3b55b 100644 (file)
@@ -33,7 +33,7 @@ return [
     'book_delete'                 => 'تم حذف الكتاب',
     'book_delete_notification'    => 'تم حذف الكتاب بنجاح',
     'book_sort'                   => 'تم سرد الكتاب',
-    'book_sort_notification'      => 'تÙ\85ت Ø¥Ø¹Ø§Ø¯Ø© سرد الكتاب بنجاح',
+    'book_sort_notification'      => 'Ø£Ù\8fعÙ\90Ù\8aدÙ\8e سرد الكتاب بنجاح',
 
     // Bookshelves
     'bookshelf_create'            => 'تم إنشاء رف الكتب',
index 60a7af4114a4c6c43f2302f97f15c199092f98c5..363310bba914b78e54ea4b2da260ec3457d801fe 100644 (file)
@@ -47,7 +47,7 @@ return [
     'reset_password_success' => 'تمت استعادة كلمة المرور بنجاح.',
     'email_reset_subject' => 'استعد كلمة المرور الخاصة بتطبيق :appName',
     'email_reset_text' => 'تم إرسال هذه الرسالة بسبب تلقينا لطلب استعادة كلمة المرور الخاصة بحسابكم.',
-    'email_reset_not_requested' => 'إذا لم يتم طلب استعادة كلمة المرور من قبلكم, فلا حاجة لاتخاذ أية خطوات.',
+    'email_reset_not_requested' => 'إذا لم يتم طلب استعادة كلمة المرور من قبلكم، فلا حاجة لاتخاذ أية خطوات.',
 
 
     // Email Confirmation
@@ -62,11 +62,11 @@ return [
     'email_not_confirmed' => 'لم يتم تأكيد البريد الإلكتروني',
     'email_not_confirmed_text' => 'لم يتم بعد تأكيد عنوان البريد الإلكتروني.',
     'email_not_confirmed_click_link' => 'الرجاء الضغط على الرابط المرسل إلى بريدكم الإلكتروني بعد تسجيلكم.',
-    'email_not_confirmed_resend' => 'إذا لم يتم إيجاد الرسالة, بإمكانكم إعادة إرسال رسالة التأكيد عن طريق تعبئة النموذج أدناه.',
+    'email_not_confirmed_resend' => 'إذا لم يتم إيجاد الرسالة، بإمكانكم إعادة إرسال رسالة التأكيد عن طريق تعبئة النموذج أدناه.',
     'email_not_confirmed_resend_button' => 'إعادة إرسال رسالة التأكيد',
 
     // User Invite
-    'user_invite_email_subject' => 'تم دعوتك للإنضمام إلى صفحة الحالة الخاصة بـ :app_name!',
+    'user_invite_email_subject' => 'تمت دعوتك للانضمام إلى صفحة الحالة الخاصة بـ :app_name!',
     'user_invite_email_greeting' => 'تم إنشاء حساب مستخدم لك على %site%.',
     'user_invite_email_text' => 'انقر على الزر أدناه لتعيين كلمة مرور الحساب والحصول على الوصول:',
     'user_invite_email_action' => 'كلمة سر المستخدم',
index 57d0b2dbd0629550458e7546c11d5c451a1c14aa..485326f1460cf965a3a99455566282b1a4bfb6e8 100644 (file)
@@ -42,19 +42,20 @@ return [
     'fullscreen' => 'شاشة كاملة',
 
     // Sort Options
-    'sort_options' => 'خيارات الترتيب',
-    'sort_direction_toggle' => 'الترتيب وفق الإتجاه',
+    'sort_options' => 'خيارات الفرز',
+    'sort_direction_toggle' => 'الفرز وفق الاتجاه',
     'sort_ascending' => 'فرز تصاعدي',
     'sort_descending' => 'فرز تنازلي',
     'sort_name' => 'الاسم',
+    'sort_default' => 'افتراضي',
     'sort_created_at' => 'تاريخ الإنشاء',
     'sort_updated_at' => 'تاريخ التحديث',
 
     // Misc
-    'deleted_user' => 'حذÙ\81 Ù\85ستخدÙ\85',
+    'deleted_user' => 'اÙ\84Ù\85ستخدÙ\85 Ø§Ù\84Ù\85حذÙ\88Ù\81',
     'no_activity' => 'لا يوجد نشاط لعرضه',
     'no_items' => 'لا توجد عناصر متوفرة',
-    'back_to_top' => 'العودة للبداية',
+    'back_to_top' => 'العودة إلى الأعلى',
     'toggle_details' => 'عرض / إخفاء التفاصيل',
     'toggle_thumbnails' => 'عرض / إخفاء الصور المصغرة',
     'details' => 'التفاصيل',
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'شريط التنقل',
 
     // Header
+    'header_menu_expand' => 'عرض القائمة',
     'profile_menu' => 'قائمة ملف التعريف',
     'view_profile' => 'عرض الملف الشخصي',
     'edit_profile' => 'تعديل الملف الشخصي',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'معلومات',
+    'tab_info_label' => 'تبويب: إظهار المعلومات الثانوية',
     'tab_content' => 'المحتوى',
+    'tab_content_label' => 'تبويب: إظهار المحتوى الأساسي',
 
     // Email Content
-    'email_action_help' => 'إذا Ù\88اجÙ\87تÙ\83Ù\85 Ù\85Ø´Ù\83Ù\84Ø© Ø¨ضغط زر ":actionText" فبإمكانكم نسخ الرابط أدناه ولصقه بالمتصفح:',
+    'email_action_help' => 'إذا Ù\88اجÙ\87تÙ\83Ù\85 Ù\85Ø´Ù\83Ù\84Ø© Ø¹Ù\86د ضغط زر ":actionText" فبإمكانكم نسخ الرابط أدناه ولصقه بالمتصفح:',
     'email_rights' => 'جميع الحقوق محفوظة',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'سياسة الخصوصية',
+    'terms_of_service' => 'اتفاقية شروط الخدمة',
 ];
index 422de114b2aba7490926a61b0e77c4de0245da7d..803232b2d00e94706b151da31f4f1647dbebad17 100644 (file)
@@ -15,7 +15,7 @@ return [
     'image_load_more' => 'المزيد',
     'image_image_name' => 'اسم الصورة',
     'image_delete_used' => 'هذه الصورة مستخدمة بالصفحات أدناه.',
-    'image_delete_confirm_text' => 'هل أنت متأكد من أنك تريد حذف هذه الصورة ؟',
+    'image_delete_confirm_text' => 'هل أنت متأكد من أنك تريد حذف هذه الصورة؟',
     'image_select_image' => 'تحديد الصورة',
     'image_dropzone' => 'قم بإسقاط الصورة أو اضغط هنا للرفع',
     'images_deleted' => 'تم حذف الصور',
index 98404373986b92ef4c61e2f7be862c5f4ae2b739..294343e228ff7dd7bf1f240527d36f8ec10f973d 100644 (file)
@@ -11,7 +11,7 @@ return [
     'recently_updated_pages' => 'صفحات حُدثت مؤخراً',
     'recently_created_chapters' => 'فصول أنشئت مؤخراً',
     'recently_created_books' => 'كتب أنشئت مؤخراً',
-    'recently_created_shelves' => 'اÙ\84أرÙ\81Ù\81 Ø§Ù\84Ù\85Ù\86شأة مؤخراً',
+    'recently_created_shelves' => 'أرÙ\81Ù\81 Ø£Ù\86شئت مؤخراً',
     'recently_update' => 'حُدثت مؤخراً',
     'recently_viewed' => 'عُرضت مؤخراً',
     'recent_activity' => 'نشاطات حديثة',
@@ -28,8 +28,8 @@ return [
     'my_recent_drafts' => 'مسوداتي الحديثة',
     'my_recently_viewed' => 'ما عرضته مؤخراً',
     'no_pages_viewed' => 'لم تستعرض أي صفحات',
-    'no_pages_recently_created' => 'لم يتم إنشاء أي صفحات مؤخراً',
-    'no_pages_recently_updated' => 'لم يتم تحديث أي صفحات مؤخراً',
+    'no_pages_recently_created' => 'لم تنشأ أي صفحات مؤخراً',
+    'no_pages_recently_updated' => 'لم تُحدّث أي صفحات مؤخراً',
     'export' => 'تصدير',
     'export_html' => 'صفحة ويب',
     'export_pdf' => 'ملف PDF',
@@ -37,7 +37,7 @@ return [
 
     // Permissions and restrictions
     'permissions' => 'الأذونات',
-    'permissions_intro' => 'في حال التفعيل, ستتم تبدية هذه الأذونات على أذونات الأدوار.',
+    'permissions_intro' => 'عند التفعيل، سوف تأخذ هذه الأذونات أولوية على أي صلاحية أخرى للدور.',
     'permissions_enable' => 'تفعيل الأذونات المخصصة',
     'permissions_save' => 'حفظ الأذونات',
     'permissions_owner' => 'Owner',
@@ -55,11 +55,12 @@ return [
     'search_exact_matches' => 'نتائج مطابقة تماماً',
     'search_tags' => 'بحث الوسوم',
     'search_options' => 'الخيارات',
-    'search_viewed_by_me' => 'تÙ\85 Ø§Ø³ØªØ¹Ø±Ø§Ø¶Ù\87ا من قبلي',
-    'search_not_viewed_by_me' => 'لم يتم استعراضها من قبلي',
+    'search_viewed_by_me' => 'استعرضت من قبلي',
+    'search_not_viewed_by_me' => 'لم تستعرض من قبلي',
     'search_permissions_set' => 'حزمة الأذونات',
     'search_created_by_me' => 'أنشئت بواسطتي',
     'search_updated_by_me' => 'حُدثت بواسطتي',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'خيارات التاريخ',
     'search_updated_before' => 'حدثت قبل',
     'search_updated_after' => 'حدثت بعد',
@@ -73,24 +74,24 @@ return [
     'shelves' => 'الأرفف',
     'x_shelves' => ':count رف|:count أرفف',
     'shelves_long' => 'أرفف الكتب',
-    'shelves_empty' => 'لم يتم إنشاء أي أرفف',
+    'shelves_empty' => 'لم ينشأ أي رف',
     'shelves_create' => 'إنشاء رف جديد',
-    'shelves_popular' => 'أرÙ\81Ù\81 Ø´Ø¹Ø¨Ù\8aة',
+    'shelves_popular' => 'أرÙ\81Ù\81 Ø±Ø§Ø¦Ø¬ة',
     'shelves_new' => 'أرفف جديدة',
     'shelves_new_action' => 'رف جديد',
     'shelves_popular_empty' => 'ستظهر هنا الأرفف الأكثر رواجًا.',
-    'shelves_new_empty' => 'ستظÙ\87ر Ù\87Ù\86ا Ø§Ù\84أرÙ\81Ù\81 Ø§Ù\84تÙ\8a ØªÙ\85 Ø¥Ù\86شاؤÙ\87ا مؤخرًا.',
+    'shelves_new_empty' => 'ستظÙ\87ر Ù\87Ù\86ا Ø§Ù\84أرÙ\81Ù\81 Ø§Ù\84تÙ\8a Ø£Ù\86شئت مؤخرًا.',
     'shelves_save' => 'حفظ الرف',
     'shelves_books' => 'كتب على هذا الرف',
     'shelves_add_books' => 'إضافة كتب لهذا الرف',
-    'shelves_drag_books' => 'اسحب Ø§Ù\84Ù\83تب Ù\87Ù\86ا Ù\84إضاÙ\81تÙ\87ا Ù\84هذا الرف',
+    'shelves_drag_books' => 'اسحب Ø§Ù\84Ù\83تب Ù\87Ù\86ا Ù\84إضاÙ\81تÙ\87ا Ù\81Ù\8a هذا الرف',
     'shelves_empty_contents' => 'لا توجد كتب مخصصة لهذا الرف',
     'shelves_edit_and_assign' => 'تحرير الرف لإدراج كتب',
-    'shelves_edit_named' => 'تحرير رف الكتب: الاسم',
+    'shelves_edit_named' => 'تحرير رف الكتب :name',
     'shelves_edit' => 'تحرير رف الكتب',
     'shelves_delete' => 'حذف رف الكتب',
-    'shelves_delete_named' => 'حذف رف الكتب: الاسم',
-    'shelves_delete_explain' => "سيؤدي هذا إلى حذف رف الكتب مع الاسم ':المُسمى به'. لن يتم حذف الكتب المتضمنة.",
+    'shelves_delete_named' => 'حذف رف الكتب :name',
+    'shelves_delete_explain' => "سيؤدي هذا إلى حذف رف الكتب المسمى ':name'، ولن تحذف الكتب المتضمنة فيه.",
     'shelves_delete_confirmation' => 'هل أنت متأكد من أنك تريد حذف هذا الرف؟',
     'shelves_permissions' => 'أذونات رف الكتب',
     'shelves_permissions_updated' => 'تم تحديث أذونات رف الكتب',
@@ -98,7 +99,7 @@ return [
     'shelves_copy_permissions_to_books' => 'نسخ أذونات الوصول إلى الكتب',
     'shelves_copy_permissions' => 'نسخ الأذونات',
     'shelves_copy_permissions_explain' => 'سيؤدي هذا إلى تطبيق إعدادات الأذونات الحالية لهذا الرف على جميع الكتب المتضمنة فيه. قبل التفعيل، تأكد من حفظ أي تغييرات في أذونات هذا الرف.',
-    'shelves_copy_permission_success' => 'تم نسخ أذونات رف الكتب إلى: عد الكتب',
+    'shelves_copy_permission_success' => 'تم نسخ أذونات رف الكتب إلى :count books',
 
     // Books
     'book' => 'كتاب',
@@ -114,7 +115,7 @@ return [
     'books_create' => 'إنشاء كتاب جديد',
     'books_delete' => 'حذف الكتاب',
     'books_delete_named' => 'حذف كتاب :bookName',
-    'books_delete_explain' => 'سيتم حذف كتاب \':bookName\'. ستتم إزالة جميع الفصول والصفحات.',
+    'books_delete_explain' => 'سيتم حذف كتاب \':bookName\'، وأيضا حذف جميع الفصول والصفحات.',
     'books_delete_confirmation' => 'تأكيد حذف الكتاب؟',
     'books_edit' => 'تعديل الكتاب',
     'books_edit_named' => 'تعديل كتاب :bookName',
@@ -214,7 +215,7 @@ return [
     'pages_revisions_created_by' => 'أنشئ بواسطة',
     'pages_revisions_date' => 'تاريخ المراجعة',
     'pages_revisions_number' => '#',
-    'pages_revisions_numbered' => 'مراجعة #: رقم تعريفي',
+    'pages_revisions_numbered' => 'مراجعة #:id',
     'pages_revisions_numbered_changes' => 'مراجعة #: رقم تعريفي التغييرات',
     'pages_revisions_changelog' => 'سجل التعديل',
     'pages_revisions_changes' => 'التعديلات',
@@ -227,7 +228,7 @@ return [
     'pages_permissions_active' => 'أذونات الصفحة مفعلة',
     'pages_initial_revision' => 'نشر مبدئي',
     'pages_initial_name' => 'صفحة جديدة',
-    'pages_editing_draft_notification' => 'جار تعديل مسودة لم يتم حفظها من :timeDiff.',
+    'pages_editing_draft_notification' => 'جارٍ تعديل مسودة لم يتم حفظها من :timeDiff.',
     'pages_draft_edited_notification' => 'تم تحديث هذه الصفحة منذ ذلك الوقت. من الأفضل التخلص من هذه المسودة.',
     'pages_draft_edit_active' => [
         'start_a' => ':count من المستخدمين بدأوا بتعديل هذه الصفحة',
@@ -236,7 +237,7 @@ return [
         'time_b' => 'في آخر :minCount دقيقة/دقائق',
         'message' => 'وقت البدء: احرص على عدم الكتابة فوق تحديثات بعضنا البعض!',
     ],
-    'pages_draft_discarded' => 'تم التخلص من المسودة. تم تحديث المحرر بمحتوى الصفحة الحالي',
+    'pages_draft_discarded' => 'تم التخلص من المسودة وتحديث المحرر بمحتوى الصفحة الحالي',
     'pages_specific' => 'صفحة محددة',
     'pages_is_template' => 'قالب الصفحة',
 
@@ -254,14 +255,14 @@ return [
     'tags_remove' => 'إزالة هذه العلامة',
     'attachments' => 'المرفقات',
     'attachments_explain' => 'ارفع بعض الملفات أو أرفق بعض الروابط لعرضها بصفحتك. ستكون الملفات والروابط معروضة في الشريط الجانبي للصفحة.',
-    'attachments_explain_instant_save' => 'سÙ\8aتÙ\85 Ø­Ù\81ظ Ø§Ù\84تغÙ\8aÙ\8aرات Ù\87Ù\86ا Ø¨Ù\84حظتÙ\87ا',
+    'attachments_explain_instant_save' => 'سÙ\8aتÙ\85 Ø­Ù\81ظ Ø§Ù\84تغÙ\8aÙ\8aرات Ù\87Ù\86ا Ø¢Ù\86Ù\8aا.',
     'attachments_items' => 'العناصر المرفقة',
     'attachments_upload' => 'رفع ملف',
     'attachments_link' => 'إرفاق رابط',
     'attachments_set_link' => 'تحديد الرابط',
     'attachments_delete' => 'هل أنت متأكد من أنك تريد حذف هذا المرفق؟',
     'attachments_dropzone' => 'أسقط الملفات أو اضغط هنا لإرفاق ملف',
-    'attachments_no_files' => 'لم يتم رفع أي ملفات',
+    'attachments_no_files' => 'لم تُرفع أي ملفات',
     'attachments_explain_link' => 'بالإمكان إرفاق رابط في حال عدم تفضيل رفع ملف. قد يكون الرابط لصفحة أخرى أو لملف في أحد خدمات التخزين السحابي.',
     'attachments_link_name' => 'اسم الرابط',
     'attachment_link' => 'رابط المرفق',
@@ -286,7 +287,7 @@ return [
     'templates_prepend_content' => 'بادئة محتوى الصفحة',
 
     // Profile View
-    'profile_user_for_x' => 'المستخدم لـ : الوقت',
+    'profile_user_for_x' => 'المستخدم لـ :time',
     'profile_created_content' => 'المحتوى المنشأ',
     'profile_not_created_pages' => 'لم يتم إنشاء أي صفحات بواسطة :userName',
     'profile_not_created_chapters' => 'لم يتم إنشاء أي فصول بواسطة :userName',
@@ -298,7 +299,7 @@ return [
     'comments' => 'تعليقات',
     'comment_add' => 'إضافة تعليق',
     'comment_placeholder' => 'ضع تعليقاً هنا',
-    'comment_count' => '{0} ا توجد تعليقات|{1} تعليق واحد|{2} تعليقان|[3,*] :count تعليقات',
+    'comment_count' => '{0} لا توجد تعليقات|{1} تعليق واحد|{2} تعليقان[3,*] :count تعليقات',
     'comment_save' => 'حفظ التعليق',
     'comment_saving' => 'جار حفظ التعليق...',
     'comment_deleting' => 'جار حذف التعليق...',
@@ -312,8 +313,8 @@ return [
     'comment_in_reply_to' => 'رداً على :commentId',
 
     // Revision
-    'revision_delete_confirm' => 'هل أنت متأكد من أنك تريد حذف هذا الإصدار؟',
-    'revision_restore_confirm' => 'هل أنت متأكد من أنك تريد استعادة هذا الإصدار؟ سيتم استبدال محتوى الصفحة الحالية.',
-    'revision_delete_success' => 'تم حذف الإصدار',
-    'revision_cannot_delete_latest' => 'لايمكن حذف آخر إصدار.'
-];
\ No newline at end of file
+    'revision_delete_confirm' => 'هل أنت متأكد من أنك تريد حذف هذه المراجعة؟',
+    'revision_restore_confirm' => 'هل أنت متأكد من أنك تريد استعادة هذه المراجعة؟ سيتم استبدال محتوى الصفحة الحالية.',
+    'revision_delete_success' => 'تم حذف المراجعة',
+    'revision_cannot_delete_latest' => 'لايمكن حذف آخر مراجعة.'
+];
index 93e8201ab7599e3a11a40fb44d076461d07137b8..829571c585c921ad489a9e8a3f99a3690fd68b98 100644 (file)
@@ -83,6 +83,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.',
     'return_home' => 'العودة للصفحة الرئيسية',
     'error_occurred' => 'حدث خطأ',
     'app_down' => ':appName لا يعمل حالياً',
index f55451ff63e300db86b02dc9a8be4c44f77ad3c8..20789d842eacbe545e17572081a120542aa8127d 100755 (executable)
@@ -37,6 +37,11 @@ return [
     '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::<key>" 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' => 'تعطيل التعليقات',
     'app_disable_comments_toggle' => 'تعطيل التعليقات',
     'app_disable_comments_desc' => 'تعطيل التعليقات على جميع الصفحات داخل التطبيق. التعليقات الموجودة من الأصل لن تكون ظاهرة.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index d0b17460bccbb992c93f8c71a3895d4fdbe2fad3..3c03f2efd7269e68f72e8e5f96cc921b828abd30 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'يجب أن يكون :attribute على الأقل :min حرف / حروف.',
         'array'   => 'يجب أن يحتوي :attribute على :min عنصر / عناصر كحد أدنى.',
     ],
-    'no_double_extension'  => 'يجب أن يكون للسمة: امتداد ملف واحد فقط.',
     'not_in'               => ':attribute المحدد غير صالح.',
     'not_regex'            => 'صيغة السمة: غير صالحة.',
     'numeric'              => 'يجب أن يكون :attribute رقم.',
index 99930ddf45882083a2f4be3e32c264ea8736a173..f3a15563cd4200f328dcf356734081eab6e97aab 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Сортирай възходящо',
     'sort_descending' => 'Низходящо сортиране',
     'sort_name' => 'Име',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Дата на създаване',
     'sort_updated_at' => 'Дата на обновяване',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Трасиране',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Профил меню',
     'view_profile' => 'Разглеждане на профил',
     'edit_profile' => 'Редактиране на профила',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Информация',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Съдържание',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Ако имате проблеми с бутона ":actionText" по-горе, копирайте и поставете URL адреса по-долу в уеб браузъра си:',
     'email_rights' => 'Всички права запазени',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index 7f9cae6f6ca976426fc3b0d9137c0a1919a11fd1..2e35b5afa42fc8a5f617c56ce228ce48235c1230 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Задаване на права',
     'search_created_by_me' => 'Създадено от мен',
     'search_updated_by_me' => 'Обновено от мен',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Настройки на дати',
     'search_updated_before' => 'Обновено преди',
     'search_updated_after' => 'Обновено след',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Сигурни ли сте, че искате да изтриете тази версия? Настоящата страница ще бъде заместена.',
     'revision_delete_success' => 'Версията беше изтрита',
     'revision_cannot_delete_latest' => 'Не може да изтриете последната версия.'
-];
\ No newline at end of file
+];
index 4b062af06b557237fe37481dd0e6b004afb0f27b..ea6d497f06903aa7658c3163b95a02edfef51521 100644 (file)
@@ -83,6 +83,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.',
     'return_home' => 'Назад към Начало',
     'error_occurred' => 'Възникна грешка',
     'app_down' => ':appName не е достъпно в момента',
index dcfe44b3ab97d02f94e077cf255c3d445264eeea..5fb24056e05fd5011f09afe640a019df530cf019 100644 (file)
@@ -37,6 +37,11 @@ return [
     '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::<key>" 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. <br> Existing comments are not shown.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 578ea999fc31618997be7834012fb30aed3d7b1f..4031de2ae743b75bafd49195ff06b29a347cbf22 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'The :attribute must be at least :min characters.',
         'array'   => 'The :attribute must have at least :min items.',
     ],
-    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'The selected :attribute is invalid.',
     'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'The :attribute must be a number.',
diff --git a/resources/lang/bs/activities.php b/resources/lang/bs/activities.php
new file mode 100644 (file)
index 0000000..a9c7bfb
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
+return [
+
+    // Pages
+    'page_create'                 => 'je kreirao/la stranicu',
+    'page_create_notification'    => 'Stranica Uspješno Kreirana',
+    'page_update'                 => 'je ažurirao/la stranicu',
+    'page_update_notification'    => 'Stranica Uspješno Ažurirana',
+    'page_delete'                 => 'je izbrisao/la stranicu',
+    'page_delete_notification'    => 'Stranica Uspješno Izbrisana',
+    'page_restore'                => 'je vratio/la stranicu',
+    'page_restore_notification'   => 'Stranica Uspješno Vraćena',
+    'page_move'                   => 'je premjestio/la stranicu',
+
+    // Chapters
+    'chapter_create'              => 'je kreirao/la poglavlje',
+    'chapter_create_notification' => 'Poglavlje Uspješno Kreirano',
+    'chapter_update'              => 'je ažurirao/la poglavlje',
+    'chapter_update_notification' => 'Poglavlje Uspješno Ažurirano',
+    'chapter_delete'              => 'je izbrisao/la poglavlje',
+    'chapter_delete_notification' => 'Poglavlje Uspješno Izbrisano',
+    'chapter_move'                => 'je premjestio/la poglavlje',
+
+    // Books
+    'book_create'                 => 'je kreirao/la knjigu',
+    'book_create_notification'    => 'Knjiga Uspješno Kreirana',
+    'book_update'                 => 'je ažurirao/la knjigu',
+    'book_update_notification'    => 'Knjiga Uspješno Ažurirana',
+    'book_delete'                 => 'je izbrisao/la knjigu',
+    'book_delete_notification'    => 'Knjiga Uspješno Izbrisana',
+    'book_sort'                   => 'je sortirao/la knjigu',
+    'book_sort_notification'      => 'Knjiga Uspješno Ponovno Sortirana',
+
+    // Bookshelves
+    'bookshelf_create'            => 'je kreirao/la Policu za knjige',
+    'bookshelf_create_notification'    => 'Polica za knjige Uspješno Kreirana',
+    'bookshelf_update'                 => 'je ažurirao/la policu za knjige',
+    'bookshelf_update_notification'    => 'Polica za knjige Uspješno Ažurirana',
+    'bookshelf_delete'                 => 'je izbrisao/la policu za knjige',
+    'bookshelf_delete_notification'    => 'Polica za knjige Uspješno Izbrisana',
+
+    // Other
+    'commented_on'                => 'je komentarisao/la na',
+    'permissions_update'          => 'je ažurirao/la dozvole',
+];
diff --git a/resources/lang/bs/auth.php b/resources/lang/bs/auth.php
new file mode 100644 (file)
index 0000000..526a861
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
+return [
+
+    'failed' => 'Ovi pristupni podaci se ne slažu sa našom evidencijom.',
+    'throttle' => 'Preveliki broj pokušaja prijave. Molimo vas da pokušate ponovo za :seconds sekundi.',
+
+    // Login & Register
+    'sign_up' => 'Registruj se',
+    'log_in' => 'Prijavi se',
+    'log_in_with' => 'Prijavi se sa :socialDriver',
+    'sign_up_with' => 'Registruj se sa :socialDriver',
+    'logout' => 'Odjavi se',
+
+    'name' => 'Ime',
+    'username' => 'Korisničko ime',
+    'email' => 'E-mail',
+    'password' => 'Lozinka',
+    'password_confirm' => 'Potvrdi lozinku',
+    'password_hint' => 'Mora imati više od 7 karaktera',
+    'forgot_password' => 'Zaboravljena lozinka?',
+    'remember_me' => 'Zapamti me',
+    'ldap_email_hint' => 'Unesite e-mail koji će se koristiti za ovaj račun.',
+    'create_account' => 'Napravi račun',
+    'already_have_account' => 'Već imate račun?',
+    'dont_have_account' => 'Nemate korisnički račun?',
+    'social_login' => 'Prijava preko društvene mreže',
+    'social_registration' => 'Registracija pomoću društvene mreže',
+    'social_registration_text' => 'Registruj i prijavi se koristeći drugi servis.',
+
+    'register_thanks' => 'Hvala na registraciji!',
+    'register_confirm' => 'Provjerite vašu e-mail adresu i pritisnite dugme za potvrdu da bi dobili pristup :appName.',
+    'registrations_disabled' => 'Registracije su trenutno onemogućene',
+    'registration_email_domain_invalid' => 'Ta e-mail domena nema pristup ovoj aplikaciji',
+    'register_success' => 'Hvala na registraciji! Sada ste registrovani i prijavljeni.',
+
+
+    // Password Reset
+    'reset_password' => 'Resetuj Lozinku',
+    'reset_password_send_instructions' => 'Unesite vašu e-mail adresu ispod i na nju ćemo vam poslati e-mail sa linkom za promjenu lozinke.',
+    'reset_password_send_button' => 'Pošalji link za promjenu',
+    'reset_password_sent' => 'Link za promjenu lozinke će biti poslan na :email ako ta adresa postoji u sistemu.',
+    'reset_password_success' => 'Vaša lozinka je uspješno promijenjena.',
+    'email_reset_subject' => 'Resetujte vašu lozinku od :appName',
+    'email_reset_text' => 'Primate ovaj e-mail jer smo dobili zahtjev za promjenu lozinke za vaš račun.',
+    'email_reset_not_requested' => 'Ako niste zahtijevali promjenu lozinke ne trebate ništa više uraditi.',
+
+
+    // Email Confirmation
+    'email_confirm_subject' => 'Potvrdite vaš e-mail na :appName',
+    'email_confirm_greeting' => 'Hvala na pristupanju :appName!',
+    'email_confirm_text' => 'Potvrdite vašu e-mail adresu pritiskom na dugme ispod:',
+    'email_confirm_action' => 'Potvrdi e-mail',
+    'email_confirm_send_error' => 'Potvrda e-maila je obavezna ali sistem nije mogao poslati e-mail. Kontaktirajte administratora da biste bili sigurni da je e-mail postavljen ispravno.',
+    'email_confirm_success' => 'Vaš e-mail je potvrđen!',
+    'email_confirm_resent' => 'E-mail za potvrdu je ponovno poslan. Provjerite vaš e-mail.',
+
+    'email_not_confirmed' => 'E-mail adresa nije potvrđena',
+    'email_not_confirmed_text' => 'Vaša e-mail adresa nije još potvrđena.',
+    'email_not_confirmed_click_link' => 'Kliknite na link u e-mailu koji vam je poslan nakon što ste se registrovali.',
+    'email_not_confirmed_resend' => 'Ako ne možete naći e-mail možete ponovno poslati e-mail za potvrdu tako što ćete ispuniti formu ispod.',
+    'email_not_confirmed_resend_button' => 'Ponovno pošalji e-mail za potvrdu',
+
+    // User Invite
+    'user_invite_email_subject' => 'Pozvani ste da se pridružite :appName!',
+    'user_invite_email_greeting' => 'Račun je napravljen za vas na :appName.',
+    'user_invite_email_text' => 'Pritisnite dugme ispod da niste postavili lozinku vašeg računa i tako dobili pristup:',
+    'user_invite_email_action' => 'Postavi lozinku računa',
+    'user_invite_page_welcome' => 'Dobrodošli na :appName!',
+    'user_invite_page_text' => 'Da biste završili vaš račun i dobili pristup morate postaviti lozinku koju ćete koristiti da se prijavite na :appName tokom budućih posjeta.',
+    'user_invite_page_confirm_button' => 'Potvrdi lozinku',
+    'user_invite_success' => 'Lozinka postavljena, sada imate pristup :sppName!'
+];
\ No newline at end of file
diff --git a/resources/lang/bs/common.php b/resources/lang/bs/common.php
new file mode 100644 (file)
index 0000000..41d53f8
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
+return [
+
+    // Buttons
+    'cancel' => 'Otkaži',
+    'confirm' => 'Potvrdi',
+    'back' => 'Nazad',
+    'save' => 'Spremi',
+    'continue' => 'Nastavi',
+    'select' => 'Odaberi',
+    'toggle_all' => 'Prebaci sve',
+    'more' => 'Više',
+
+    // Form Labels
+    'name' => 'Ime',
+    'description' => 'Opis',
+    'role' => 'Uloga',
+    'cover_image' => 'Naslovna slika',
+    'cover_image_description' => 'Ova slika treba biti približno 440x250px.',
+    
+    // Actions
+    'actions' => 'Akcije',
+    'view' => 'Prikaz',
+    'view_all' => 'Prikaži sve',
+    'create' => 'Kreiraj',
+    'update' => 'Ažuriraj',
+    'edit' => 'Uredi',
+    'sort' => 'Sortiraj',
+    'move' => 'Pomjeri',
+    'copy' => 'Kopiraj',
+    'reply' => 'Odgovori',
+    'delete' => 'Izbriši',
+    'delete_confirm' => 'Potvrdi brisanje',
+    'search' => 'Traži',
+    'search_clear' => 'Očisti pretragu',
+    'reset' => 'Resetuj',
+    'remove' => 'Ukloni',
+    'add' => 'Dodaj',
+    'fullscreen' => 'Prikaz preko čitavog ekrana',
+
+    // Sort Options
+    'sort_options' => 'Opcije sortiranja',
+    'sort_direction_toggle' => 'Prebacivanje smjera sortiranja',
+    'sort_ascending' => 'Sortiraj uzlazno',
+    'sort_descending' => 'Sortiraj silazno',
+    'sort_name' => 'Ime',
+    'sort_default' => 'Default',
+    'sort_created_at' => 'Datum kreiranja',
+    'sort_updated_at' => 'Datum ažuriranja',
+
+    // Misc
+    'deleted_user' => 'Obrisani korisnik',
+    'no_activity' => 'Nema aktivnosti za prikazivanje',
+    'no_items' => 'Nema dostupnih stavki',
+    'back_to_top' => 'Povratak na vrh',
+    'toggle_details' => 'Vidi detalje',
+    'toggle_thumbnails' => 'Vidi prikaze slika',
+    'details' => 'Detalji',
+    'grid_view' => 'Prikaz rešetke',
+    'list_view' => 'Prikaz liste',
+    'default' => 'Početne postavke',
+    'breadcrumb' => 'Navigacijske stavke',
+
+    // Header
+    'header_menu_expand' => 'Expand Header Menu',
+    'profile_menu' => 'Meni profila',
+    'view_profile' => 'Pogledaj profil',
+    'edit_profile' => 'Izmjeni profil',
+    'dark_mode' => 'Tamni način rada',
+    'light_mode' => 'Svijetli način rada',
+
+    // Layout tabs
+    'tab_info' => 'Informacije',
+    'tab_info_label' => 'Tab: Show Secondary Information',
+    'tab_content' => 'Sadržaj',
+    'tab_content_label' => 'Tab: Show Primary Content',
+
+    // Email Content
+    'email_action_help' => 'Ukoliko imate poteškoća sa pritiskom na ":actionText" dugme, kopirajte i zaljepite URL koji se nalazi ispod u vaš web pretraživač:',
+    'email_rights' => 'Sva prava pridržana',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Pravila o privatnosti',
+    'terms_of_service' => 'Uslovi korištenja',
+];
diff --git a/resources/lang/bs/components.php b/resources/lang/bs/components.php
new file mode 100644 (file)
index 0000000..d40a95a
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
+return [
+
+    // Image Manager
+    'image_select' => 'Biraj sliku',
+    'image_all' => 'Sve',
+    'image_all_title' => 'Pogledaj sve slike',
+    'image_book_title' => 'Pogledaj slike prenesene u ovu knjigu',
+    'image_page_title' => 'Pogledaj slike prenesene na ovu stranicu',
+    'image_search_hint' => 'Traži po nazivu slike',
+    'image_uploaded' => 'Preneseno :uploadedDate',
+    'image_load_more' => 'Učitaj još',
+    'image_image_name' => 'Naziv slike',
+    'image_delete_used' => 'Ova slika se koristi na stranicama prikazanim ispod.',
+    'image_delete_confirm_text' => 'Jeste li sigurni da želite obrisati ovu sliku?',
+    'image_select_image' => 'Odaberi sliku',
+    'image_dropzone' => 'Ostavi slike ili pritisnite ovdje da ih prenesete',
+    'images_deleted' => 'Slike su izbrisane',
+    'image_preview' => 'Pregled Slike',
+    'image_upload_success' => 'Slika uspješno učitana',
+    'image_update_success' => 'Detalji slike uspješno ažurirani',
+    'image_delete_success' => 'Slika uspješno izbrisana',
+    'image_upload_remove' => 'Ukloni',
+
+    // Code Editor
+    'code_editor' => 'Uredi Kod',
+    'code_language' => 'Jezik koda',
+    'code_content' => 'Sadržaj Koda',
+    'code_session_history' => 'Historija Sesije',
+    'code_save' => 'Snimi Kod',
+];
diff --git a/resources/lang/bs/entities.php b/resources/lang/bs/entities.php
new file mode 100644 (file)
index 0000000..1e5350f
--- /dev/null
@@ -0,0 +1,320 @@
+<?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
+return [
+
+    // Shared
+    'recently_created' => 'Nedavno napravljen',
+    'recently_created_pages' => 'Nedavno napravljene stranice',
+    'recently_updated_pages' => 'Nedavno ažurirane stranice',
+    'recently_created_chapters' => 'Nedavno napravljena poglavlja',
+    'recently_created_books' => 'Nedavno napravljene knjige',
+    'recently_created_shelves' => 'Nedavno napravljene police',
+    'recently_update' => 'Nedavno ažurirana',
+    'recently_viewed' => 'Nedavno pogledana',
+    'recent_activity' => 'Nedavna aktivnost',
+    'create_now' => 'Napravi jednu sada',
+    'revisions' => 'Promjene',
+    'meta_revision' => 'Promjena #:revisionCount',
+    'meta_created' => 'Napravljena :timeLength',
+    'meta_created_name' => 'Napravljena :timeLength od :user',
+    'meta_updated' => 'Ažurirana :timeLength',
+    'meta_updated_name' => 'Ažurirana :timeLength od :user',
+    'meta_owned_name' => 'Vlasnik je :user',
+    'entity_select' => 'Odaberi entitet',
+    'images' => 'Slike',
+    'my_recent_drafts' => 'Moje nedavne skice',
+    'my_recently_viewed' => 'Moji nedavni pregledi',
+    'no_pages_viewed' => 'Niste pogledali nijednu stranicu',
+    'no_pages_recently_created' => 'Nijedna stranica nije napravljena nedavno',
+    'no_pages_recently_updated' => 'Niijedna stranica nije ažurirana nedavno',
+    'export' => 'Izvezi',
+    'export_html' => 'Sadržani web fajl',
+    'export_pdf' => 'PDF fajl',
+    'export_text' => 'Plain Text fajl',
+
+    // Permissions and restrictions
+    'permissions' => 'Dozvole',
+    'permissions_intro' => 'Jednom omogućene, ove dozvole imaju prednost nad dozvolama uloge.',
+    'permissions_enable' => 'Omogući prilagođena dopuštenja',
+    'permissions_save' => 'Snimi dozvole',
+    'permissions_owner' => 'Vlasnik',
+
+    // Search
+    'search_results' => 'Rezultati pretrage',
+    'search_total_results_found' => ':count rezultata je nađeno|:count ukupno rezultata je nađeno',
+    'search_clear' => 'Očisti pretragu',
+    'search_no_pages' => 'Nijedna stranica nije nađena',
+    'search_for_term' => 'Traži :term',
+    'search_more' => 'Više rezultata',
+    'search_advanced' => 'Napredna pretraga',
+    'search_terms' => 'Pojmovi za pretragu',
+    'search_content_type' => 'Vrsta sadržaja',
+    'search_exact_matches' => 'Tačna podudaranja',
+    'search_tags' => 'Pretraga oznaka',
+    'search_options' => 'Opcije',
+    'search_viewed_by_me' => 'Ja sam pogledao/la',
+    'search_not_viewed_by_me' => 'Nisam pogledao/la',
+    'search_permissions_set' => 'Dozvole',
+    'search_created_by_me' => 'Ja sam napravio/la',
+    'search_updated_by_me' => 'Ja sam ažurirao/la',
+    'search_owned_by_me' => 'Owned by me',
+    'search_date_options' => 'Opcije datuma',
+    'search_updated_before' => 'Ažurirano prije',
+    'search_updated_after' => 'Ažurirano nakon',
+    'search_created_before' => 'Kreirano prije',
+    'search_created_after' => 'Kreirano nakon',
+    'search_set_date' => 'Postavi datum',
+    'search_update' => 'Ažuriraj pretragu',
+
+    // Shelves
+    'shelf' => 'Polica',
+    'shelves' => 'Police',
+    'x_shelves' => ':count Polica|:count Police',
+    'shelves_long' => 'Police za knjige',
+    'shelves_empty' => 'Niti jedna polica nije kreirana',
+    'shelves_create' => 'Kreiraj novu policu',
+    'shelves_popular' => 'Popularne police',
+    'shelves_new' => 'Nove police',
+    'shelves_new_action' => 'Nova polica',
+    'shelves_popular_empty' => 'Najpopularnije police će se pojaviti ovdje.',
+    'shelves_new_empty' => 'Najnovije police će se pojaviti ovdje.',
+    'shelves_save' => 'Spremi policu',
+    'shelves_books' => 'Knjige na ovoj polici',
+    'shelves_add_books' => 'Dodaj knjige na ovu policu',
+    'shelves_drag_books' => 'Prenesi knjige ovdje da bi ih dodao/la na ovu policu',
+    'shelves_empty_contents' => 'Ova polica nema knjiga koje su postavljene na nju',
+    'shelves_edit_and_assign' => 'Uredi policu da bi dodao/la knjige',
+    'shelves_edit_named' => 'Uredi :name police za knjige',
+    'shelves_edit' => 'Uredi policu za knjige',
+    'shelves_delete' => 'Izbriši policu za knjige',
+    'shelves_delete_named' => 'Izbriši policu za knjige :name',
+    'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
+    'shelves_delete_confirmation' => 'Are you sure you want to delete this bookshelf?',
+    'shelves_permissions' => 'Bookshelf Permissions',
+    'shelves_permissions_updated' => 'Bookshelf Permissions Updated',
+    'shelves_permissions_active' => 'Bookshelf Permissions Active',
+    '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 bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
+    'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
+
+    // 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' => 'Ovo će izbrisati knjigu naziva \':bookName\'. Sve stranice i poglavlja će biti uklonjene.',
+    'books_delete_confirmation' => 'Jeste li sigurni da želite izbrisati ovu knjigu?',
+    'books_edit' => 'Uredi knjigu',
+    'books_edit_named' => 'Uredi knjigu :bookName',
+    'books_form_book_name' => 'Naziv knjige',
+    'books_save' => 'Spremi knjigu',
+    'books_permissions' => 'Dozvole knjige',
+    'books_permissions_updated' => 'Dozvole knjige su ažurirane',
+    'books_empty_contents' => 'Za ovu knjigu nisu napravljene ni stranice ni poglavlja.',
+    'books_empty_create_page' => 'Napravi novu stranicu',
+    'books_empty_sort_current_book' => 'Sortiraj trenutnu knjigu',
+    'books_empty_add_chapter' => 'Dodaj poglavlje',
+    'books_permissions_active' => 'Dozvole za knjigu su aktivne',
+    'books_search_this' => 'Pretraži ovu knjigu',
+    'books_navigation' => 'Navigacija knjige',
+    'books_sort' => 'Sortiraj sadržaj knjige',
+    'books_sort_named' => 'Sortiraj knjigu :bookName',
+    'books_sort_name' => 'Sortiraj po imenu',
+    'books_sort_created' => 'Sortiraj po datumu kreiranja',
+    'books_sort_updated' => 'Sortiraj po datumu ažuriranja',
+    'books_sort_chapters_first' => 'Poglavlja prva',
+    'books_sort_chapters_last' => 'Poglavlja zadnja',
+    'books_sort_show_other' => 'Prikaži druge knjige',
+    'books_sort_save' => 'Spremi trenutni poredak',
+
+    // Chapters
+    'chapter' => 'Poglavlje',
+    'chapters' => 'Poglavlja',
+    'x_chapters' => ':count Poglavlje|:count Poglavlja',
+    'chapters_popular' => 'Popularna poglavlja',
+    'chapters_new' => 'Novo poglavlje',
+    'chapters_create' => 'Napravi novo poglavlje',
+    'chapters_delete' => 'Izbriši poglavlje',
+    'chapters_delete_named' => 'Izbriši poglavlje :chapterName',
+    'chapters_delete_explain' => 'Ovo će izbrisati poglavlje naziva \':chapterName\'. Sve stranice koje postoje u ovom poglavlju će također biti izbrisane.',
+    'chapters_delete_confirm' => 'Jeste li sigurni da želite izbrisati ovo poglavlje?',
+    'chapters_edit' => 'Uredi poglavlje',
+    'chapters_edit_named' => 'Uredi poglavlje :chapterName',
+    'chapters_save' => 'Spremi poglavlje',
+    'chapters_move' => 'Premjesti poglavlje',
+    'chapters_move_named' => 'Premjesti poglavlje :chapterName',
+    'chapter_move_success' => 'Poglavlje premješteno u :bookName',
+    'chapters_permissions' => 'Dozvole poglavlja',
+    'chapters_empty' => 'U ovom poglavlju trenutno nema stranica.',
+    'chapters_permissions_active' => 'Dozvole za poglavlje su aktivne',
+    'chapters_permissions_success' => 'Dozvole za poglavlje su ažurirane',
+    'chapters_search_this' => 'Pretražuj ovo poglavlje',
+
+    // Pages
+    'page' => 'Stranica',
+    'pages' => 'Stranice',
+    'x_pages' => ':count Stranica|:count Stranice',
+    'pages_popular' => 'Popularne stranice',
+    'pages_new' => 'Nova stranica',
+    '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_discard_draft' => 'Discard Draft',
+    '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_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_not_in_chapter' => 'Page is not in a chapter',
+    'pages_move' => 'Move Page',
+    'pages_move_success' => 'Page moved to ":parentName"',
+    '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_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',
+    'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revision #:id',
+    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
+    'pages_revisions_changelog' => 'Changelog',
+    'pages_revisions_changes' => 'Changes',
+    'pages_revisions_current' => 'Trenutna verzija',
+    'pages_revisions_preview' => 'Pregled',
+    'pages_revisions_restore' => 'Vrati',
+    'pages_revisions_none' => 'Ova stranica nema promjena',
+    'pages_copy_link' => 'Iskopiraj link',
+    'pages_edit_content_link' => 'Uredi sadržaj',
+    'pages_permissions_active' => 'Dozvole za stranicu su aktivne',
+    'pages_initial_revision' => 'Prvo izdavanje',
+    'pages_initial_name' => 'Nova stranica',
+    'pages_editing_draft_notification' => 'Trenutno uređujete skicu koja je posljednji put snimljena :timeDiff.',
+    'pages_draft_edited_notification' => 'Ova stranica je ažurirana nakon tog vremena. Preporučujemo da odbacite ovu skicu.',
+    'pages_draft_edit_active' => [
+        'start_a' => ':count korisnika je počelo sa uređivanjem ove stranice',
+        'start_b' => ':userName je počeo/la sa uređivanjem ove stranice',
+        'time_a' => 'od kada je stranica posljednji put ažurirana',
+        'time_b' => 'u posljednjih :minCount minuta',
+        'message' => ':start :time. Pazite da jedni drugima ne prepišete promjene!',
+    ],
+    'pages_draft_discarded' => 'Skica je odbačena, uređivač je ažuriran sa trenutnim sadržajem stranice',
+    'pages_specific' => 'Specifična stranica',
+    'pages_is_template' => 'Predložak stranice',
+
+    // Editor Sidebar
+    'page_tags' => 'Oznake stranice',
+    'chapter_tags' => 'Oznake poglavlja',
+    'book_tags' => 'Oznake knjige',
+    'shelf_tags' => 'Oznake police',
+    'tag' => 'Oznaka',
+    'tags' =>  'Oznake',
+    'tag_name' =>  'Naziv oznake',
+    'tag_value' => 'Vrijednost oznake (nije obavezno)',
+    'tags_explain' => "Dodaj nekoliko oznaka da bi sadržaj bio bolje kategorisan. \n Možeš dodati vrijednost oznaci za dublju organizaciju.",
+    'tags_add' => 'Dodaj još jednu oznaku',
+    'tags_remove' => 'Ukloni ovu oznaku',
+    'attachments' => 'Prilozi',
+    'attachments_explain' => 'Učitajte fajlove ili priložite poveznice da bi ih prikazali na stranici. Oni su onda vidljivi u navigaciji sa strane.',
+    'attachments_explain_instant_save' => 'Sve promjene se snimaju odmah.',
+    'attachments_items' => 'Priložene stavke',
+    'attachments_upload' => 'Učitaj fajl',
+    'attachments_link' => 'Zakači link',
+    'attachments_set_link' => 'Postavi link',
+    'attachments_delete' => 'Jeste li sigurni da želite obrisati ovaj prilog?',
+    'attachments_dropzone' => 'Spustite fajlove ili pritisnite ovdje da priložite fajl',
+    'attachments_no_files' => 'Niti jedan fajl nije prenesen',
+    'attachments_explain_link' => 'Možete zakačiti link ako ne želite učitati fajl. To može biti link druge stranice ili link za fajl u oblaku.',
+    'attachments_link_name' => 'Naziv linka',
+    'attachment_link' => 'Link poveznice',
+    'attachments_link_url' => 'Link do fajla',
+    'attachments_link_url_hint' => 'Url stranice ili fajla',
+    'attach' => 'Zakači',
+    'attachments_insert_link' => 'Dodaj priloženi link na stranicu',
+    'attachments_edit_file' => 'Uredi fajl',
+    'attachments_edit_file_name' => 'Naziv fajla',
+    'attachments_edit_drop_upload' => 'Spusti fajlove ili pritisni ovdje da učitaš i prepišeš',
+    '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',
+
+    // 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',
+
+    // 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_saving' => 'Saving comment...',
+    'comment_deleting' => 'Deleting comment...',
+    'comment_new' => 'New Comment',
+    'comment_created' => 'commented :createDiff',
+    'comment_updated' => 'Updated :updateDiff by :username',
+    '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',
+
+    // 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_delete_success' => 'Revision deleted',
+    'revision_cannot_delete_latest' => 'Cannot delete the latest revision.'
+];
diff --git a/resources/lang/bs/errors.php b/resources/lang/bs/errors.php
new file mode 100644 (file)
index 0000000..f369606
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Text shown in error messaging.
+ */
+return [
+
+    // Permissions
+    'permission' => 'Nemate ovlaštenje da pristupite ovoj stranici.',
+    'permissionJson' => 'Nemate ovlaštenje da izvršite tu akciju.',
+
+    // Auth
+    'error_user_exists_different_creds' => 'Korisnik sa e-mailom :email već postoji ali sa različitim podacima.',
+    'email_already_confirmed' => 'E-mail je već potvrđen, pokušajte se prijaviti.',
+    'email_confirmation_invalid' => 'Ovaj token za potvrdu nije ispravan ili je već iskorišten, molimo vas pokušajte se registrovati ponovno.',
+    'email_confirmation_expired' => 'Ovaj token za potvrdu je istekao, novi e-mail za potvrdu je poslan.',
+    'email_confirmation_awaiting' => 'E-mail adresa za račun koji se koristi mora biti potvrđena',
+    'ldap_fail_anonymous' => 'LDAP pristup nije uspio koristeći anonimno povezivanje',
+    'ldap_fail_authed' => 'LDAP pristup nije uspio koristeći date detalje lozinke i dn',
+    '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',
+    'social_no_action_defined' => 'Nema definisane akcije',
+    'social_login_bad_response' => "Došlo je do greške prilikom prijave preko :socialAccount :\n:error",
+    'social_account_in_use' => 'Ovaj :socialAccount račun se već koristi, pokušajte se prijaviti putem :socialAccount opcije.',
+    'social_account_email_in_use' => 'E-mail :email se već koristi. Ako već imate račun možete povezati vaš :socialAccount račun u postavkama profila.',
+    'social_account_existing' => 'Ovaj :socialAccount je već povezan sa vašim profilom.',
+    'social_account_already_used_existing' => 'Drugi korisnik već koristi ovaj :socialAccount.',
+    'social_account_not_used' => 'Ovaj :socialAccount nije povezan ni sa jednim korisnikom. Povežite ga u postavkama profila. ',
+    'social_account_register_instructions' => 'Ako još uvijek nemate račun, možete se registrovati koristeći :socialAccount opciju.',
+    'social_driver_not_found' => 'Driver društvene mreže nije pronađen',
+    'social_driver_not_configured' => 'Vaše :socialAccount postavke nisu konfigurisane ispravno.',
+    'invite_token_expired' => 'Pozivni link je istekao. Možete umjesto toga pokušati da resetujete lozinku.',
+
+    // System
+    'path_not_writable' => 'Na putanju fajla :filePath se ne može učitati. Potvrdite da je omogućeno pisanje na server.',
+    'cannot_get_image_from_url' => 'Nije moguće dobiti sliku sa :url',
+    'cannot_create_thumbs' => 'Server ne može kreirati sličice. Provjerite da imate instaliranu GD PHP ekstenziju.',
+    'server_upload_limit' => 'Server ne dopušta učitavanja ove veličine. Pokušajte sa manjom veličinom fajla.',
+    'uploaded'  => 'Server ne dopušta učitavanja ove veličine. Pokušajte sa manjom veličinom fajla.',
+    'image_upload_error' => 'Desila se greška prilikom učitavanja slike',
+    'image_upload_type_error' => 'Vrsta slike koja se učitava je neispravna',
+    'file_upload_timeout' => 'Vrijeme učitavanja fajla je isteklo.',
+
+    // Attachments
+    'attachment_not_found' => 'Prilog nije pronađen',
+
+    // Pages
+    'page_draft_autosave_fail' => 'Snimanje skice nije uspjelo. Provjerite da ste povezani na internet prije snimanja ove stranice',
+    'page_custom_home_deletion' => 'Stranicu nije moguće izbrisati dok se koristi kao početna stranica',
+
+    // Entities
+    'entity_not_found' => 'Entitet nije pronađen',
+    'bookshelf_not_found' => 'Polica za knjige nije pronađena',
+    'book_not_found' => 'Knjiga nije pronađena',
+    'page_not_found' => 'Stranica nije pronađena',
+    'chapter_not_found' => 'Poglavlje nije pronađeno',
+    'selected_book_not_found' => 'Odabrana knjiga nije pronađena',
+    'selected_book_chapter_not_found' => 'Odabrana knjiga ili poglavlje nije pronađeno',
+    'guests_cannot_save_drafts' => 'Gosti ne mogu snimati skice',
+
+    // Users
+    'users_cannot_delete_only_admin' => 'Ne možete izbrisati jedinog administratora',
+    'users_cannot_delete_guest' => 'Ne možete izbrisati gost korisnika',
+
+    // Roles
+    'role_cannot_be_edited' => 'Ova uloga ne može biti mijenjana',
+    'role_system_cannot_be_deleted' => 'Ova uloga je sistemska uloga i ne može biti izbrisana',
+    'role_registration_default_cannot_delete' => 'Ova uloga ne može biti izbrisana dok je postavljena kao osnovna registracijska uloga',
+    'role_cannot_remove_only_admin' => 'Ovaj korisnik je jedini korisnik sa ulogom administratora. Postavite ulogu administratora drugom korisniku prije nego je uklonite ovdje.',
+
+    // Comments
+    'comment_list' => 'Desila se greška prilikom dobavljanja komentara.',
+    'cannot_add_comment_to_draft' => 'Ne možete dodati komentare na skicu.',
+    'comment_add' => 'Desila se greška prilikom dodavanja / ažuriranja komentara.',
+    'comment_delete' => 'Desila se greška prilikom brisanja komentara.',
+    'empty_comment' => 'Nemoguće dodati prazan komentar.',
+
+    // Error pages
+    '404_page_not_found' => 'Stranica nije pronađena',
+    'sorry_page_not_found' => 'Stranica koju ste tražili nije pronađena.',
+    'sorry_page_not_found_permission_warning' => 'Ako ste očekivali da ova stranica postoji, možda nemate privilegije da joj pristupite.',
+    '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' => 'Nazad na početnu stranu',
+    'error_occurred' => 'Desila se greška',
+    'app_down' => ':appName trenutno nije u funkciji',
+    'back_soon' => 'Biti će uskoro u funkciji.',
+
+    // API errors
+    'api_no_authorization_found' => 'Na zahtjevu nije pronađen token za autorizaciju',
+    'api_bad_authorization_format' => 'Token za autorizaciju je pronađen u zahtjevu ali je format neispravan',
+    'api_user_token_not_found' => 'Nije pronađen odgovarajući API token za pruženi token autorizacije',
+    'api_incorrect_token_secret' => 'Tajni ključ naveden za dati korišteni API token nije tačan',
+    'api_user_no_api_permission' => 'Vlasnik korištenog API tokena nema dozvolu za upućivanje API poziva',
+    'api_user_token_expired' => 'Autorizacijski token je istekao',
+
+    // Settings & Maintenance
+    'maintenance_test_email_failure' => 'Došlo je do greške prilikom slanja testnog e-maila:',
+
+];
diff --git a/resources/lang/bs/pagination.php b/resources/lang/bs/pagination.php
new file mode 100644 (file)
index 0000000..a8d1417
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
+return [
+
+    'previous' => '&laquo; Prethodna',
+    'next'     => 'Sljedeća &raquo;',
+
+];
diff --git a/resources/lang/bs/passwords.php b/resources/lang/bs/passwords.php
new file mode 100644 (file)
index 0000000..0383e8a
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
+return [
+
+    'password' => 'Lozinke moraju sadržavati najmanje osam karaktera i podudarati se sa potvrdom lozinke.',
+    'user' => "Ne možemo naći korisnika sa tom e-mail adresom.",
+    'token' => 'Token za poništavanje lozinke nije validan za ovu e-mail adresu.',
+    'sent' => 'Poslali smo link za poništavanje vaše lozinke na e-mail!',
+    'reset' => 'Vaša lozinka je resetovana!',
+
+];
diff --git a/resources/lang/bs/settings.php b/resources/lang/bs/settings.php
new file mode 100644 (file)
index 0000000..ab7f153
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
+return [
+
+    // Common Messages
+    'settings' => 'Settings',
+    'settings_save' => 'Save Settings',
+    'settings_save_success' => 'Settings saved',
+
+    // 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_editor' => 'Page Editor',
+    'app_editor_desc' => 'Select which editor will be used by all users to edit pages.',
+    'app_custom_html' => 'Custom HTML Head Content',
+    'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> 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 image should be 43px in height. <br>Large images will be scaled down.',
+    'app_primary_color' => 'Application Primary Color',
+    'app_primary_color_desc' => 'Sets the primary color for the application including the banner, buttons, and links.',
+    '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::<key>" 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. <br> Existing comments are not shown.',
+
+    // Color settings
+    'content_colors' => 'Content Colors',
+    'content_colors_desc' => 'Sets 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',
+
+    // 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. <br> Note that users will be able to change their email addresses after successful registration.',
+    'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
+
+    // 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',
+
+    // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
+    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+
+    // 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_date' => 'Activity Date',
+    'audit_date_from' => 'Date Range From',
+    'audit_date_to' => 'Date Range To',
+
+    // Role Settings
+    'roles' => 'Roles',
+    'role_user_roles' => 'User Roles',
+    'role_create' => 'Create New Role',
+    'role_create_success' => 'Role successfully created',
+    '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_delete_success' => 'Role successfully deleted',
+    'role_edit' => 'Edit Role',
+    'role_details' => 'Role Details',
+    'role_name' => 'Role Name',
+    'role_desc' => 'Short Description of Role',
+    '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_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_all' => 'All',
+    'role_own' => 'Own',
+    'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
+    'role_save' => 'Save Role',
+    'role_update_success' => 'Role successfully updated',
+    'role_users' => 'Users in this role',
+    'role_users_none' => 'No users are currently assigned to this role',
+
+    // Users
+    'users' => 'Users',
+    '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 6 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' => 'This is the ID used to match this user when communicating with your external authentication system.',
+    'users_password_warning' => 'Only fill the below if you would like to change your password.',
+    '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_delete_success' => 'User successfully removed',
+    'users_edit' => 'Edit User',
+    'users_edit_profile' => 'Edit Profile',
+    'users_edit_success' => 'User successfully updated',
+    '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_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_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_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',
+
+    // 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_create_success' => 'API token successfully created',
+    'user_api_token_update_success' => 'API token successfully updated',
+    '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_delete_success' => 'API token successfully deleted',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
+        'cs' => 'Česky',
+        'da' => 'Dansk',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'he' => 'עברית',
+        'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
+        'it' => 'Italian',
+        'ja' => '日本語',
+        'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
+        'nl' => 'Nederlands',
+        'nb' => 'Norsk (Bokmål)',
+        'pl' => 'Polski',
+        'pt' => 'Português',
+        'pt_BR' => 'Português do Brasil',
+        'ru' => 'Русский',
+        'sk' => 'Slovensky',
+        'sl' => 'Slovenščina',
+        'sv' => 'Svenska',
+        'tr' => 'Türkçe',
+        'uk' => 'Українська',
+        'vi' => 'Tiếng Việt',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+    ]
+    //!////////////////////////////////
+];
diff --git a/resources/lang/bs/validation.php b/resources/lang/bs/validation.php
new file mode 100644 (file)
index 0000000..f3af075
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
+return [
+
+    // Standard laravel validation lines
+    'accepted'             => ':attribute mora biti prihvaćen.',
+    'active_url'           => ':attribute nije ispravan URL.',
+    'after'                => ':attribute mora biti datum nakon :date.',
+    'alpha'                => ':attribute može sadržavati samo slova.',
+    'alpha_dash'           => ':attribute može sadržavati samo slova, brojeve, crtice i donje crtice.',
+    'alpha_num'            => ':attribute može sadržavati samo slova i brojeve.',
+    'array'                => ':attribute mora biti niz.',
+    'before'               => ':attribute mora biti datum prije :date.',
+    'between'              => [
+        'numeric' => ':attribute mora biti između :min i :max.',
+        'file'    => ':attribute mora biti između :min i :max kilobajta.',
+        'string'  => ':attribute mora biti između :min i :max karaktera.',
+        'array'   => ':attribute mora imati između :min i :max stavki.',
+    ],
+    'boolean'              => ':attribute polje mora biti tačno ili netačno.',
+    'confirmed'            => ':attribute potvrda se ne slaže.',
+    'date'                 => ':attribute nije ispravan datum.',
+    'date_format'          => ':attribute ne odgovara formatu :format.',
+    'different'            => ':attribute i :other moraju biti različiti.',
+    'digits'               => ':attribute mora imati :digits brojeve.',
+    'digits_between'       => ':attribute mora imati između :min i :max brojeva.',
+    'email'                => ':attribute mora biti ispravna e-mail adresa.',
+    'ends_with' => ':attribute mora završavati sa jednom od sljedećih: :values',
+    'filled'               => 'Polje :attribute je obavezno.',
+    'gt'                   => [
+        'numeric' => ':attribute mora biti veći od :value.',
+        'file'    => ':attribute mota biti veći od :value kilobajta.',
+        'string'  => ':attribute mora imati više od :value karaktera.',
+        'array'   => ':attribute mora imati više od :value stavki.',
+    ],
+    'gte'                  => [
+        'numeric' => ':attribute mora biti veći od ili jednak :value.',
+        'file'    => ':attribute mora imati više od ili jednako :value kilobajta.',
+        'string'  => ':attribute mora imati više od ili jednako :value karaktera.',
+        'array'   => ':attribute mora imati :value stavki ili više.',
+    ],
+    'exists'               => 'Odabrani :attribute je neispravan.',
+    'image'                => ':attribute mora biti slika.',
+    'image_extension'      => ':attribute mora imati ispravnu i podržanu ekstenziju slike.',
+    'in'                   => 'Odabrani :attribute je neispravan.',
+    'integer'              => ':attribute mora biti integer.',
+    'ip'                   => ':attribute mora biti ispravna IP adresa.',
+    'ipv4'                 => ':attribute mora biti ispravna IPv4 adresa.',
+    'ipv6'                 => ':attribute mora biti ispravna IPv6 adresa.',
+    'json'                 => ':attribute mora biti ispravan JSON string.',
+    'lt'                   => [
+        'numeric' => ':attribute mora biti manji od :value.',
+        'file'    => ':attribute mora imati manje od :value kilobajta.',
+        'string'  => ':attribute mora imati manje od :value karaktera.',
+        'array'   => ':attribute mora imati manje od :value stavki.',
+    ],
+    'lte'                  => [
+        'numeric' => ':attribute mora imati vrijednost manju od ili jednaku :value.',
+        'file'    => ':attribute mora imati manje od ili jednako :value kilobajta.',
+        'string'  => ':attribute mora imati manje od ili jednako :value karaktera.',
+        'array'   => ':attribute ne smije imati više od :value stavki.',
+    ],
+    'max'                  => [
+        'numeric' => ':attribute ne može biti veći od :max.',
+        'file'    => ':attribute ne može imati više od :max kilobajta.',
+        'string'  => ':attribute ne može imati više od :max karaktera.',
+        'array'   => ':attribute ne može imati više od :max stavki.',
+    ],
+    'mimes'                => ':attribute mora biti fajl vrste: values.',
+    'min'                  => [
+        'numeric' => ':attribute mora biti najmanje :min.',
+        'file'    => ':attribute mora imati najmanje :min kilobajta.',
+        'string'  => ':attribute mora imati najmanje :min karaktera.',
+        'array'   => ':attribute mora imati najmanje :min stavki.',
+    ],
+    'not_in'               => 'Odabrani :attribute je neispravan.',
+    'not_regex'            => 'Format :attribute je neispravan.',
+    'numeric'              => ':attribute mora biti broj.',
+    'regex'                => 'Format :attribute je neispravan.',
+    'required'             => 'Polje :attribute je obavezno.',
+    'required_if'          => 'Polje :attribute je obavezno kada :other ima vrijednost :value.',
+    'required_with'        => 'Polje :attribute je obavezno kada su prisutne :values.',
+    'required_with_all'    => 'Polje :attribute je obavezno kada su prisutne :values.',
+    'required_without'     => 'Polje :attribute je obavezno kada :values nisu prisutne.',
+    'required_without_all' => 'Polje :attribute je obavezno kada nijedno od :values nije prisutno.',
+    'same'                 => ':attribute i :other se moraju poklapati.',
+    'safe_url'             => 'Navedeni link možda nije siguran.',
+    'size'                 => [
+        'numeric' => ':attribute mora biti :size.',
+        'file'    => ':attribute mora imati :size kilobajta.',
+        'string'  => ':attribute mora imati :size karaktera.',
+        'array'   => ':attribute mora sadržavati :size stavki.',
+    ],
+    'string'               => ':attribute mora biti string.',
+    'timezone'             => ':attribute mora biti ispravna zona.',
+    'unique'               => ':attribute je zauzet.',
+    'url'                  => 'Format :attribute je neispravan.',
+    'uploaded'             => 'Fajl nije učitan. Server ne prihvata fajlove ove veličine.',
+
+    // Custom validation lines
+    'custom' => [
+        'password-confirm' => [
+            'required_with' => 'Zahtijeva se potvrda lozinke',
+        ],
+    ],
+
+    // Custom validation attributes
+    'attributes' => [],
+];
diff --git a/resources/lang/ca/activities.php b/resources/lang/ca/activities.php
new file mode 100644 (file)
index 0000000..38e4d4e
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
+return [
+
+    // Pages
+    'page_create'                 => 'ha creat la pàgina',
+    'page_create_notification'    => 'Pàgina creada correctament',
+    'page_update'                 => 'ha actualitzat la pàgina',
+    'page_update_notification'    => 'Pàgina actualitzada correctament',
+    'page_delete'                 => 'ha suprimit una pàgina',
+    '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',
+
+    // Chapters
+    'chapter_create'              => 'ha creat el capítol',
+    'chapter_create_notification' => 'Capítol creat correctament',
+    '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 suprimit correctament',
+    'chapter_move'                => 'ha mogut el capítol',
+
+    // Books
+    'book_create'                 => 'ha creat el llibre',
+    'book_create_notification'    => 'Llibre creat correctament',
+    'book_update'                 => 'ha actualitzat el llibre',
+    'book_update_notification'    => 'Llibre actualitzat correctament',
+    'book_delete'                 => 'ha suprimit un llibre',
+    'book_delete_notification'    => 'Llibre suprimit correctament',
+    'book_sort'                   => 'ha ordenat el llibre',
+    'book_sort_notification'      => 'Llibre reordenat correctament',
+
+    // Bookshelves
+    'bookshelf_create'            => 'ha creat el prestatge',
+    'bookshelf_create_notification'    => 'Prestatge creat 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',
+
+    // Other
+    'commented_on'                => 'ha comentat a',
+    'permissions_update'          => 'ha actualitzat els permisos',
+];
diff --git a/resources/lang/ca/auth.php b/resources/lang/ca/auth.php
new file mode 100644 (file)
index 0000000..a66f75f
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
+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.',
+
+    // Login & Register
+    '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',
+    'logout' => 'Tanca la sessió',
+
+    'name' => 'Nom',
+    'username' => 'Nom d\'usuari',
+    'email' => 'Adreça electrònica',
+    'password' => 'Contrasenya',
+    'password_confirm' => 'Confirmeu la contrasenya',
+    'password_hint' => 'Cal que tingui més de 7 caràcters',
+    'forgot_password' => 'Heu oblidat la contrasenya?',
+    '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_registration_text' => 'Registreu-vos i inicieu la sessió fent servir un altre servei.',
+
+    'register_thanks' => 'Gràcies per registrar-vos!',
+    'register_confirm' => 'Reviseu el vostre correu electrònic i feu clic al botó de confirmació per a accedir a :appName.',
+    'registrations_disabled' => 'Actualment, els registres estan desactivats',
+    'registration_email_domain_invalid' => 'Aquest domini de correu electrònic no té accés a aquesta aplicació',
+    'register_success' => 'Gràcies per registrar-vos! Ja us hi heu registrat i heu iniciat la sessió.',
+
+
+    // 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.',
+    '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 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' => 'S\'ha confirmat el vostre correu electrònic!',
+    'email_confirm_resent' => 'S\'ha tornat a enviar el correu electrònic de confirmació. Reviseu la vostra safata d\'entrada.',
+
+    '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_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_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' => 'S\'ha establert la contrasenya, ara ja teniu accés a :appName!'
+];
\ No newline at end of file
diff --git a/resources/lang/ca/common.php b/resources/lang/ca/common.php
new file mode 100644 (file)
index 0000000..22b33de
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
+return [
+
+    // Buttons
+    'cancel' => 'Cancel·la',
+    'confirm' => 'D\'acord',
+    'back' => 'Enrere',
+    'save' => 'Desa',
+    'continue' => 'Continua',
+    'select' => 'Selecciona',
+    'toggle_all' => 'Commuta-ho tot',
+    'more' => 'Més',
+
+    // Form Labels
+    'name' => 'Nom',
+    'description' => 'Descripció',
+    'role' => 'Rol',
+    'cover_image' => 'Imatge de portada',
+    'cover_image_description' => 'Aquesta imatge hauria de fer aproximadament 440x250 px.',
+    
+    // Actions
+    'actions' => 'Accions',
+    'view' => 'Visualitza',
+    'view_all' => 'Visualitza-ho tot',
+    'create' => 'Crea',
+    'update' => 'Actualitza',
+    'edit' => 'Edita',
+    'sort' => 'Ordena',
+    'move' => 'Mou',
+    'copy' => 'Copia',
+    'reply' => 'Respon',
+    'delete' => 'Suprimeix',
+    'delete_confirm' => 'Confirma la supressió',
+    'search' => 'Cerca',
+    'search_clear' => 'Esborra la cerca',
+    'reset' => 'Reinicialitza',
+    'remove' => 'Elimina',
+    'add' => 'Afegeix',
+    'fullscreen' => 'Pantalla completa',
+
+    // Sort Options
+    '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ó',
+
+    // Misc
+    'deleted_user' => 'Usuari eliminat',
+    'no_activity' => 'No hi ha activitat',
+    'no_items' => 'No hi ha cap element',
+    'back_to_top' => 'Torna a dalt',
+    'toggle_details' => 'Commuta els detalls',
+    'toggle_thumbnails' => 'Commuta les miniatures',
+    'details' => 'Detalls',
+    'grid_view' => 'Visualització en graella',
+    'list_view' => 'Visualització en llista',
+    'default' => 'Per defecte',
+    'breadcrumb' => 'Ruta de navegació',
+
+    // Header
+    'header_menu_expand' => 'Expand Header Menu',
+    'profile_menu' => 'Menú del perfil',
+    'view_profile' => 'Mostra el perfil',
+    'edit_profile' => 'Edita el perfil',
+    'dark_mode' => 'Mode fosc',
+    'light_mode' => 'Mode clar',
+
+    // Layout tabs
+    'tab_info' => 'Informació',
+    'tab_info_label' => 'Tab: Show Secondary Information',
+    'tab_content' => 'Contingut',
+    'tab_content_label' => 'Tab: Show Primary Content',
+
+    // 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_rights' => 'Tots els drets reservats',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Política de privadesa',
+    'terms_of_service' => 'Condicions del servei',
+];
diff --git a/resources/lang/ca/components.php b/resources/lang/ca/components.php
new file mode 100644 (file)
index 0000000..d96582d
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
+return [
+
+    // Image Manager
+    'image_select' => 'Selecciona 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_uploaded' => 'Pujada :uploadedDate',
+    '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_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',
+    '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_upload_remove' => 'Suprimeix',
+
+    // Code Editor
+    'code_editor' => 'Edita el codi',
+    'code_language' => 'Llenguatge del codi',
+    'code_content' => 'Contingut del codi',
+    'code_session_history' => 'Historial de la sessió',
+    'code_save' => 'Desa el codi',
+];
diff --git a/resources/lang/ca/entities.php b/resources/lang/ca/entities.php
new file mode 100644 (file)
index 0000000..1426168
--- /dev/null
@@ -0,0 +1,320 @@
+<?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
+return [
+
+    // Shared
+    'recently_created' => 'Creat fa poc',
+    'recently_created_pages' => 'Pàgines creades fa poc',
+    'recently_updated_pages' => 'Pàgines actualitzades fa poc',
+    'recently_created_chapters' => 'Capítols creats fa poc',
+    'recently_created_books' => 'Llibres creats fa poc',
+    'recently_created_shelves' => 'Prestatges creats fa poc',
+    'recently_update' => 'Actualitzat fa poc',
+    'recently_viewed' => 'Vist fa poc',
+    'recent_activity' => 'Activitat recent',
+    'create_now' => 'Crea\'n ara',
+    'revisions' => 'Revisions',
+    'meta_revision' => 'Revisió núm. :revisionCount',
+    'meta_created' => 'Creat :timeLength',
+    'meta_created_name' => 'Creat :timeLength per :user',
+    'meta_updated' => 'Actualitzat :timeLength',
+    'meta_updated_name' => 'Actualitzat :timeLength per :user',
+    'meta_owned_name' => 'Propietat de :user',
+    'entity_select' => 'Selecciona una entitat',
+    'images' => 'Imatges',
+    'my_recent_drafts' => 'Els vostres esborranys recents',
+    'my_recently_viewed' => 'Les vostres visualitzacions recents',
+    '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',
+    'export' => 'Exporta',
+    'export_html' => 'Fitxer web independent',
+    'export_pdf' => 'Fitxer PDF',
+    'export_text' => 'Fitxer de text sense format',
+
+    // Permissions and restrictions
+    'permissions' => 'Permisos',
+    'permissions_intro' => 'Si els activeu, aquests permisos tindran més prioritat que qualsevol permís de rol.',
+    'permissions_enable' => 'Activa els permisos personalitzats',
+    'permissions_save' => 'Desa els permisos',
+    'permissions_owner' => 'Propietari',
+
+    // 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_clear' => 'Esborra la cerca',
+    'search_no_pages' => 'La cerca no coincideix amb cap pàgina',
+    'search_for_term' => 'Cerca :term',
+    'search_more' => 'Més resultats',
+    'search_advanced' => 'Cerca avançada',
+    'search_terms' => 'Termes de la cerca',
+    'search_content_type' => 'Tipus de contingut',
+    'search_exact_matches' => 'Coincidències exactes',
+    '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_date_options' => 'Opcions de dates',
+    'search_updated_before' => 'Actualitzat abans de',
+    'search_updated_after' => 'Actualitzat després de',
+    'search_created_before' => 'Creat abans de',
+    'search_created_after' => 'Creat després de',
+    'search_set_date' => 'Defineix una data',
+    'search_update' => 'Actualitza la cerca',
+
+    // Shelves
+    'shelf' => 'Prestatge',
+    'shelves' => 'Prestatges',
+    'x_shelves' => ':count prestatge|:count prestatges',
+    'shelves_long' => 'Prestatges',
+    'shelves_empty' => 'No hi ha cap prestatge creat',
+    'shelves_create' => 'Crea un prestatge nou',
+    'shelves_popular' => 'Prestatges populars',
+    'shelves_new' => 'Prestatges nous',
+    'shelves_new_action' => 'Prestatge nou',
+    'shelves_popular_empty' => 'Aquí apareixeran els prestatges més populars.',
+    'shelves_new_empty' => 'Aquí apareixeran els prestatges creats fa poc.',
+    'shelves_save' => 'Desa el prestatge',
+    'shelves_books' => 'Llibres en aquest prestatge',
+    'shelves_add_books' => 'Afegeix llibres a aquest prestatge',
+    'shelves_drag_books' => 'Arrossegueu llibres aquí 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' => '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' => 'S\'han activat els permisos del prestatge',
+    'shelves_copy_permissions_to_books' => 'Copia els permisos als llibres',
+    'shelves_copy_permissions' => 'Copia els permisos',
+    'shelves_copy_permissions_explain' => 'Això aplicarà la configuració de permisos actual d\'aquest prestatge a tots els llibres que contingui. Abans d\'activar-ho, assegureu-vos que hàgiu desat qualsevol canvi als permisos d\'aquest prestatge.',
+    'shelves_copy_permission_success' => 'S\'han copiat els permisos del prestatge a :count llibres',
+
+    // Books
+    'book' => 'Llibre',
+    'books' => 'Llibres',
+    'x_books' => ':count llibre|:count llibres',
+    'books_empty' => 'No hi ha cap llibre creat',
+    'books_popular' => 'Llibres populars',
+    'books_recent' => 'Llibres recents',
+    'books_new' => 'Llibres nous',
+    'books_new_action' => 'Llibre nou',
+    'books_popular_empty' => 'Aquí apareixeran els llibres més populars.',
+    'books_new_empty' => 'Aquí apareixeran els llibres creats fa poc.',
+    '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_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_permissions' => '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_search_this' => 'Cerca en aquest llibre',
+    'books_navigation' => 'Navegació pel llibre',
+    'books_sort' => 'Ordena el contingut del llibre',
+    '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_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',
+
+    // Chapters
+    'chapter' => 'Capítol',
+    'chapters' => 'Capítols',
+    'x_chapters' => ':count capítol|:count capítols',
+    'chapters_popular' => 'Capítols populars',
+    'chapters_new' => 'Capítol nou',
+    '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_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',
+    'chapter_move_success' => 'S\'ha mogut el capítol a :bookName',
+    '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_search_this' => 'Cerca en aquest capítol',
+
+    // Pages
+    'page' => 'Pàgina',
+    'pages' => 'Pàgines',
+    'x_pages' => ':count pàgina|:count pàgines',
+    'pages_popular' => 'Pàgines populars',
+    'pages_new' => 'Pàgina nova',
+    'pages_attachments' => 'Adjuncions',
+    '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_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_editing_page' => 'Esteu editant la pàgina',
+    'pages_edit_draft_save_at' => 'Esborrany desat ',
+    'pages_edit_delete_draft' => 'Suprimeix l\'esborrany',
+    'pages_edit_discard_draft' => 'Descarta l\'esborrany',
+    '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_save' => 'Desa la pàgina',
+    'pages_title' => 'Títol de la pàgina',
+    'pages_name' => 'Nom de la pàgina',
+    'pages_md_editor' => 'Editor',
+    'pages_md_preview' => 'Previsualització',
+    '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_not_in_chapter' => 'La pàgina no pertany a cap capítol',
+    'pages_move' => 'Mou la pàgina',
+    'pages_move_success' => 'S\'ha mogut la pàgina a ":parentName"',
+    '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_revision' => 'Revisió',
+    'pages_revisions' => 'Revisions de la pàgina',
+    '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_numbered' => 'Revisió núm. :id',
+    'pages_revisions_numbered_changes' => 'Canvis de la revisió núm. :id',
+    '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' => 'Edita el contingut',
+    'pages_permissions_active' => 'S\'han activat els permisos de la pàgina',
+    'pages_initial_revision' => 'Publicació inicial',
+    '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_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_b' => 'en els darrers :minCount minuts',
+        'message' => ':start :time. Aneu amb compte de no trepitjar-vos les actualitzacions entre vosaltres!',
+    ],
+    'pages_draft_discarded' => 'S\'ha descartat l\'esborrany, l\'editor s\'ha actualitzat amb el contingut actual de la pàgina',
+    'pages_specific' => 'Una pàgina específica',
+    'pages_is_template' => 'Plantilla de pàgina',
+
+    // Editor Sidebar
+    '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',
+    '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',
+    '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_items' => 'Elements adjunts',
+    'attachments_upload' => 'Puja un fitxer',
+    'attachments_link' => 'Adjunta un enllaç',
+    'attachments_set_link' => 'Defineix l\'enllaç',
+    'attachments_delete' => 'Seguir que voleu suprimir aquesta adjunció?',
+    'attachments_dropzone' => 'Arrossegueu fitxers o feu clic aquí per a adjuntar un fitxer',
+    '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_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_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_file_uploaded' => 'Fitxer pujat correctament',
+    'attachments_file_updated' => 'Fitxer actualitzat correctament',
+    'attachments_link_attached' => 'Enllaç adjuntat a la pàgina correctament',
+    'templates' => 'Plantilles',
+    'templates_set_as_template' => 'La pàgina és una plantilla',
+    'templates_explain_set_as_template' => 'Podeu definir aquesta pàgina com a plantilla perquè el seu contingut es pugui fer servir en crear altres pàgines. Els altres usuaris podran fer servir la plantilla si tenen permís per a veure aquesta pàgina.',
+    'templates_replace_content' => 'Substitueix el contingut de la pàgina',
+    'templates_append_content' => 'Afegeix al final del contingut de la pàgina',
+    'templates_prepend_content' => 'Afegeix al principi del contingut de la pàgina',
+
+    // Profile View
+    'profile_user_for_x' => 'Usuari fa :time',
+    'profile_created_content' => 'Contingut creat',
+    'profile_not_created_pages' => ':userName no ha creat cap pàgina',
+    'profile_not_created_chapters' => ':userName no ha creat cap capítol',
+    'profile_not_created_books' => ':userName no ha creat cap llibre',
+    'profile_not_created_shelves' => ':userName no ha creat cap prestatge',
+
+    // Comments
+    'comment' => 'Comentari',
+    'comments' => 'Comentaris',
+    'comment_add' => 'Afegeix un comentari',
+    'comment_placeholder' => 'Deixeu un comentari aquí',
+    'comment_count' => '{0} Sense comentaris|{1} 1 comentari|[2,*] :count comentaris',
+    'comment_save' => 'Desa el comentari',
+    'comment_saving' => 'S\'està desant el comentari...',
+    'comment_deleting' => 'S\'està suprimint el comentari...',
+    'comment_new' => 'Comentari nou',
+    'comment_created' => 'ha comentat :createDiff',
+    'comment_updated' => 'Actualitzat :updateDiff per :username',
+    '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',
+
+    // Revision
+    'revision_delete_confirm' => 'Segur que voleu suprimir aquesta revisió?',
+    'revision_restore_confirm' => 'Segur que voleu restaurar aquesta revisió? Se substituirà el contingut de la pàgina actual.',
+    'revision_delete_success' => 'S\'ha suprimit la revisió',
+    'revision_cannot_delete_latest' => 'No es pot suprimir la darrera revisió.'
+];
diff --git a/resources/lang/ca/errors.php b/resources/lang/ca/errors.php
new file mode 100644 (file)
index 0000000..1a413ba
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Text shown in error messaging.
+ */
+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.',
+
+    // 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',
+    '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',
+    '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_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_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.',
+
+    // 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.',
+    'uploaded'  => 'El servidor no permet pujades d\'aquesta mida. Proveu-ho amb una mida de fitxer més petita.',
+    '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',
+    'file_upload_timeout' => 'La pujada del fitxer ha superat el temps màxim d\'espera.',
+
+    // Attachments
+    'attachment_not_found' => 'No s\'ha trobat 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_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' => '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',
+
+    // 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.',
+
+    // Comments
+    '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.',
+    'empty_comment' => 'No podeu afegir un comentari buit.',
+
+    // Error pages
+    '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',
+    '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',
+
+    // Settings & Maintenance
+    'maintenance_test_email_failure' => 'S\'ha produït un error en enviar un correu electrònic de prova:',
+
+];
diff --git a/resources/lang/ca/pagination.php b/resources/lang/ca/pagination.php
new file mode 100644 (file)
index 0000000..f05a8b3
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
+return [
+
+    'previous' => '&laquo; Anterior',
+    'next'     => 'Següent &raquo;',
+
+];
diff --git a/resources/lang/ca/passwords.php b/resources/lang/ca/passwords.php
new file mode 100644 (file)
index 0000000..1f0f759
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
+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!',
+
+];
diff --git a/resources/lang/ca/settings.php b/resources/lang/ca/settings.php
new file mode 100755 (executable)
index 0000000..6ce3b19
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
+return [
+
+    // Common Messages
+    'settings' => 'Configuració',
+    'settings_save' => 'Desa la configuració',
+    'settings_save_success' => 'S\'ha desat la configuració',
+
+    // App Settings
+    'app_customization' => 'Personalització',
+    'app_features_security' => 'Funcionalitats i seguretat',
+    '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_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_editor' => 'Editor de pàgines',
+    'app_editor_desc' => 'Seleccioneu quin editor faran servir tots els usuaris per a editar les pàgines.',
+    '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ó <head> 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' => 'Aquesta imatge hauria de tenir 43 px d\'alçada. <br>Les imatges grosses es reduiran.',
+    'app_primary_color' => 'Color primari de l\'aplicació',
+    'app_primary_color_desc' => 'Defineix el color primari de l\'aplicació, incloent-hi la part superior, els botons i els enllaços.',
+    '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::<clau>" 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ó. <br> Els comentaris existents no es mostraran.',
+
+    // Color settings
+    'content_colors' => 'Colors del contingut',
+    'content_colors_desc' => 'Defineix els colors de tots els elements de la jerarquia d\'organització de pàgines. És recomanable triar colors amb una brillantor semblant als colors per defecte per a mantenir-ne la llegibilitat.',
+    'bookshelf_color' => 'Color dels prestatges',
+    'book_color' => 'Color dels llibres',
+    'chapter_color' => 'Color dels capítols',
+    'page_color' => 'Color de les pàgines',
+    'page_draft_color' => 'Color dels esborranys de pàgines',
+
+    // 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_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_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ó. <br> 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_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_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_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_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_open' => 'Obre la paperera de reciclatge',
+
+    // 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_deleted_item' => 'Element suprimit',
+    'recycle_bin_deleted_by' => 'Suprimit per',
+    'recycle_bin_deleted_at' => 'Moment de la supressió',
+    'recycle_bin_permanently_delete' => 'Suprimeix permanentment',
+    'recycle_bin_restore' => 'Restaura',
+    'recycle_bin_contents_empty' => 'La paperera de reciclatge és buida',
+    'recycle_bin_empty' => 'Buida la paperera de reciclatge',
+    'recycle_bin_empty_confirm' => 'Se suprimiran de manera permanent tots els elements de la paperera de reciclatge, incloent-hi el contingut dins de cada element. Segur que voleu buidar la paperera de reciclatge?',
+    '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_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_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_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',
+    'role_create' => 'Crea un rol nou',
+    'role_create_success' => 'Rol creat correctament',
+    '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_no_migration' => "No migris els usuaris",
+    'role_delete_sure' => 'Segur que voleu suprimir aquest rol?',
+    'role_delete_success' => 'Rol suprimit correctament',
+    'role_edit' => 'Edita el rol',
+    'role_details' => 'Detalls del rol',
+    'role_name' => 'Nom del rol',
+    'role_desc' => 'Descripció curta del rol',
+    '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_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_all' => 'Tot',
+    'role_own' => 'Propi',
+    'role_controlled_by_asset' => 'Controlat pel recurs en què es pugen',
+    'role_save' => 'Desa el rol',
+    'role_update_success' => 'Rol actualitzat correctament',
+    'role_users' => 'Usuaris amb aquest rol',
+    'role_users_none' => 'Ara mateix no hi ha cap usuari assignat a aquest rol',
+
+    // Users
+    'users' => 'Usuaris',
+    '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_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' => 'Definiu una contrasenya per a iniciar la sessió a l\'aplicació. Cal que tingui un mínim de 6 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' => 'Aquest és l\'identificador que s\'utilitza per a enllaçar aquest usuari en comunicar amb el sistema d\'autenticació extern.',
+    'users_password_warning' => 'Ompliu-ho només si voleu canviar la vostra contrasenya.',
+    '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_none_selected' => 'No hi ha cap usuari seleccionat',
+    'users_delete_success' => 'Usuari suprimit correctament',
+    'users_edit' => 'Edita l\'usuari',
+    'users_edit_profile' => 'Edita el perfil',
+    'users_edit_success' => 'Usuari actualitzat correctament',
+    '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_social_accounts' => 'Comptes socials',
+    '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_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_none' => 'No s\'ha creat cap testimoni d\'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',
+
+    // API Tokens
+    'user_api_token_create' => 'Crea un testimoni d\'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_create_success' => 'Testimoni d\'API creat correctament',
+    'user_api_token_update_success' => 'Testimoni d\'API actualitzat correctament',
+    'user_api_token' => 'Testimoni d\'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_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_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_success' => 'Testimoni d\'API suprimit correctament',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
+        'cs' => 'Česky',
+        'da' => 'Dansk',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'he' => 'עברית',
+        'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
+        'it' => 'Italian',
+        'ja' => '日本語',
+        'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
+        'nl' => 'Nederlands',
+        'nb' => 'Norsk (Bokmål)',
+        'pl' => 'Polski',
+        'pt' => 'Português',
+        'pt_BR' => 'Português do Brasil',
+        'ru' => 'Русский',
+        'sk' => 'Slovensky',
+        'sl' => 'Slovenščina',
+        'sv' => 'Svenska',
+        'tr' => 'Türkçe',
+        'uk' => 'Українська',
+        'vi' => 'Tiếng Việt',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+    ]
+    //!////////////////////////////////
+];
diff --git a/resources/lang/ca/validation.php b/resources/lang/ca/validation.php
new file mode 100644 (file)
index 0000000..c134c36
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
+return [
+
+    // Standard laravel validation lines
+    'accepted'             => 'Cal que acceptis :attribute.',
+    'active_url'           => 'L\':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.',
+    'before'               => 'El camp :attribute ha de ser una data anterior a :date.',
+    'between'              => [
+        '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.',
+    ],
+    'boolean'              => 'El camp :attribute ha de ser cert o fals.',
+    'confirmed'            => 'La confirmació del camp :attribute no coincideix.',
+    'date'                 => 'El camp :attribute no és una data vàlida.',
+    'date_format'          => 'El camp :attribute no coincideix amb el format :format.',
+    'different'            => 'Els camps :attribute i :other han de ser diferents.',
+    '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',
+    'filled'               => 'El camp :attribute és obligatori.',
+    'gt'                   => [
+        'numeric' => 'El camp :attribute ha de ser més gran que :value.',
+        'file'    => 'El camp :attribute ha de tenir més de :value kilobytes.',
+        'string'  => 'El camp :attribute ha de tenir més de :value caràcters.',
+        'array'   => 'El camp :attribute ha de tenir més de :value elements.',
+    ],
+    'gte'                  => [
+        'numeric' => 'El camp :attribute ha de ser més gran o igual que :value.',
+        'file'    => 'El camp :attribute ha de tenir :value kilobytes o més.',
+        '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.',
+    '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.',
+    '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.',
+    'ipv4'                 => 'El camp :attribute ha de ser una adreça IPv4 vàlida.',
+    'ipv6'                 => 'El camp :attribute ha de ser una adreça IPv6 vàlida.',
+    'json'                 => 'El camp :attribute ha de ser una cadena JSON vàlida.',
+    'lt'                   => [
+        'numeric' => 'El camp :attribute ha de ser menor que :value.',
+        'file'    => 'El camp :attribute ha de tenir menys de :value kilobytes.',
+        'string'  => 'El camp :attribute ha de tenir menys de :value caràcters.',
+        'array'   => 'El camp :attribute ha de tenir menys de :value elements.',
+    ],
+    'lte'                  => [
+        'numeric' => 'El camp :attribute ha de ser més petit o igual que :value.',
+        'file'    => 'El camp :attribute ha de tenir :value kilobytes o menys.',
+        'string'  => 'El camp :attribute ha de tenir :value caràcters o menys.',
+        'array'   => 'El camp :attribute ha de tenir :value elements o menys.',
+    ],
+    'max'                  => [
+        'numeric' => 'El camp :attribute no pot ser més gran que :max.',
+        'file'    => 'El camp :attribute no pot tenir més de :max kilobytes.',
+        'string'  => 'El camp :attribute no pot tenir més de :max caràcters.',
+        'array'   => 'El camp :attribute no pot tenir més de :max elements.',
+    ],
+    'mimes'                => 'El camp :attribute ha de ser un fitxer del tipus: :values.',
+    'min'                  => [
+        'numeric' => 'El camp :attribute no pot ser més petit que :min.',
+        'file'    => 'El camp :attribute no pot tenir menys de :min kilobytes.',
+        'string'  => 'El camp :attribute no pot tenir menys de :min caràcters.',
+        'array'   => 'El camp :attribute no pot tenir menys de :min elements.',
+    ],
+    'not_in'               => 'El camp :attribute seleccionat no és vàlid.',
+    'not_regex'            => 'El format del camp :attribute no és vàlid.',
+    'numeric'              => 'El camp :attribute ha de ser un número.',
+    'regex'                => 'El format del camp :attribute no és vàlid.',
+    '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_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.',
+    'same'                 => 'Els camps :attribute i :other han de coincidir.',
+    '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.',
+        'string'  => 'El camp :attribute ha de tenir :size caràcters.',
+        'array'   => 'El camp :attribute ha de contenir :size elements.',
+    ],
+    'string'               => 'El camp :attribute ha de ser una cadena.',
+    'timezone'             => 'El camp :attribute ha de ser una zona vàlida.',
+    'unique'               => 'El camp :attribute ja està ocupat.',
+    '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.',
+
+    // Custom validation lines
+    'custom' => [
+        'password-confirm' => [
+            'required_with' => 'Cal la confirmació de la contrasenya',
+        ],
+    ],
+
+    // Custom validation attributes
+    'attributes' => [],
+];
index 342610f5d9942ea73b3bd123cbed7b44c1d7073d..e8a75d612c31f1f73b91b17bf214ab01d5c2782a 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Řadit vzestupně',
     'sort_descending' => 'Řadit sestupně',
     'sort_name' => 'Název',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Datum vytvoření',
     'sort_updated_at' => 'Datum aktualizace',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Drobečková navigace',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Nabídka profilu',
     'view_profile' => 'Zobrazit profil',
     'edit_profile' => 'Upravit profil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informace',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Obsah',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Pokud se vám nedaří kliknout na tlačítko „:actionText“, zkopírujte a vložte níže uvedenou URL do vašeho webového prohlížeče:',
     'email_rights' => 'Všechna práva vyhrazena',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index 983239c82648fd2fa1ea8e437ff61d7a661ded14..210aca563d5ccc6eb3a577401408bca5f7cff873 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Sada oprávnění',
     'search_created_by_me' => 'Vytvořeno mnou',
     'search_updated_by_me' => 'Aktualizováno mnou',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Možnosti data',
     'search_updated_before' => 'Aktualizováno před',
     'search_updated_after' => 'Aktualizováno po',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Jste si jisti, že chcete obnovit tuto revizi? Aktuální obsah stránky bude nahrazen.',
     'revision_delete_success' => 'Revize smazána',
     'revision_cannot_delete_latest' => 'Nelze smazat poslední revizi.'
-];
\ No newline at end of file
+];
index 43ba536f0496236363baa7f18e1ff4cf17b398e3..102a1f88b4f09301124a254bcad0d3efb0fcd9dd 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Stránka nenalezena',
     'sorry_page_not_found' => 'Omlouváme se, ale stránka, kterou hledáte nebyla nalezena.',
     'sorry_page_not_found_permission_warning' => 'Pokud očekáváte, že by stránka měla existovat, možná jen nemáte oprávnění pro její zobrazení.',
+    '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' => 'Návrat domů',
     'error_occurred' => 'Nastala chyba',
     'app_down' => ':appName je momentálně vypnutá',
index 80aa6dc385a445c9c0abf5b99bde240d2ee60e41..1dc4000aae3e94f7402ee856db318543c932fa80 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Úvodní stránka aplikace',
     'app_homepage_desc' => 'Vyberte si zobrazení, které se použije jako úvodní stránka. U zvolených stránek bude ignorováno jejich oprávnění.',
     'app_homepage_select' => 'Zvolte stránku',
+    '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::<key>" 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' => 'Vypnutí komentářů',
     'app_disable_comments_toggle' => 'Vypnout komentáře',
     'app_disable_comments_desc' => 'Vypne komentáře napříč všemi stránkami. <br> Existující komentáře se přestanou zobrazovat.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index a843973497637e5b2905c98021e0b115fa4e17c9..1a94b9e24f210a7c17eea9c3328ee1b2364ffdc9 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute musí být delší než :min znaků.',
         'array'   => ':attribute musí obsahovat více než :min prvků.',
     ],
-    'no_double_extension'  => ':attribute musí obsahovat pouze jednu příponu souboru.',
     'not_in'               => 'Zvolená hodnota pro :attribute je neplatná.',
     'not_regex'            => ':attribute musí být regulární výraz.',
     'numeric'              => ':attribute musí být číslo.',
index 264deec609721fd7aafcabf40a3b1b582d10d81b..16898bd2bf5e445cb025c1395b1e00987c5233ed 100644 (file)
@@ -20,7 +20,7 @@ return [
     'chapter_create'              => 'oprettede kapitel',
     'chapter_create_notification' => 'Kapitel blev oprettet',
     'chapter_update'              => 'opdaterede kapitel',
-    'chapter_update_notification' => 'Kapitel blev opdateret',
+    'chapter_update_notification' => 'Kapitlet blev opdateret',
     'chapter_delete'              => 'slettede kapitel',
     'chapter_delete_notification' => 'Kapitel blev slettet',
     'chapter_move'                => 'flyttede kapitel',
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'kommenterede til',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => 'Tilladelser opdateret',
 ];
index f060b24b8f46d9c4e14d4a6d8a1955feae37cab1..8ea58517493187a77f01582a22590e2ec4aa2957 100644 (file)
@@ -6,8 +6,8 @@
  */
 return [
 
-    'failed' => 'Det indtastede stemmer ikke overens med vores registrering.',
-    'throttle' => 'For mange mislykkede loginforsøg. Prøv igen om :seconds seconds.',
+    'failed' => 'Dee indtastede brugeroplysninger stemmer ikke overens med vores registreringer.',
+    'throttle' => 'For mange mislykkede loginforsøg. Prøv igen om :seconds sekunder.',
 
     // Login & Register
     'sign_up' => 'Registrér',
@@ -21,11 +21,11 @@ return [
     'email' => 'E-mail',
     'password' => 'Adgangskode',
     'password_confirm' => 'Bekræft adgangskode',
-    'password_hint' => 'Skal være på mindst 8 karakterer',
+    'password_hint' => 'Skal være på mindst 7 karakterer',
     'forgot_password' => 'Glemt Adgangskode?',
-    'remember_me' => 'Husk Mig',
+    'remember_me' => 'Husk mig',
     'ldap_email_hint' => 'Angiv venligst din kontos e-mail.',
-    'create_account' => 'Opret Konto',
+    'create_account' => 'Opret konto',
     'already_have_account' => 'Har du allerede en konto?',
     'dont_have_account' => 'Har du ikke en konto?',
     'social_login' => 'Social Log ind',
index dbd76d03c41380f891f528ace4a0f3e5b6912938..8613cc04512544d15f4ad14be6e655f0652539ec 100644 (file)
@@ -33,7 +33,7 @@ return [
     'copy' => 'Kopier',
     'reply' => 'Besvar',
     'delete' => 'Slet',
-    'delete_confirm' => 'Confirm Deletion',
+    'delete_confirm' => 'Bekræft sletning',
     'search' => 'Søg',
     'search_clear' => 'Ryd søgning',
     'reset' => 'Nulstil',
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Sorter stigende',
     'sort_descending' => 'Sorter faldende',
     'sort_name' => 'Navn',
+    'sort_default' => 'Standard',
     'sort_created_at' => 'Oprettelsesdato',
     'sort_updated_at' => 'Opdateringsdato',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Brødkrumme',
 
     // Header
+    'header_menu_expand' => 'Udvid header menu',
     'profile_menu' => 'Profilmenu',
     'view_profile' => 'Vis profil',
     'edit_profile' => 'Redigér Profil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Faneblad: Vis sekundær information',
     'tab_content' => 'Indhold',
+    'tab_content_label' => 'Faneblad: Vis primær indhold',
 
     // Email Content
     'email_action_help' => 'Hvis du har problemer med at trykke på ":actionText" knappen, prøv at kopiere og indsætte linket herunder ind i din webbrowser:',
     'email_rights' => 'Alle rettigheder forbeholdes',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privatlivspolitik',
+    'terms_of_service' => 'Tjenestevilkår',
 ];
index 28e1404cd02965dbf40d66873b629ce98e7f711f..9ba511fc5124a2cc5a6bf7c05395796c6bc4c77c 100644 (file)
@@ -15,7 +15,7 @@ return [
     'image_load_more' => 'Indlæse mere',
     'image_image_name' => 'Billednavn',
     'image_delete_used' => 'Dette billede er brugt på siderne nedenfor.',
-    'image_delete_confirm_text' => 'Are you sure you want to delete this image?',
+    'image_delete_confirm_text' => 'Er du sikker på at du vil slette dette billede?',
     'image_select_image' => 'Vælg billede',
     'image_dropzone' => 'Træk-og-slip billede eller klik her for at uploade',
     'images_deleted' => 'Billede slettet',
@@ -29,6 +29,6 @@ return [
     'code_editor' => 'Rediger kode',
     'code_language' => 'Kodesprog',
     'code_content' => 'Kodeindhold',
-    'code_session_history' => 'Session History',
+    'code_session_history' => 'Sessionshistorik',
     'code_save' => 'Gem kode',
 ];
index fee4d4134d74d50fc5bc1b8083ee33a140611b61..adc723dada5e3e5a65e423a47fb458deb75f9af6 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Oprettet :timeLength af :user',
     'meta_updated' => 'Opdateret :timeLength',
     'meta_updated_name' => 'Opdateret :timeLength af :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Ejet af :user',
     'entity_select' => 'Vælg emne',
     'images' => 'Billeder',
     'my_recent_drafts' => 'Mine seneste kladder',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Når de er aktiveret, vil disse tilladelser have prioritet over alle indstillede rolletilladelser.',
     'permissions_enable' => 'Aktivér tilpassede tilladelser',
     'permissions_save' => 'Gem tilladelser',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Ejer',
 
     // Search
     'search_results' => 'Søgeresultater',
@@ -49,8 +49,8 @@ return [
     'search_no_pages' => 'Ingen sider matchede søgning',
     'search_for_term' => 'Søgning for :term',
     'search_more' => 'Flere resultater',
-    'search_advanced' => 'Advanced Search',
-    'search_terms' => 'Search Terms',
+    'search_advanced' => 'Avanceret søgning',
+    'search_terms' => 'Søgeord',
     'search_content_type' => 'Indholdstype',
     'search_exact_matches' => 'Nøjagtige matches',
     'search_tags' => 'Tagsøgninger',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Rettigheders sæt',
     'search_created_by_me' => 'Oprettet af mig',
     'search_updated_by_me' => 'Opdateret af mig',
+    'search_owned_by_me' => 'Ejet af mig',
     'search_date_options' => 'Datoindstillinger',
     'search_updated_before' => 'Opdateret før',
     'search_updated_after' => 'Opdateret efter',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Opret nyt kapitel',
     'chapters_delete' => 'Slet kapitel',
     'chapters_delete_named' => 'Slet kapitel :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_explain' => 'Dette vil slette kapitlet med navnet \':chapterName\'. Alle sider i dette kapitel vil også blive slettet.',
     'chapters_delete_confirm' => 'Er du sikker på du vil slette dette kapitel?',
     'chapters_edit' => 'Rediger kapitel',
     'chapters_edit_named' => 'Rediger kapitel :chapterName',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Sidserevisioner',
     'pages_revisions_named' => 'Siderevisioner for :pageName',
     'pages_revision_named' => 'Siderevision for :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Genoprettet fra #:id; :summary',
     'pages_revisions_created_by' => 'Oprettet af',
     'pages_revisions_date' => 'Revisionsdato',
     'pages_revisions_number' => '#',
@@ -259,7 +260,7 @@ return [
     'attachments_upload' => 'Upload fil',
     'attachments_link' => 'Vedhæft link',
     'attachments_set_link' => 'Sæt link',
-    'attachments_delete' => 'Are you sure you want to delete this attachment?',
+    'attachments_delete' => 'Er du sikker på at du vil slette denne vedhæftning?',
     'attachments_dropzone' => 'Slip filer eller klik her for at vedhæfte en fil',
     'attachments_no_files' => 'Ingen filer er blevet overført',
     'attachments_explain_link' => 'Du kan vedhæfte et link, hvis du foretrækker ikke at uploade en fil. Dette kan være et link til en anden side eller et link til en fil i skyen.',
@@ -268,7 +269,7 @@ return [
     'attachments_link_url' => 'Link til filen',
     'attachments_link_url_hint' => 'Adresse (URL) på side eller fil',
     'attach' => 'Vedhæft',
-    'attachments_insert_link' => 'Add Attachment Link to Page',
+    'attachments_insert_link' => 'Tilføj vedhæftningslink til side',
     'attachments_edit_file' => 'Rediger fil',
     'attachments_edit_file_name' => 'Filnavn',
     'attachments_edit_drop_upload' => 'Slip filer eller klik her for at uploade og overskrive',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Er du sikker på at du ønsker at gendanne denne revision? Nuværende sideindhold vil blive erstattet.',
     'revision_delete_success' => 'Revision slettet',
     'revision_cannot_delete_latest' => 'Kan ikke slette seneste revision.'
-];
\ No newline at end of file
+];
index 0f6287fbc1861a1546fa9ada4690b0bb3a2a43db..806ac8177c2f1a3071bff928db2c1f26497d7884 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Siden blev ikke fundet',
     'sorry_page_not_found' => 'Beklager, siden du leder efter blev ikke fundet.',
     'sorry_page_not_found_permission_warning' => 'Hvis du forventede, at denne side skulle eksistere, har du muligvis ikke tilladelse til at se den.',
+    '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' => 'Gå tilbage til hjem',
     'error_occurred' => 'Der opstod en fejl',
     'app_down' => ':appName er nede lige nu',
index 80ebcb1239fda4ccfa4bab8972cb81206495a088..e0640888b00b78dfa4a5169886e75fe8770a47de 100644 (file)
@@ -9,13 +9,13 @@ return [
     // Common Messages
     'settings' => 'Indstillinger',
     'settings_save' => 'Gem indstillinger',
-    'settings_save_success' => 'Indstillinger gemt',
+    'settings_save_success' => 'Indstillingerne blev gemt',
 
     // App Settings
     'app_customization' => 'Tilpasning',
-    'app_features_security' => 'Funktioner & sikkerhed',
-    'app_name' => 'Programnavn',
-    'app_name_desc' => 'Dette er navnet vist i headeren og i systemafsendte E-Mails.',
+    'app_features_security' => 'Funktionalitet og sikkerhed',
+    'app_name' => 'Applikationsnavn',
+    'app_name_desc' => 'Dette navn vises i headeren og i alle e-mails sendt fra systemet.',
     'app_name_header' => 'Vis navn i header',
     'app_public_access' => 'Offentlig adgang',
     'app_public_access_desc' => 'Aktivering af denne funktion giver besøgende, der ikke er logget ind, adgang til indhold i din BookStack-instans.',
@@ -24,19 +24,24 @@ return [
     'app_public_viewing' => 'Tillad offentlig visning?',
     'app_secure_images' => 'Højere sikkerhed for billeduploads',
     'app_secure_images_toggle' => 'Aktiver højere sikkerhed for billeduploads',
-    'app_secure_images_desc' => 'Af ydeevneårsager er alle billeder offentlige. Denne funktion tilføjer en tilfældig, vanskelig at gætte streng foran billed-Url\'er. Sørg for, at mappeindekser ikke er aktiveret for at forhindre nem adgang.',
+    'app_secure_images_desc' => 'Af performanceårsager er alle billeder offentlige. Denne funktion tilføjer en tilfældig, vanskelig at gætte streng foran billed-url\'er. Sørg for, at mappeindeksering ikke er aktiveret for at forhindre nem adgang.',
     'app_editor' => 'Sideeditor',
     'app_editor_desc' => 'Vælg hvilken editor der skal bruges af alle brugere til at redigere sider.',
-    'app_custom_html' => 'Tilpasset HTML head-indhold',
-    'app_custom_html_desc' => 'Al indhold tilføjet her, vil blive indsat i bunden af <head> sektionen på alle sider. Dette er brugbart til overskrivning af styling og tilføjelse af analysekode.',
-    'app_custom_html_disabled_notice' => 'Brugerdefineret HTML-head indhold er deaktiveret på denne indstillingsside for at sikre, at ødelæggende ændringer kan rettes.',
-    'app_logo' => 'Programlogo',
-    'app_logo_desc' => 'Dette billede skal være 43px højt. <br> Større billeder vil blive skaleret ned.',
-    'app_primary_color' => 'Primær programfarve',
+    'app_custom_html' => 'Tilpasset HTML head indhold',
+    'app_custom_html_desc' => 'Alt indhold tilføjet her, vil blive indsat i bunden af <head> sektionen på alle sider. Dette er brugbart til overskrivning af styles og tilføjelse af analytics kode.',
+    'app_custom_html_disabled_notice' => 'Brugerdefineret HTML head indhold er deaktiveret på denne indstillingsside for at, at ændringer kan rulles tilbage.',
+    'app_logo' => 'Applikationslogo',
+    'app_logo_desc' => 'Dette billede skal være 43px højt. <br>Store billeder vil blive skaleret ned.',
+    'app_primary_color' => 'Primær applikationsfarve',
     'app_primary_color_desc' => 'Sætter den primære farve for applikationen herunder banneret, knapper og links.',
-    'app_homepage' => 'Programforside',
-    'app_homepage_desc' => 'Vælg en visning, der skal vises på startsiden i stedet for standardvisningen. Sidetilladelser ignoreres for valgte sider.',
+    'app_homepage' => 'Applikationsforside',
+    'app_homepage_desc' => 'Vælg en visning, der skal vises på forsiden i stedet for standardvisningen. Sidetilladelser ignoreres for de valgte sider.',
     'app_homepage_select' => 'Vælg en side',
+    'app_footer_links' => 'Footer links',
+    'app_footer_links_desc' => 'Tilføj links til footeren. Linksene vil blive vist nederst på de fleste sider, inkluderet sider, som ikke kræver login. Brug en label med "trans::<key>" for at bruge systemdefinerede oversættelser. For eksempel: "trans::common.privacy_policy" giver den oversatte tekst "Privacy Policy" og "trans::common.terms_of_service" vil give den oversatte tekst "Terms of Service".',
+    'app_footer_links_label' => 'Link label',
+    'app_footer_links_url' => 'Link URL',
+    'app_footer_links_add' => 'Tilføj footer link',
     'app_disable_comments' => 'Deaktiver kommentarer',
     'app_disable_comments_toggle' => 'Deaktiver kommentar',
     'app_disable_comments_desc' => 'Deaktiverer kommentarer på tværs af alle sider i applikationen. <br> Eksisterende kommentarer vises ikke.',
@@ -68,7 +73,7 @@ return [
     'maint' => 'Vedligeholdelse',
     'maint_image_cleanup' => 'Ryd op i billeder',
     'maint_image_cleanup_desc' => "Scanner side & revisionsindhold for at kontrollere, hvilke billeder og tegninger, der i øjeblikket er i brug, og hvilke billeder, der er overflødige. Sørg for, at du opretter en komplet database og billedbackup, før du kører dette.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Slet også billeder, der kun findes i gamle siderevisioner',
     'maint_image_cleanup_run' => 'Kør Oprydning',
     'maint_image_cleanup_warning' => 'der blev fundet :count potentielt ubrugte billeder. Er du sikker på, at du vil slette disse billeder?',
     'maint_image_cleanup_success' => ':count: potentielt ubrugte billeder fundet og slettet!',
@@ -80,41 +85,41 @@ return [
     'maint_send_test_email_mail_subject' => 'Test E-Mail',
     'maint_send_test_email_mail_greeting' => 'E-Mail levering ser ud til at virke!',
     'maint_send_test_email_mail_text' => 'Tillykke! Da du har modtaget denne mailnotifikation, ser det ud som om, at dine mailindstillinger er opsat korrekt.',
-    '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_recycle_bin_desc' => 'Slettede hylder, bøger, kapitler og sider overføres til papirkurven, så de kan gendannes eller slettes permanent. Ældre elementer i papirkurven fjernes automatisk efter et stykke tid afhængigt af systemets konfiguration.',
+    'maint_recycle_bin_open' => 'Åbn papirkurven',
 
     // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin' => 'Papirkurv',
+    'recycle_bin_desc' => 'Her kan du gendanne elementer, der er blevet slettet eller vælge at permanent fjerne dem fra systemet. Denne liste er ufiltreret, i modsætning til lignende aktivitetslister i systemet, hvor tilladelsesfiltre anvendes.',
+    'recycle_bin_deleted_item' => 'Slettet element',
+    'recycle_bin_deleted_by' => 'Slettet af',
+    'recycle_bin_deleted_at' => 'Sletningstidspunkt',
+    'recycle_bin_permanently_delete' => 'Slet permanent',
+    'recycle_bin_restore' => 'Gendan',
+    'recycle_bin_contents_empty' => 'Papirkurven er tom',
+    'recycle_bin_empty' => 'Tøm papirkurv',
+    'recycle_bin_empty_confirm' => 'Dette vil permanent slette alle elementer i papirkurven, inkluderet hvert elements indhold. Er du sikker på, at du vil tømme papirkurven?',
+    'recycle_bin_destroy_confirm' => 'Denne handling sletter dette element permanent, sammen med elementerne anført nedenfor, fra systemet. Du vil ikke være i stand til at gendanne dette indhold. Er du sikker på, at du vil slette dette element permanent?',
+    'recycle_bin_destroy_list' => 'Elementer der skal slettes',
+    'recycle_bin_restore_list' => 'Elementer der skal gendannes',
+    'recycle_bin_restore_confirm' => 'Denne handling vil gendanne det slettede element, herunder alle underelementer, til deres oprindelige placering. Hvis den oprindelige placering siden er blevet slettet, og nu er i papirkurven, vil det overordnede element også skulle gendannes.',
+    'recycle_bin_restore_deleted_parent' => 'Det overordnede element til dette element er også blevet slettet. Disse vil forblive slettet indtil det overordnede også er gendannet.',
+    'recycle_bin_destroy_notification' => 'Slettede :count elementer fra papirkurven.',
+    'recycle_bin_restore_notification' => 'Gendannede :count elementer fra papirkurven.',
 
     // 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_date' => 'Activity Date',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit' => 'Revisionslog',
+    'audit_desc' => 'Denne revisionslog viser en liste over aktiviteter sporet i systemet. Denne liste er ufiltreret i modsætning til lignende aktivitetslister i systemet, hvor tilladelsesfiltre anvendes.',
+    'audit_event_filter' => 'Event filter',
+    'audit_event_filter_no_filter' => 'Intet filter',
+    'audit_deleted_item' => 'Element slettet',
+    'audit_deleted_item_name' => 'Navn: :name',
+    'audit_table_user' => 'Bruger',
+    'audit_table_event' => 'Hændelse',
+    'audit_table_related' => 'Relateret element eller detalje',
+    'audit_table_date' => 'Aktivitetsdato',
+    'audit_date_from' => 'Datointerval fra',
+    'audit_date_to' => 'Datointerval til',
 
     // Role Settings
     'roles' => 'Roller',
@@ -141,7 +146,7 @@ return [
     'role_access_api' => 'Tilgå system-API',
     'role_manage_settings' => 'Administrer app-indstillinger',
     'role_asset' => 'Tilladelser for medier og "assets"',
-    '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.',
+    'roles_system_warning' => 'Vær opmærksom på, at adgang til alle af de ovennævnte tre tilladelser, kan give en bruger mulighed for at ændre deres egne brugerrettigheder eller brugerrettigheder for andre i systemet. Tildel kun roller med disse tilladelser til betroede brugere.',
     'role_asset_desc' => 'Disse tilladelser kontrollerer standardadgang til medier og "assets" i systemet. Tilladelser til bøger, kapitler og sider tilsidesætter disse tilladelser.',
     'role_asset_admins' => 'Administratorer får automatisk adgang til alt indhold, men disse indstillinger kan vise eller skjule UI-indstillinger.',
     'role_all' => 'Alle',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'Brugerprofil',
     'users_add_new' => 'Tilføj ny bruger',
     'users_search' => 'Søg efter brugere',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Seneste aktivitet',
     'users_details' => 'Brugeroplysninger',
     'users_details_desc' => 'Angiv et visningsnavn og en E-Mail-adresse for denne bruger. E-Mail-adressen bruges til at logge ind på applikationen.',
     'users_details_desc_no_email' => 'Sætter et visningsnavn for denne bruger, så andre kan genkende dem.',
@@ -175,10 +180,10 @@ return [
     'users_delete_named' => 'Slet bruger :userName',
     'users_delete_warning' => 'Dette vil helt slette denne bruger med navnet \':userName\' fra systemet.',
     'users_delete_confirm' => 'Er du sikker på, at du vil slette denne bruger?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'Overfør ejerskab',
+    'users_migrate_ownership_desc' => 'Vælg en bruger her, hvis du vil have en anden bruger til at blive ejer af alle elementer, der i øjeblikket ejes af denne bruger.',
+    'users_none_selected' => 'Ingen bruger valgt',
+    'users_delete_success' => 'Brugeren blev fjernet',
     'users_edit' => 'Rediger bruger',
     'users_edit_profile' => 'Rediger profil',
     'users_edit_success' => 'Bruger suscesfuldt opdateret',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Catalansk',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'Hebraisk',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 7647132ca114134ac8370228802d2c06edd27730..6c11f2e0fe4f3d938cd6b87a2903a6c3ec31c173 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute skal mindst være :min tegn.',
         'array'   => ':attribute skal have mindst :min elementer.',
     ],
-    'no_double_extension'  => ':attribute må kun indeholde én filtype.',
     'not_in'               => 'Den valgte :attribute er ikke gyldig.',
     'not_regex'            => ':attribute-formatet er ugyldigt.',
     'numeric'              => ':attribute skal være et tal.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => ':attribute skal udfyldes når :values ikke er udfyldt.',
     'required_without_all' => ':attribute skal udfyldes når ingen af :values er udfyldt.',
     'same'                 => ':attribute og :other skal være ens.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => 'Det angivne link kan være usikkert.',
     'size'                 => [
         'numeric' => ':attribute skal være :size.',
         'file'    => ':attribute skal være :size kilobytes.',
index fc44a9250839eb2dc54c4ab8726d8876c2d72307..fb9e91e438134e4a95ffdb3449bf5eb0e0b43b05 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Aufsteigend sortieren',
     'sort_descending' => 'Absteigend sortieren',
     'sort_name' => 'Name',
+    'sort_default' => 'Standard',
     'sort_created_at' => 'Erstellungsdatum',
     'sort_updated_at' => 'Aktualisierungsdatum',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Brotkrumen',
 
     // Header
+    'header_menu_expand' => 'Header-Menü erweitern',
     'profile_menu' => 'Profilmenü',
     'view_profile' => 'Profil ansehen',
     'edit_profile' => 'Profil bearbeiten',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Tab: Sekundäre Informationen anzeigen',
     'tab_content' => 'Inhalt',
+    'tab_content_label' => 'Tab: Hauptinhalt anzeigen',
 
     // Email Content
     'email_action_help' => 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffnen Sie folgende URL in Ihrem Browser:',
     'email_rights' => 'Alle Rechte vorbehalten',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Datenschutzbestimmungen',
+    'terms_of_service' => 'Allgemeine Geschäftsbedingungen',
 ];
index 2ea11a6cf979faed1f6e4c77aec02d6fe31ed722..22319d30da94a92e6344a4bd6f21052a29817271 100644 (file)
@@ -22,14 +22,14 @@ return [
     'meta_created_name' => 'Erstellt: :timeLength von :user',
     'meta_updated' => 'Zuletzt aktualisiert: :timeLength',
     'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Im Besitz von :user',
     'entity_select' => 'Eintrag auswählen',
     'images' => 'Bilder',
     'my_recent_drafts' => 'Meine kürzlichen Entwürfe',
     'my_recently_viewed' => 'Kürzlich von mir angesehen',
-    'no_pages_viewed' => 'Sie haben bisher keine Seiten angesehen.',
-    'no_pages_recently_created' => 'Sie haben bisher keine Seiten angelegt.',
-    'no_pages_recently_updated' => 'Sie haben bisher keine Seiten aktualisiert.',
+    'no_pages_viewed' => 'Sie haben bisher keine Seiten angesehen',
+    'no_pages_recently_created' => 'Sie haben bisher keine Seiten angelegt',
+    'no_pages_recently_updated' => 'Sie haben bisher keine Seiten aktualisiert',
     'export' => 'Exportieren',
     'export_html' => 'HTML-Datei',
     'export_pdf' => 'PDF-Datei',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Wenn individuelle Berechtigungen aktiviert werden, überschreiben diese Einstellungen durch Rollen zugewiesene Berechtigungen.',
     'permissions_enable' => 'Individuelle Berechtigungen aktivieren',
     'permissions_save' => 'Berechtigungen speichern',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Besitzer',
 
     // Search
     'search_results' => 'Suchergebnisse',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Berechtigungen gesetzt',
     'search_created_by_me' => 'Von mir erstellt',
     'search_updated_by_me' => 'Von mir aktualisiert',
+    'search_owned_by_me' => 'Besitzt von mir',
     'search_date_options' => 'Datums Optionen',
     'search_updated_before' => 'Aktualisiert vor',
     'search_updated_after' => 'Aktualisiert nach',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Seitenversionen',
     'pages_revisions_named' => 'Seitenversionen von ":pageName"',
     'pages_revision_named' => 'Seitenversion von ":pageName"',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Wiederhergestellt von #:id; :summary',
     'pages_revisions_created_by' => 'Erstellt von',
     'pages_revisions_date' => 'Versionsdatum',
     'pages_revisions_number' => '#',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Sind Sie sicher, dass Sie diese Revision wiederherstellen wollen? Der aktuelle Seiteninhalt wird ersetzt.',
     'revision_delete_success' => 'Revision gelöscht',
     'revision_cannot_delete_latest' => 'Die letzte Version kann nicht gelöscht werden.'
-];
\ No newline at end of file
+];
index 32be9b248f5933fe8942701363d12b3f15249a78..c1da9639fc1fa89043b32e014ca8c04f19c13613 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Seite nicht gefunden',
     'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Sie angefordert haben, wurde nicht gefunden.',
     'sorry_page_not_found_permission_warning' => 'Wenn Sie erwartet haben, dass diese Seite existiert, haben Sie möglicherweise nicht die Berechtigung, sie anzuzeigen.',
+    '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' => 'Zurück zur Startseite',
     'error_occurred' => 'Es ist ein Fehler aufgetreten',
     'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
index 60d4361d9e851225b235b46315ab0070f185ce34..71276c034dfbbd833db16d63593f2ebac113450a 100644 (file)
@@ -39,6 +39,11 @@ Wenn Sie nicht eingeben, wird die Anwendung auf die Standardfarbe zurückgesetzt
     'app_homepage' => 'Startseite der Anwendung',
     'app_homepage_desc' => 'Wählen Sie eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.',
     'app_homepage_select' => 'Wählen Sie eine Seite aus',
+    'app_footer_links' => 'Fußzeilen-Links',
+    'app_footer_links_desc' => 'Fügen Sie Links hinzu, die innerhalb der Seitenfußzeile angezeigt werden. Diese werden am unteren Ende der meisten Seiten angezeigt, einschließlich derjenigen, die keinen Login benötigen. Sie können die Bezeichnung "trans::<key>" verwenden, um systemdefinierte Übersetzungen zu verwenden. Beispiel: Mit "trans::common.privacy_policy" wird der übersetzte Text "Privacy Policy" bereitgestellt, und "trans::common.terms_of_service" liefert den übersetzten Text "Terms of Service".',
+    'app_footer_links_label' => 'Link-Label',
+    'app_footer_links_url' => 'Link-URL',
+    'app_footer_links_add' => 'Fußzeilen-Link hinzufügen',
     'app_disable_comments' => 'Kommentare deaktivieren',
     'app_disable_comments_toggle' => 'Kommentare deaktivieren',
     'app_disable_comments_desc' => 'Deaktiviert Kommentare über alle Seiten in der Anwendung. Vorhandene Kommentare werden nicht angezeigt.',
@@ -71,7 +76,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
     'maint' => 'Wartung',
     'maint_image_cleanup' => 'Bilder bereinigen',
     'maint_image_cleanup_desc' => "Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstellen Sie vor dem Start ein Backup Ihrer Datenbank und Bilder.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Lösche auch Bilder, die nur in alten Seitenüberarbeitungen vorhanden sind',
     'maint_image_cleanup_run' => 'Reinigung starten',
     'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchten Sie diese Bilder löschen?',
     'maint_image_cleanup_success' => ':count eventuell unbenutze Bilder wurden gefunden und gelöscht.',
@@ -178,10 +183,10 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
     'users_delete_named' => 'Benutzer ":userName" löschen',
     'users_delete_warning' => 'Der Benutzer ":userName" wird aus dem System gelöscht.',
     'users_delete_confirm' => 'Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'Besitz migrieren',
+    'users_migrate_ownership_desc' => 'Wählen Sie hier einen Benutzer, wenn Sie möchten, dass ein anderer Benutzer der Besitzer aller Einträge wird, die diesem Benutzer derzeit gehören.',
+    'users_none_selected' => 'Kein Benutzer ausgewählt',
+    'users_delete_success' => 'Benutzer erfolgreich entfernt',
     'users_edit' => 'Benutzer bearbeiten',
     'users_edit_profile' => 'Profil bearbeiten',
     'users_edit_success' => 'Benutzer erfolgreich aktualisisert',
@@ -229,6 +234,8 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bulgarisch',
+        'bs' => 'Bosanski',
+        'ca' => 'Katalanisch',
         'cs' => 'Česky',
         'da' => 'Dänisch',
         'de' => 'Deutsch (Sie)',
@@ -238,12 +245,15 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
         'fr' => 'Français',
         'he' => 'Hebräisch',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 8b1febc85d8da7078606d6c0e76f8f8a6364db2e..50f8a76a3e3817efd1ad4aedcc2ce41d0ecb734d 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute muss mindestens :min Zeichen lang sein.',
         'array'   => ':attribute muss mindesten :min Elemente enthalten.',
     ],
-    'no_double_extension'  => ':attribute darf nur eine gültige Dateiendung',
     'not_in'               => ':attribute ist ungültig.',
     'not_regex'            => ':attribute ist kein valides Format.',
     'numeric'              => ':attribute muss eine Zahl sein.',
index 01c54bf4f714b3ba616da646e72d32115245653e..50bc29646fed9e5368a1739cfa4204f86326c1e6 100644 (file)
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'kommentiert',
-    'permissions_update'          => 'hat die Berechtigungen aktualisiert',
+    'permissions_update'          => 'aktualisierte Berechtigungen',
 ];
index c18f786f61c495c7728372ea4a55861ad97c3dd9..84733659ee4503b72fe1c5167c6a7ff576bc8732 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Aufsteigend sortieren',
     'sort_descending' => 'Absteigend sortieren',
     'sort_name' => 'Name',
+    'sort_default' => 'Standard',
     'sort_created_at' => 'Erstellungsdatum',
     'sort_updated_at' => 'Aktualisierungsdatum',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Brotkrumen',
 
     // Header
+    'header_menu_expand' => 'Header-Menü erweitern',
     'profile_menu' => 'Profilmenü',
     'view_profile' => 'Profil ansehen',
     'edit_profile' => 'Profil bearbeiten',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Tab: Sekundäre Informationen anzeigen',
     'tab_content' => 'Inhalt',
+    'tab_content_label' => 'Tab: Hauptinhalt anzeigen',
 
     // Email Content
     'email_action_help' => 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffne die folgende URL in Deinem Browser:',
     'email_rights' => 'Alle Rechte vorbehalten',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Datenschutzerklärung',
+    'terms_of_service' => 'Allgemeine Geschäftsbedingungen',
 ];
index b899324732faebf2f0497981f879e586a921ed3b..50dea6cf3f857e07b11fc5b1234dffa46a5673a4 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Erstellt: :timeLength von :user',
     'meta_updated' => 'Zuletzt aktualisiert: :timeLength',
     'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Im Besitz von :user',
     'entity_select' => 'Eintrag auswählen',
     'images' => 'Bilder',
     'my_recent_drafts' => 'Meine kürzlichen Entwürfe',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Wenn individuelle Berechtigungen aktiviert werden, überschreiben diese Einstellungen durch Rollen zugewiesene Berechtigungen.',
     'permissions_enable' => 'Individuelle Berechtigungen aktivieren',
     'permissions_save' => 'Berechtigungen speichern',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Besitzer',
 
     // Search
     'search_results' => 'Suchergebnisse',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Berechtigungen gesetzt',
     'search_created_by_me' => 'Von mir erstellt',
     'search_updated_by_me' => 'Von mir aktualisiert',
+    'search_owned_by_me' => 'Besitzt von mir',
     'search_date_options' => 'Datums Optionen',
     'search_updated_before' => 'Aktualisiert vor',
     'search_updated_after' => 'Aktualisiert nach',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Neues Kapitel anlegen',
     'chapters_delete' => 'Kapitel entfernen',
     'chapters_delete_named' => 'Kapitel ":chapterName" entfernen',
-    'chapters_delete_explain' => 'Dies löscht das Kapitel mit dem Namen \':chapterName\'. Alle Seiten, die innerhalb dieses Kapitels existieren, werden ebenfalls gelöscht.',
+    'chapters_delete_explain' => 'Hiermit löschen Sie das Kapitel mit dem Namen \':chapterName\'. Alle Seiten, die innerhalb dieses Kapitels existieren, werden ebenfalls gelöscht.',
     'chapters_delete_confirm' => 'Bist Du sicher, dass Du dieses Kapitel löschen möchtest?',
     'chapters_edit' => 'Kapitel bearbeiten',
     'chapters_edit_named' => 'Kapitel ":chapterName" bearbeiten',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Seitenversionen',
     'pages_revisions_named' => 'Seitenversionen von ":pageName"',
     'pages_revision_named' => 'Seitenversion von ":pageName"',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Wiederhergestellt von #:id; :summary',
     'pages_revisions_created_by' => 'Erstellt von',
     'pages_revisions_date' => 'Versionsdatum',
     'pages_revisions_number' => '#',
@@ -268,7 +269,7 @@ return [
     'attachments_link_url' => 'Link zu einer Datei',
     'attachments_link_url_hint' => 'URL einer Seite oder Datei',
     'attach' => 'Hinzufügen',
-    'attachments_insert_link' => 'Link zum Anhang auf Seite einfügen',
+    'attachments_insert_link' => 'Anhangslink zur Seite hinzufügen',
     'attachments_edit_file' => 'Datei bearbeiten',
     'attachments_edit_file_name' => 'Dateiname',
     'attachments_edit_drop_upload' => 'Ziehe Dateien hierher, um diese hochzuladen und zu überschreiben',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Sind Sie sicher, dass Sie diese Revision wiederherstellen wollen? Der aktuelle Seiteninhalt wird ersetzt.',
     'revision_delete_success' => 'Revision gelöscht',
     'revision_cannot_delete_latest' => 'Die letzte Version kann nicht gelöscht werden.'
-];
\ No newline at end of file
+];
index 656c3c23647133f1d12b38e0714bc68dd48f9a7c..15e4ba51d4b81ef5335be0fe5c73294bd7873c6f 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Seite nicht gefunden',
     'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.',
     'sorry_page_not_found_permission_warning' => 'Wenn du erwartet hast, dass diese Seite existiert, hast du möglicherweise nicht die Berechtigung, sie anzuzeigen.',
+    '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' => 'Zurück zur Startseite',
     'error_occurred' => 'Es ist ein Fehler aufgetreten',
     'app_down' => ':appName befindet sich aktuell im Wartungsmodus.',
index d0caef9ee9b81422ff56865d259e1a918c866f1e..440f54d965eebb462ea94ca66380c70f38caaad6 100644 (file)
@@ -39,6 +39,11 @@ Wenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt
     'app_homepage' => 'Startseite der Anwendung',
     'app_homepage_desc' => 'Wähle eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.',
     'app_homepage_select' => 'Wählen Sie eine Seite aus',
+    'app_footer_links' => 'Fußzeilen-Links',
+    'app_footer_links_desc' => 'Fügen Sie Links hinzu, die innerhalb der Seitenfußzeile angezeigt werden. Diese werden am unteren Ende der meisten Seiten angezeigt, einschließlich derjenigen, die keinen Login benötigen. Sie können die Bezeichnung "trans::<key>" verwenden, um systemdefinierte Übersetzungen zu verwenden. Beispiel: Mit "trans::common.privacy_policy" wird der übersetzte Text "Privacy Policy" bereitgestellt, und "trans::common.terms_of_service" liefert den übersetzten Text "Terms of Service".',
+    'app_footer_links_label' => 'Link-Label',
+    'app_footer_links_url' => 'Link-URL',
+    'app_footer_links_add' => 'Fußzeilenlink hinzufügen',
     'app_disable_comments' => 'Kommentare deaktivieren',
     'app_disable_comments_toggle' => 'Kommentare deaktivieren',
     'app_disable_comments_desc' => 'Deaktiviert Kommentare über alle Seiten in der Anwendung. Vorhandene Kommentare werden nicht angezeigt.',
@@ -71,7 +76,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
     'maint' => 'Wartung',
     'maint_image_cleanup' => 'Bilder bereinigen',
     'maint_image_cleanup_desc' => "Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstelle vor dem Start ein Backup Deiner Datenbank und Bilder.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Lösche auch Bilder, die nur in alten Seitenüberarbeitungen vorhanden sind',
     'maint_image_cleanup_run' => 'Reinigung starten',
     'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?',
     'maint_image_cleanup_success' => ':count eventuell unbenutze Bilder wurden gefunden und gelöscht.',
@@ -83,38 +88,38 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
     'maint_send_test_email_mail_subject' => 'Test E-Mail',
     'maint_send_test_email_mail_greeting' => 'E-Mail-Versand scheint zu funktionieren!',
     'maint_send_test_email_mail_text' => 'Glückwunsch! Da du diese E-Mail Benachrichtigung erhalten hast, scheinen deine E-Mail-Einstellungen korrekt konfiguriert zu sein.',
-    'maint_recycle_bin_desc' => 'Gelöschte Regale, Bücher, Kapitel & Seiten werden in den Papierkorb verschoben, so dass sie wiederhergestellt oder dauerhaft gelöscht werden können. Ältere Gegenstände im Papierkorb können, in Abhängigkeit von der Systemkonfiguration, nach einer Weile automatisch entfernt werden.',
+    'maint_recycle_bin_desc' => 'Gelöschte Regale, Bücher, Kapitel & Seiten werden in den Papierkorb verschoben, so dass sie wiederhergestellt oder dauerhaft gelöscht werden können. Ältere Einträge im Papierkorb können, in Abhängigkeit von der Systemkonfiguration, nach einer Weile automatisch entfernt werden.',
     'maint_recycle_bin_open' => 'Papierkorb öffnen',
 
     // Recycle Bin
     'recycle_bin' => 'Papierkorb',
-    'recycle_bin_desc' => 'Hier können Sie gelöschte Elemente wiederherstellen oder sie dauerhaft aus dem System entfernen. Diese Liste ist nicht gefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, wo Berechtigungsfilter angewendet werden.',
-    'recycle_bin_deleted_item' => 'Gelöschtes Element',
+    'recycle_bin_desc' => 'Hier können Sie gelöschte Einträge wiederherstellen oder sie dauerhaft aus dem System entfernen. Diese Liste ist nicht gefiltert, im Gegensatz zu ähnlichen Aktivitätslisten im System, wo Berechtigungsfilter angewendet werden.',
+    'recycle_bin_deleted_item' => 'Gelöschter Eintrag',
     'recycle_bin_deleted_by' => 'Gelöscht von',
     'recycle_bin_deleted_at' => 'Löschzeitpunkt',
     'recycle_bin_permanently_delete' => 'Dauerhaft löschen',
     'recycle_bin_restore' => 'Wiederherstellen',
     'recycle_bin_contents_empty' => 'Der Papierkorb ist derzeit leer',
     'recycle_bin_empty' => 'Papierkorb leeren',
-    'recycle_bin_empty_confirm' => 'Dies wird alle Gegenstände im Papierkorb dauerhaft entfernen, einschließlich der Inhalte, die darin enthalten sind. Sind Sie sicher, dass Sie den Papierkorb leeren möchten?',
-    'recycle_bin_destroy_confirm' => 'Diese Aktion wird dieses Element zusammen mit allen unten aufgeführten Unterelementen dauerhaft aus dem System löschen und Sie werden nicht in der Lage sein, diesen Inhalt wiederherzustellen. Sind Sie sicher, dass Sie dieses Element endgültig löschen möchten?',
-    'recycle_bin_destroy_list' => 'Zu löschende Elemente',
-    'recycle_bin_restore_list' => 'Zu wiederherzustellende Elemente',
-    'recycle_bin_restore_confirm' => 'Mit dieser Aktion wird das gelöschte Element einschließlich aller untergeordneten Elemente an seinen ursprünglichen Ort wiederherstellen. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch das übergeordnete Element wiederhergestellt werden.',
-    'recycle_bin_restore_deleted_parent' => 'Das übergeordnete Elements wurde ebenfalls gelöscht. Dieses Element wird weiterhin als gelöscht zählen, bis auch das übergeordnete Element wiederhergestellt wurde.',
-    'recycle_bin_destroy_notification' => ':count Elemente wurden aus dem Papierkorb gelöscht.',
-    'recycle_bin_restore_notification' => ':count Elemente wurden aus dem Papierkorb wiederhergestellt.',
+    'recycle_bin_empty_confirm' => 'Dies wird alle Einträge im Papierkorb dauerhaft entfernen, einschließlich der Inhalte, die darin enthalten sind. Sind Sie sicher, dass Sie den Papierkorb leeren möchten?',
+    'recycle_bin_destroy_confirm' => 'Diese Aktion wird diesen Eintrag zusammen mit allen unten aufgeführten Untereinträgen dauerhaft aus dem System löschen und Sie werden nicht in der Lage sein, diesen Inhalt wiederherzustellen. Sind Sie sicher, dass Sie diesen Eintrag endgültig löschen möchten?',
+    'recycle_bin_destroy_list' => 'Zu löschende Einträge',
+    'recycle_bin_restore_list' => 'Wiederherzustellende Einträge',
+    'recycle_bin_restore_confirm' => 'Mit dieser Aktion wird der gelöschte Eintrag einschließlich aller untergeordneten Einträge an seinen ursprünglichen Ort wiederherstellen. Wenn der ursprüngliche Ort gelöscht wurde und sich nun im Papierkorb befindet, muss auch der übergeordnete Eintrag wiederhergestellt werden.',
+    'recycle_bin_restore_deleted_parent' => 'Der übergeordnete Eintrag wurde ebenfalls gelöscht. Dieser Eintrag wird weiterhin als gelöscht zählen, bis auch der übergeordnete Eintrag wiederhergestellt wurde.',
+    'recycle_bin_destroy_notification' => ':count Einträge wurden aus dem Papierkorb gelöscht.',
+    'recycle_bin_restore_notification' => ':count Einträge wurden aus dem Papierkorb wiederhergestellt.',
 
     // Audit Log
-    'audit' => 'Audit-Protokoll',
+    'audit' => 'Änderungsprotokoll',
     'audit_desc' => 'Dieses Audit-Protokoll zeigt eine Liste der Aktivitäten an, welche vom System protokolliert werden. Im Gegensatz zu den anderen Aktivitätslisten im System, bei denen Berechtigungen angewendet werden, ist diese Liste ungefiltert.',
     'audit_event_filter' => 'Ereignisfilter',
     'audit_event_filter_no_filter' => 'Kein Filter',
-    'audit_deleted_item' => 'Gelöschtes Objekt',
+    'audit_deleted_item' => 'Gelöschtes Element',
     'audit_deleted_item_name' => 'Name: :name',
     'audit_table_user' => 'Benutzer',
     'audit_table_event' => 'Ereignis',
-    'audit_table_related' => 'Verknüpftes Element oder Detail',
+    'audit_table_related' => 'Verknüpfter Eintrag oder Detail',
     'audit_table_date' => 'Aktivitätsdatum',
     'audit_date_from' => 'Zeitraum von',
     'audit_date_to' => 'Zeitraum bis',
@@ -178,10 +183,10 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
     'users_delete_named' => 'Benutzer ":userName" löschen',
     'users_delete_warning' => 'Der Benutzer ":userName" wird aus dem System gelöscht.',
     'users_delete_confirm' => 'Bist Du sicher, dass Du diesen Benutzer löschen möchtest?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'Besitz migrieren',
+    'users_migrate_ownership_desc' => 'Wählen Sie hier einen Benutzer, wenn Sie möchten, dass ein anderer Benutzer der Besitzer aller Einträge wird, die diesem Benutzer derzeit gehören.',
+    'users_none_selected' => 'Kein Benutzer ausgewählt',
+    'users_delete_success' => 'Benutzer erfolgreich entfernt',
     'users_edit' => 'Benutzer bearbeiten',
     'users_edit_profile' => 'Profil bearbeiten',
     'users_edit_success' => 'Benutzer erfolgreich aktualisisert',
@@ -229,6 +234,8 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bulgarisch',
+        'bs' => 'Bosanski',
+        'ca' => 'Katalanisch',
         'cs' => 'Česky',
         'da' => 'Dänisch',
         'de' => 'Deutsch (Sie)',
@@ -238,12 +245,15 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 24c1e8addb8e224e4ce848427a3da45a3ff49e70..42456da6eb3a13593cddbee4ea72643faad0dacf 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute muss mindestens :min Zeichen lang sein.',
         'array'   => ':attribute muss mindesten :min Elemente enthalten.',
     ],
-    'no_double_extension'  => ':attribute darf nur eine gültige Dateiendung',
     'not_in'               => ':attribute ist ungültig.',
     'not_regex'            => ':attribute ist kein gültiges Format.',
     'numeric'              => ':attribute muss eine Zahl sein.',
index fe937b061930262a060465699446647adab763d9..5917de2cfb25745e4cda5a8046f310ec19da885b 100644 (file)
@@ -43,6 +43,10 @@ return [
     'bookshelf_delete'                 => 'deleted bookshelf',
     'bookshelf_delete_notification'    => 'Bookshelf Successfully Deleted',
 
+    // Favourites
+    'favourite_add_notification' => '":name" has been added to your favourites',
+    'favourite_remove_notification' => '":name" has been removed from your favourites',
+
     // Other
     'commented_on'                => 'commented on',
     'permissions_update'          => 'updated permissions',
index e87bd11a5e343173fadf78e62a042e1ca0579a4a..e198878ad30cb0b416e448911ee2c7f527611e96 100644 (file)
@@ -40,6 +40,8 @@ return [
     'remove' => 'Remove',
     'add' => 'Add',
     'fullscreen' => 'Fullscreen',
+    'favourite' => 'Favourite',
+    'unfavourite' => 'Unfavourite',
 
     // Sort Options
     'sort_options' => 'Sort Options',
@@ -47,6 +49,7 @@ return [
     'sort_ascending' => 'Sort Ascending',
     'sort_descending' => 'Sort Descending',
     'sort_name' => 'Name',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Created Date',
     'sort_updated_at' => 'Updated Date',
 
@@ -64,6 +67,7 @@ return [
     'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profile Menu',
     'view_profile' => 'View Profile',
     'edit_profile' => 'Edit Profile',
@@ -72,9 +76,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Content',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // 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',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index 6c0980fab380d430f50d12ca8928f69be522b203..462402f33f407b458700e80e2c15f22257ceba52 100644 (file)
@@ -27,6 +27,8 @@ return [
     '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',
@@ -60,6 +62,7 @@ return [
     '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',
@@ -316,4 +319,4 @@ return [
     'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'Revision deleted',
     'revision_cannot_delete_latest' => 'Cannot delete the latest revision.'
-];
\ No newline at end of file
+];
index 79024e482ed69efa633116592f9b7c83a0bcc93a..eb8ba54ea81506519a4958769d54086cc3b3d7be 100644 (file)
@@ -83,6 +83,9 @@ return [
     '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',
index 414650d21bac0c5187afc841de93f0f0ea40aaed..ab7f153224afd41bb90e9cd54a9472895727b619 100755 (executable)
@@ -37,6 +37,11 @@ return [
     '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::<key>" 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. <br> Existing comments are not shown.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 578ea999fc31618997be7834012fb30aed3d7b1f..4031de2ae743b75bafd49195ff06b29a347cbf22 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'The :attribute must be at least :min characters.',
         'array'   => 'The :attribute must have at least :min items.',
     ],
-    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'The selected :attribute is invalid.',
     'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'The :attribute must be a number.',
index 9c6575a5381a3769119162dc292c9accbd52236d..81cec2b8151387b57221171c2c525c8a2dd0b977 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Ordenar Ascendentemente',
     'sort_descending' => 'Ordenar Descendentemente',
     'sort_name' => 'Nombre',
+    'sort_default' => 'Predeterminada',
     'sort_created_at' => 'Fecha de Creación',
     'sort_updated_at' => 'Fecha de Modificación',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Rastro de migas de pan',
 
     // Header
+    'header_menu_expand' => 'Expandir el Menú de la Cabecera',
     'profile_menu' => 'Menú de Perfil',
     'view_profile' => 'Ver Perfil',
     'edit_profile' => 'Editar Perfil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Información',
+    'tab_info_label' => 'Pestaña: Mostrar Información Secundaria',
     'tab_content' => 'Contenido',
+    'tab_content_label' => 'Pestaña: Mostrar Contenido Primario',
 
     // Email Content
     'email_action_help' => 'Si está teniendo problemas clicando en el botón ":actionText", copie y pegue la siguiente URL en su navegador web:',
     'email_rights' => 'Todos los derechos reservados',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Política de privacidad',
+    'terms_of_service' => 'Términos de Servicio',
 ];
index 5b1d3fcbab62cedd8c58580f60eade38039049e4..714f5b92890ae329ad9d216c6e6e593c376e33ab 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Permisos ajustados',
     'search_created_by_me' => 'Creadas por mí',
     'search_updated_by_me' => 'Actualizadas por mí',
+    'search_owned_by_me' => 'De mi propiedad',
     'search_date_options' => 'Opciones de fecha',
     'search_updated_before' => 'Actualizadas antes de',
     'search_updated_after' => 'Actualizadas después de',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => '¿Está seguro de que desea restaurar esta revisión? El contenido actual de la página será reemplazado.',
     'revision_delete_success' => 'Revisión eliminada',
     'revision_cannot_delete_latest' => 'No se puede eliminar la última revisión.'
-];
\ No newline at end of file
+];
index f83ec4b80d77755158d06acbd5dc5d8cf2c25681..59ad7323bafc183c42d0bc5fb50091daea298e61 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Página no encontrada',
     'sorry_page_not_found' => 'Lo sentimos, la página a la que intenta acceder no pudo ser encontrada.',
     'sorry_page_not_found_permission_warning' => 'Si esperaba que esta página existiera, puede que no tenga permiso para verla.',
+    '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' => 'Volver a la página de inicio',
     'error_occurred' => 'Ha ocurrido un error',
     'app_down' => 'La aplicación :appName se encuentra caída en este momento',
index d6488eded53f98c67521d2b7d9a8005a3f636d84..d6a208b8c1da2aa261816ee2f29776b5466955a6 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Página de inicio',
     'app_homepage_desc' => 'Elija la vista que se mostrará en la página de inicio en lugar de la vista predeterminada. Se ignorarán los permisos de la página seleccionada.',
     'app_homepage_select' => 'Elija una página',
+    'app_footer_links' => 'Enlaces de pie de página',
+    'app_footer_links_desc' => 'Añade enlaces para mostrar dentro del pie de página del sitio. Estos se mostrarán en la parte inferior de la mayoría de las páginas, incluyendo aquellas que no requieren estar registrado. Puede utilizar una etiqueta de "trans::<key>" para utilizar traducciones definidas por el sistema. Por ejemplo: el uso de "trans::common.privacy_policy" proporcionará el texto traducido "Política de privacidad" y "trans::common.terms_of_service" proporcionará el texto traducido "Términos de servicio".',
+    'app_footer_links_label' => 'Etiqueta del enlace',
+    'app_footer_links_url' => 'Dirección URL del enlace',
+    'app_footer_links_add' => 'Añadir enlace al pie de página',
     'app_disable_comments' => 'Deshabilitar Comentarios',
     'app_disable_comments_toggle' => 'Deshabilitar comentarios',
     'app_disable_comments_desc' => 'Deshabilita los comentarios en todas las páginas de la aplicación. <br> Los comentarios existentes no se muestran.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Danés',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index f6f9a1ee12bf65c98841af1aca9efb17ccad5455..450e923753b001d77f58a84a31a33c56c991dcf0 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'El :attribute debe ser al menos :min caracteres.',
         'array'   => 'El :attribute debe tener como mínimo :min items.',
     ],
-    'no_double_extension'  => 'El :attribute solo debe tener una extensión de archivo.',
     'not_in'               => 'El :attribute seleccionado es inválio.',
     'not_regex'            => 'El formato de :attribute es inválido.',
     'numeric'              => 'El :attribute debe ser numérico.',
index c6bef56bce6884c58635641f6d105495a37e4d3b..f2d54bea712cae1305a55aeb1c7783b0fb1d8995 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Orden Ascendente',
     'sort_descending' => 'Orden Descendente',
     'sort_name' => 'Nombre',
+    'sort_default' => 'Por defecto',
     'sort_created_at' => 'Fecha de creación',
     'sort_updated_at' => 'Fecha de actualización',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Miga de Pan',
 
     // Header
+    'header_menu_expand' => 'Expandir el Menú de Cabecera',
     'profile_menu' => 'Menu del Perfil',
     'view_profile' => 'Ver Perfil',
     'edit_profile' => 'Editar Perfil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Información',
+    'tab_info_label' => 'Pestaña: Mostrar Información Secundaria',
     'tab_content' => 'Contenido',
+    'tab_content_label' => 'Pestaña: Mostrar Contenido Primario',
 
     // Email Content
     'email_action_help' => 'Si está teniendo problemas haga click en el botón ":actionText", copie y pegue la siguiente URL en su navegador web:',
     'email_rights' => 'Todos los derechos reservados',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Política de privacidad',
+    'terms_of_service' => 'Términos de Servicio',
 ];
index 093641a362346804a0a82b0205d0897e7b853aca..ef7e6d25a1540e547a261b697a8415672e37402a 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Permisos establecidos',
     'search_created_by_me' => 'Creado por mí',
     'search_updated_by_me' => 'Actualizado por mí',
+    'search_owned_by_me' => 'De mi propiedad',
     'search_date_options' => 'Opciones de fecha',
     'search_updated_before' => 'Actualizado antes de',
     'search_updated_after' => 'Actualizado después de',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Crear nuevo capítulo',
     'chapters_delete' => 'Borrar capítulo',
     'chapters_delete_named' => 'Borrar capítulo :chapterName',
-    'chapters_delete_explain' => 'Esto eliminará el capítulo con el nombre \':chapterName\'. También se eliminarán todas las páginas que existen dentro de este capítulo.',
+    'chapters_delete_explain' => 'Esta acción eliminará el capítulo con el nombre \':chapterName\'. Todas las páginas que existen dentro del capítulo también se eliminarán.',
     'chapters_delete_confirm' => '¿Está seguro de borrar este capítulo?',
     'chapters_edit' => 'Editar capítulo',
     'chapters_edit_named' => 'Editar capítulo :chapterName',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Revisiones de página',
     'pages_revisions_named' => 'Revisiones de página para :pageName',
     'pages_revision_named' => 'Revisión de ágina para :pageName',
-    'pages_revision_restored_from' => 'Restaurado de #:id; :summary',
+    'pages_revision_restored_from' => 'Restaurado desde #:id; :summary',
     'pages_revisions_created_by' => 'Creado por',
     'pages_revisions_date' => 'Fecha de revisión',
     'pages_revisions_number' => '#',
@@ -280,7 +281,7 @@ return [
     'attachments_link_attached' => 'Enlace agregado exitosamente a la página',
     'templates' => 'Plantillas',
     'templates_set_as_template' => 'La Página es una plantilla',
-    'templates_explain_set_as_template' => 'Puede establecer esta página como plantilla para que el contenido pueda utilizarse para al crear otras páginas. Otris usuarios podrán utilizar esta plantilla si tienen permisos para ver de esta página.',
+    'templates_explain_set_as_template' => 'Puede establecer esta página como plantilla para que el contenido pueda utilizarse al crear otras páginas. Otros usuarios podrán utilizar esta plantilla si tienen permisos para ver de esta página.',
     'templates_replace_content' => 'Reemplazar el contenido de la página',
     'templates_append_content' => 'Incorporar al fina del contenido de la página',
     'templates_prepend_content' => 'Incorporar al principio del contenido de la página',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => '¿Está seguro de que quiere restaurar esta revisión? Se reemplazará el contenido de la página actual.',
     'revision_delete_success' => 'Revisión eliminada',
     'revision_cannot_delete_latest' => 'No se puede eliminar la última revisión.'
-];
\ No newline at end of file
+];
index 41719a5bcb3c035c553eada751bd4e93eebf8a48..f96e29db148913798fbc4d181b297b093a796d7b 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Página no encontrada',
     'sorry_page_not_found' => 'Lo sentimos, la página que intenta acceder no pudo ser encontrada.',
     'sorry_page_not_found_permission_warning' => 'Si esperaba que esta página existiera, puede que no tenga permiso para verla.',
+    'image_not_found' => 'No se encuentra la imagen',
+    'image_not_found_subtitle' => 'Lo siento, no se pudo encontrar la imagen que busca.',
+    'image_not_found_details' => 'Si esperaba que esta imagen exista es probable que se haya eliminado.',
     'return_home' => 'Volver al home',
     'error_occurred' => 'Ha ocurrido un error',
     'app_down' => 'La aplicación :appName se encuentra caída en este momento',
index 8ab0941839a77c03e0334d7da1f5ad18fb1a3883..cf250d79e670373fc39a0875f6ae38199a4dae7a 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Página de inicio de la Aplicación',
     'app_homepage_desc' => 'Seleccione una página de inicio para mostrar en lugar de la vista por defecto. Se ignoran los permisos de página para las páginas seleccionadas.',
     'app_homepage_select' => 'Seleccione una página',
+    'app_footer_links' => 'Enlaces de pie de página',
+    'app_footer_links_desc' => 'Añade enlaces para mostrar dentro del pie de página del sitio. Estos se mostrarán en la parte inferior de la mayoría de las páginas, incluyendo aquellas que no requieren estar registrado. Puede utilizar una etiqueta de "trans::<key>" para utilizar traducciones definidas por el sistema. Por ejemplo: el uso de "trans::common.privacy_policy" proporcionará el texto traducido "Política de privacidad" y "trans::common.terms_of_service" proporcionará el texto traducido "Términos de servicio".',
+    'app_footer_links_label' => 'Etiqueta del enlace',
+    'app_footer_links_url' => 'Dirección URL del enlace',
+    'app_footer_links_add' => 'Añadir enlace al pie de página',
     'app_disable_comments' => 'Deshabilitar comentarios',
     'app_disable_comments_toggle' => 'Deshabilitar comentarios',
     'app_disable_comments_desc' => 'Deshabilitar comentarios en todas las páginas de la aplicación. Los comentarios existentes no se muestran.',
@@ -68,7 +73,7 @@ return [
     'maint' => 'Mantenimiento',
     'maint_image_cleanup' => 'Limpiar imágenes',
     'maint_image_cleanup_desc' => "Analizar contenido de páginas y revisiones para detectar cuáles imágenes y dibujos están en uso y cuáles son redundantes. Asegúrese de crear un respaldo completo de imágenes y base de datos antes de ejecutar esta tarea.",
-    'maint_delete_images_only_in_revisions' => 'Elimina también imágenes que sólo existen en antiguas revisiones de páginas',
+    'maint_delete_images_only_in_revisions' => 'También elimina imágenes que sólo existen en antiguas revisiones de páginas',
     'maint_image_cleanup_run' => 'Ejecutar limpieza',
     'maint_image_cleanup_warning' => 'Se encontraron :count imágenes pontencialmente sin uso. Está seguro de que quiere eliminarlas?',
     'maint_image_cleanup_success' => 'Se encontraron y se eliminaron :count imágenes pontencialmente sin uso!',
@@ -94,13 +99,13 @@ return [
     'recycle_bin_contents_empty' => 'La papelera de reciclaje está vacía',
     'recycle_bin_empty' => 'Vaciar Papelera de reciclaje',
     'recycle_bin_empty_confirm' => 'Esto destruirá permanentemente todos los elementos de la papelera de reciclaje, incluyendo el contenido existente en cada elemento. ¿Está seguro de que desea vaciar la papelera de reciclaje?',
-    'recycle_bin_destroy_confirm' => 'Esta acción eliminará permanentemente este elemento del sistema, junto con los elementos secundarios listados a continuación, y no podrá restaurar este contenido de nuevo. ¿Está seguro de que desea eliminar permanentemente este elemento?',
-    'recycle_bin_destroy_list' => 'Elementos a eliminar',
+    'recycle_bin_destroy_confirm' => 'Esta acción eliminará permanentemente este elemento del sistema, junto con los elementos secundarios listados a continuación, y no podrá restaurar este contenido. ¿Está seguro de que desea eliminar permanentemente este elemento?',
+    'recycle_bin_destroy_list' => 'Elementos a destruir',
     'recycle_bin_restore_list' => 'Elementos a restaurar',
     'recycle_bin_restore_confirm' => 'Esta acción restaurará el elemento eliminado, incluyendo cualquier elemento secundario, a su ubicación original. Si la ubicación original ha sido eliminada, y ahora está en la papelera de reciclaje, el elemento padre también tendrá que ser restaurado.',
     'recycle_bin_restore_deleted_parent' => 'El padre de este elemento también ha sido eliminado. Estos permanecerán eliminados hasta que el padre también sea restaurado.',
-    'recycle_bin_destroy_notification' => 'Eliminados :count artículos de la papelera de reciclaje.',
-    'recycle_bin_restore_notification' => 'Restaurados :count artículos desde la papelera de reciclaje.',
+    'recycle_bin_destroy_notification' => 'Eliminados :count elementos de la papelera de reciclaje.',
+    'recycle_bin_restore_notification' => 'Restaurados :count elementos desde la papelera de reciclaje.',
 
     // Audit Log
     'audit' => 'Registro de Auditoría',
@@ -178,8 +183,8 @@ return [
     'users_delete_confirm' => '¿Está seguro que desea borrar este usuario?',
     'users_migrate_ownership' => 'Cambiar Propietario',
     'users_migrate_ownership_desc' => 'Seleccione un usuario aquí si desea que otro usuario se convierta en el dueño de todos los elementos que actualmente son propiedad de este usuario.',
-    'users_none_selected' => 'Usuario no seleccionado',
-    'users_delete_success' => 'El usuario se ha eliminado correctamente',
+    'users_none_selected' => 'No hay usuario seleccionado',
+    'users_delete_success' => 'El usuario fue eliminado correctamente',
     'users_edit' => 'Editar Usuario',
     'users_edit_profile' => 'Editar perfil',
     'users_edit_success' => 'Usuario actualizado',
@@ -227,6 +232,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Danés',
         'de' => 'Deutsch (Sie)',
@@ -236,12 +243,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 7afd2bb461fbbe5c16852b70525c8e849ab03bdb..c3f5d83dd69c684752fb3cb7e8071d50ac43b9c6 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute debe ser al menos :min caracteres.',
         'array'   => ':attribute debe tener como mínimo :min items.',
     ],
-    'no_double_extension'  => 'El :attribute debe tener una única extensión de archivo.',
     'not_in'               => ':attribute seleccionado es inválido.',
     'not_regex'            => 'El formato de :attribute es inválido.',
     'numeric'              => ':attribute debe ser numérico.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => ':attribute es requerido cuando no se encuentre entre los valores :values.',
     'required_without_all' => ':attribute es requerido cuando ninguno de los valores :values están presentes.',
     'same'                 => ':attribute y :other deben coincidir.',
-    'safe_url'             => 'El enlace proporcionado puede no ser seguro.',
+    'safe_url'             => 'El enlace provisto puede ser inseguro.',
     'size'                 => [
         'numeric' => ':attribute debe ser :size.',
         'file'    => ':attribute debe ser :size kilobytes.',
index 4cac54b2a706efa35cb8873ebc247c20420e65b5..fe937b061930262a060465699446647adab763d9 100644 (file)
@@ -45,4 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'commented on',
+    'permissions_update'          => 'updated permissions',
 ];
index e87bd11a5e343173fadf78e62a042e1ca0579a4a..855c1c807488a8af9860f35f24004e0bb055edc1 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Sort Ascending',
     'sort_descending' => 'Sort Descending',
     'sort_name' => 'Name',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Created Date',
     'sort_updated_at' => 'Updated Date',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profile Menu',
     'view_profile' => 'View Profile',
     'edit_profile' => 'Edit Profile',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Content',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // 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',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index f64867a56c31736a1730d58c51f3fe0c088364d1..1661bae57cae5184af9381b15f79fe0c301d003a 100644 (file)
@@ -22,6 +22,7 @@ return [
     'meta_created_name' => 'Created :timeLength by :user',
     'meta_updated' => 'Updated :timeLength',
     'meta_updated_name' => 'Updated :timeLength by :user',
+    'meta_owned_name' => 'Owned by :user',
     'entity_select' => 'Entity Select',
     'images' => 'Images',
     'my_recent_drafts' => 'My Recent Drafts',
@@ -39,6 +40,7 @@ return [
     'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.',
     'permissions_enable' => 'Enable Custom Permissions',
     'permissions_save' => 'Save Permissions',
+    'permissions_owner' => 'Owner',
 
     // Search
     'search_results' => 'Search Results',
@@ -58,6 +60,7 @@ return [
     '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',
@@ -146,7 +149,7 @@ return [
     '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 will be removed and added directly to the parent book.',
+    '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',
@@ -208,6 +211,7 @@ return [
     'pages_revisions' => 'Page Revisions',
     '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',
     'pages_revisions_number' => '#',
@@ -313,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'Revision deleted',
     'revision_cannot_delete_latest' => 'Cannot delete the latest revision.'
-];
\ No newline at end of file
+];
index 79024e482ed69efa633116592f9b7c83a0bcc93a..eb8ba54ea81506519a4958769d54086cc3b3d7be 100644 (file)
@@ -83,6 +83,9 @@ return [
     '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',
index 2bd314cf0f28561f9a2f296b373483df42480f89..ab7f153224afd41bb90e9cd54a9472895727b619 100644 (file)
@@ -37,6 +37,11 @@ return [
     '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::<key>" 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. <br> Existing comments are not shown.',
@@ -68,7 +73,7 @@ return [
     '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_image_cleanup_ignore_revisions' => 'Ignore images in revisions',
+    '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!',
@@ -80,6 +85,27 @@ return [
     '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',
+
+    // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
+    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
 
     // Audit Log
     'audit' => 'Audit Log',
@@ -90,7 +116,7 @@ return [
     'audit_deleted_item_name' => 'Name: :name',
     'audit_table_user' => 'User',
     'audit_table_event' => 'Event',
-    'audit_table_item' => 'Related Item',
+    'audit_table_related' => 'Related Item or Detail',
     'audit_table_date' => 'Activity Date',
     'audit_date_from' => 'Date Range From',
     'audit_date_to' => 'Date Range To',
@@ -136,6 +162,7 @@ return [
     '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.',
@@ -153,7 +180,10 @@ return [
     '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_delete_success' => 'Users successfully removed',
+    '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_delete_success' => 'User successfully removed',
     'users_edit' => 'Edit User',
     'users_edit_profile' => 'Edit Profile',
     'users_edit_success' => 'User successfully updated',
@@ -179,7 +209,7 @@ return [
     '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_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_create_success' => 'API token successfully created',
     'user_api_token_update_success' => 'API token successfully updated',
     'user_api_token' => 'API Token',
@@ -187,8 +217,8 @@ return [
     '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_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?',
@@ -200,6 +230,9 @@ return [
     'language_select' => [
         'en' => 'English',
         'ar' => 'العربية',
+        'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -209,11 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
+        'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 76b57a2a3b58ddb8ef41e0562c5187359cc6e542..4031de2ae743b75bafd49195ff06b29a347cbf22 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'The :attribute must be at least :min characters.',
         'array'   => 'The :attribute must have at least :min items.',
     ],
-    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'The selected :attribute is invalid.',
     'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'The :attribute must be a number.',
@@ -90,6 +89,7 @@ return [
     '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.',
     'size'                 => [
         'numeric' => 'The :attribute must be :size.',
         'file'    => 'The :attribute must be :size kilobytes.',
index 6571db60c6d37485f5476e9123da9b5ccfb00b43..5142cdff17608ca2c8e8d5a6621c8deb551841f0 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Tri ascendant',
     'sort_descending' => 'Tri descendant',
     'sort_name' => 'Nom',
+    'sort_default' => 'Défaut',
     'sort_created_at' => 'Date de création',
     'sort_updated_at' => 'Date de mise à jour',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Fil d\'Ariane',
 
     // Header
+    'header_menu_expand' => 'Développer le menu',
     'profile_menu' => 'Menu du profil',
     'view_profile' => 'Voir le profil',
     'edit_profile' => 'Modifier le profil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informations',
+    'tab_info_label' => 'Onglet : Afficher les informations secondaires',
     'tab_content' => 'Contenu',
+    'tab_content_label' => 'Onglet : Afficher le contenu principal',
 
     // Email Content
     'email_action_help' => 'Si vous rencontrez des problèmes pour cliquer sur le bouton ":actionText", copiez et collez l\'adresse ci-dessous dans votre navigateur :',
     'email_rights' => 'Tous droits réservés',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Politique de confidentialité',
+    'terms_of_service' => 'Conditions d\'utilisation',
 ];
index 72711bcb57ea7312fd59bc840c5c8986a033c951..56cf6065b91c2c2d8c290d2859484ea7752b5c1c 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Ensemble d\'autorisations',
     'search_created_by_me' => 'Créé par moi',
     'search_updated_by_me' => 'Mis à jour par moi',
+    'search_owned_by_me' => 'Créés par moi',
     'search_date_options' => 'Recherche par date',
     'search_updated_before' => 'Mis à jour avant',
     'search_updated_after' => 'Mis à jour après',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Êtes-vous sûr de vouloir restaurer cette révision ? Le contenu courant de la page va être remplacé.',
     'revision_delete_success' => 'Révision supprimée',
     'revision_cannot_delete_latest' => 'Impossible de supprimer la dernière révision.'
-];
\ No newline at end of file
+];
index c1c14fe31f36ed8313026824792f19e2577cda8f..511683285649e13aa9177451af9b85f8655400f2 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Page non trouvée',
     'sorry_page_not_found' => 'Désolé, cette page n\'a pas pu être trouvée.',
     'sorry_page_not_found_permission_warning' => 'Si vous vous attendiez à ce que cette page existe, il se peut que vous n\'ayez pas l\'autorisation de la consulter.',
+    'image_not_found' => 'Image non trouvée',
+    'image_not_found_subtitle' => 'Désolé, l\'image que vous cherchez ne peut être trouvée.',
+    'image_not_found_details' => 'Si vous vous attendiez à ce que cette image existe, elle pourrait avoir été supprimée.',
     'return_home' => 'Retour à l\'accueil',
     'error_occurred' => 'Une erreur est survenue',
     'app_down' => ':appName n\'est pas en service pour le moment',
index 89e2f0ca5691bf4aaa725f3ac5b7156258f0a85b..fb525cec28553bfa04cc836e48971739669b3dc0 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Page d\'accueil de l\'application',
     'app_homepage_desc' => 'Choisissez une page à afficher sur la page d\'accueil au lieu de la vue par défaut. Les permissions sont ignorées pour les pages sélectionnées.',
     'app_homepage_select' => 'Choisissez une page',
+    'app_footer_links' => 'Liens de pied de page',
+    'app_footer_links_desc' => 'Ajoutez des liens dans le pied de page du site. Ils seront affichés en bas de la plupart des pages, incluant celles qui ne nécesittent pas de connexion. Vous pouvez utiliser l\'étiquette "trans::<key>" pour utiliser les traductions définies par le système. Par exemple, utiliser "trans::common.privacy_policy" fournira la traduction de "Politique de Confidentalité" et "trans::common.terms_of_service" fournira la traduction de "Conditions d\'utilisation".',
+    'app_footer_links_label' => 'Libellé du lien',
+    'app_footer_links_url' => 'URL du lien',
+    'app_footer_links_add' => 'Ajouter un lien en pied de page',
     'app_disable_comments' => 'Désactiver les commentaires',
     'app_disable_comments_toggle' => 'Désactiver les commentaires',
     'app_disable_comments_desc' => 'Désactive les commentaires sur toutes les pages de l\'application. Les commentaires existants ne sont pas affichés.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bulgare',
+        'bs' => 'Bosanski',
+        'ca' => 'Catalan',
         'cs' => 'Česky',
         'da' => 'Danois',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'Hébreu',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norvegien',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 60d8d34acfb02193039909d432e6d86760ceba52..684404f42d45c5ad921c0d95fa879c714c41dc40 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute doit contenir au moins :min caractères.',
         'array'   => ':attribute doit contenir au moins :min éléments.',
     ],
-    'no_double_extension'  => ':attribute ne doit avoir qu\'une seule extension de fichier.',
     'not_in'               => 'L\'attribut sélectionné :attribute est invalide.',
     'not_regex'            => ':attribute a un format invalide.',
     'numeric'              => ':attribute doit être un nombre.',
index 8a6311abdcfdbcd0266c413850dddaff20685a99..9cbd047fba0a8882f19462e6491cd5a7541c195c 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Sort Ascending',
     'sort_descending' => 'Sort Descending',
     'sort_name' => 'שם',
+    'sort_default' => 'Default',
     'sort_created_at' => 'תאריך יצירה',
     'sort_updated_at' => 'תאריך עדכון',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profile Menu',
     'view_profile' => 'הצג פרופיל',
     'edit_profile' => 'ערוך פרופיל',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'מידע',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'תוכן',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'אם לא ניתן ללחות על כפתור ״:actionText״, יש להעתיק ולהדביק את הכתובת למטה אל דפדפן האינטרנט שלך:',
     'email_rights' => 'כל הזכויות שמורות',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index 84ff7c3105cb343ab90cc02ac2cd48bbc5986123..48f7aa2d25af1c67f6b6866780b736357de4c214 100644 (file)
@@ -15,7 +15,7 @@ return [
     'image_load_more' => 'טען עוד',
     'image_image_name' => 'שם התמונה',
     'image_delete_used' => 'תמונה זו בשימוש בדפים שמתחת',
-    'image_delete_confirm_text' => 'Are you sure you want to delete this image?',
+    'image_delete_confirm_text' => 'האם את/ה בטוח/ה שברצונך למחוק את התמונה הזו?',
     'image_select_image' => 'בחר תמונה',
     'image_dropzone' => 'גרור תמונות או לחץ כאן להעלאה',
     'images_deleted' => 'התמונות נמחקו',
@@ -29,6 +29,6 @@ return [
     'code_editor' => 'ערוך קוד',
     'code_language' => 'שפת הקוד',
     'code_content' => 'תוכן הקוד',
-    'code_session_history' => 'Session History',
+    'code_session_history' => 'היסטורית ה-Session',
     'code_save' => 'שמור קוד',
 ];
index 59199bd8bd9079bf17c65c79898cc35fd23c0202..c8d239bca6cf711a365a1a4e1e69f44ba7e9ba57 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'סט הרשאות',
     'search_created_by_me' => 'שנוצרו על ידי',
     'search_updated_by_me' => 'שעודכנו על ידי',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'אפשרויות תאריך',
     'search_updated_before' => 'שעודכנו לפני',
     'search_updated_after' => 'שעודכנו לאחר',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'האם ברצונך לשחזר נוסח זה? תוכן הדף הנוכחי יעודכן לנוסח זה.',
     'revision_delete_success' => 'נוסח נמחק',
     'revision_cannot_delete_latest' => 'לא ניתן למחוק את הנוסח האחרון'
-];
\ No newline at end of file
+];
index f6a01d3a3ff4d9c94ddc1c2ec980793cb0b63530..5c879216c234006ce0480904a0bb3a868ccc8768 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'דף לא קיים',
     'sorry_page_not_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' => 'בחזרה לדף הבית',
     'error_occurred' => 'התרחשה שגיאה',
     'app_down' => ':appName כרגע אינו זמין',
index 17fad68d5baf120215751986c10ce194b19e6678..a94d7c30b36449e94a5a64a5497383c28c25ff9e 100644 (file)
@@ -6,10 +6,10 @@
  */
 return [
 
-    'password' => '×\94ס×\99ס×\9e×\90 ×\97×\99×\99×\91ת ×\9c×\94×\99×\95ת ×\91×¢×\9cת 6 ×ª×\95×\95×\99×\9d ×\95×\9c×\94ת×\90×\99×\9d ×\9c×\90×\99×\9e×\95ת',
-    'user' => "×\9c×\90 × ×\99ת×\9f ×\9c×\9eצ×\95×\90 ×\9eשת×\9eש ×¢×\9d ×\94×\9e×\99×\99×\9c ×©×¡×\95פק",
-    'token' => 'The password reset token is invalid for this email address.',
-    'sent' => 'נש×\9c×\97 ×\90×\9c×\99×\9a ×\90×\99\9e×\99×\99×\9c ×¢×\9d ×§×\99ש×\95ר ×\9c×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\90',
-    'reset' => '×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\90 הושלם בהצלחה!',
+    'password' => '×\94ס×\99ס×\9e×\94 ×\97×\99×\99×\91ת ×\9c×\94×\99×\95ת ×\91×¢×\9cת 8 ×ª×\95×\95×\99×\9d ×\9cפ×\97×\95ת ×\95×\9c×\94ת×\90×\99×\9d ×\9c×\90×\99×\9e×\95ת.',
+    'user' => "×\9c×\90 × ×\9eצ×\90 ×\9eשת×\9eש ×¢×\9d ×\9bת×\95×\91ת ×\93×\95×\90\"×\9c ×\96×\95.",
+    'token' => 'אסימון איפוס הסיסמה לא תקף עבור כתובת דוא"ל זו.',
+    'sent' => 'ש×\9c×\97× ×\95 ×\9c×\9a ×\93×\95×\90\9c ×¢×\9d ×§×\99ש×\95ר ×\9c×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94!',
+    'reset' => '×\90×\99פ×\95ס ×\94ס×\99ס×\9e×\94 הושלם בהצלחה!',
 
 ];
index a0632d061f7a3dec41fdb4d83932228393c0e95c..b7ce527693a0cbc986ce3a36d01b59c9b3f888c0 100755 (executable)
@@ -37,18 +37,23 @@ return [
     'app_homepage' => 'דף הבית של היישום',
     'app_homepage_desc' => 'אנא בחר דף להצגה בדף הבית במקום דף ברירת המחדל. הרשאות הדף לא יחולו בדפים הנבחרים.',
     'app_homepage_select' => 'בחר דף',
+    'app_footer_links' => 'קישורים בתחתית הדף',
+    'app_footer_links_desc' => 'הוסיפו קישורים שיוצגו בתחתית האתר. קישורים אלה יוצגו בתחתית רוב הדפים, לרבות אלה שלא מצריכים התחברות. תוכלו להשתמש בתווית "trans::<key>" כדי להשתמש בתרגומים המוגדרים על ידי המערכת. לדוגמה: שימוש ב"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' => 'מבטל את התגובות לאורך כל היישום, תגובות קיימות לא יוצגו.',
 
     // Color settings
-    'content_colors' => 'Content Colors',
-    'content_colors_desc' => 'Sets 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',
+    'content_colors' => 'צבעי התוכן',
+    'content_colors_desc' => 'מגדיר צבעים לכל האלמנטים בהיררכיה הארגונית של הדף. לחווית קריאה מיטבית, מומלץ לבחור צבעים בבהירות הדומה לצבעי ברירת המחדל.',
+    'bookshelf_color' => 'צבע המדף',
+    'book_color' => 'צבע הספר',
+    'chapter_color' => 'צבע הפרק',
+    'page_color' => 'צבע העמוד',
+    'page_draft_color' => 'צבע טיוטת העמוד',
 
     // Registration Settings
     'reg_settings' => 'הרשמה',
@@ -56,7 +61,7 @@ return [
     'reg_enable_toggle' => 'אפשר להרשם',
     'reg_enable_desc' => 'כאשר אפשר להרשם משתמשים יוכלו להכנס באופן עצמאי. בעת ההרשמה המשתמש יקבל הרשאה יחידה כברירת מחדל.',
     'reg_default_role' => 'הרשאה כברירת מחדל',
-    '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_enable_external_warning' => 'האפשרות לעיל חסרת השפעה כאשר מתבצע שימוש באותנטיקציה חיצונית מסוג LDAP או SAML. חשבונות משתמש לחברים לא קיימים יווצרו באופן אוטומטי במידה ואותנטיקציה, הנוגדת את המערכת החיצונית בשימוש, מצליחה.',
     'reg_email_confirmation' => 'אימות כתובת אי-מייל',
     'reg_email_confirmation_toggle' => 'יש לאמת את כתובת המייל',
     'reg_confirm_email_desc' => 'אם מופעלת הגבלה לדומיין ספציפי אז אימות המייל לא יבוצע',
@@ -68,53 +73,53 @@ return [
     'maint' => 'תחזוקה',
     'maint_image_cleanup' => 'ניקוי תמונות',
     'maint_image_cleanup_desc' => "סורק את הדפים והגרסאות על מנת למצוא אילו תמונות לא בשימוש. יש לוודא גיבוי מלא של מסד הנתונים והתמונות לפני הרצה",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'מחק בנוסף תמונות שקיימות בגרסאות ישנות של הדף בלבד',
     'maint_image_cleanup_run' => 'הפעל ניקוי תמונות',
     'maint_image_cleanup_warning' => 'נמצאו כ :count תמונות אשר לא בשימוש האם ברצונך להמשיך?',
     'maint_image_cleanup_success' => ':count תמונות שלא בשימוש נמחקו',
     'maint_image_cleanup_nothing_found' => 'לא נמצאו תמונות אשר לא בשימוש, לא נמחקו קבצים כלל.',
-    '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_send_test_email' => 'שלח דוא"ל ניסיוני',
+    'maint_send_test_email_desc' => 'שולח דוא"ל ניסיוני לכתובת הדוא"ל המצוינת בפרופיל שלך.',
+    'maint_send_test_email_run' => 'שלח דוא"ל ניסיוני',
+    'maint_send_test_email_success' => 'דוא"ל נשלח לכתובת :address',
+    'maint_send_test_email_mail_subject' => 'דוא"ל ניסיוני',
+    'maint_send_test_email_mail_greeting' => 'נראה ששליחת דוא"ל עובדת!',
+    'maint_send_test_email_mail_text' => 'ברכות! מאחר וקיבלת התראת דוא"ל זו, נראה שהגדרות הדוא"ל שלך הוגדרו כמו שצריך.',
+    'maint_recycle_bin_desc' => 'מדפים, ספרים, פרקים חדשים שנמחקו נשלחים לסל המיחזור, כדי שתוכלו לאחזר אותם או למחוק אותם לצמיתות. ייתכן שפריטים ישנים יותר בסל המיחזור יימחקו באופן אוטומטי לאחר זמן-מה, בהסתמך על הגדרות המערכת.',
+    'maint_recycle_bin_open' => 'פתח את סל המיחזור',
 
     // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin' => 'סל המיחזור',
+    'recycle_bin_desc' => 'כאן תוכלו לאחזר פריטים שנמחקו או לבחור למחוק אותם מהמערכת לצמיתות. רשימה זו לא מסוננת, בשונה מרשימות פעילות דומות במערכת, בהן מוחלים מסנני הרשאות.',
+    'recycle_bin_deleted_item' => 'פריט שנמחק',
+    'recycle_bin_deleted_by' => 'נמחק על ידי',
+    'recycle_bin_deleted_at' => 'זמן המחיקה',
+    'recycle_bin_permanently_delete' => 'מחק לצמיתות',
+    'recycle_bin_restore' => 'אחזר',
+    'recycle_bin_contents_empty' => 'סל המיחזור כרגע ריק',
+    'recycle_bin_empty' => 'רוקן את סל המיחזור',
+    'recycle_bin_empty_confirm' => 'פעולה זו תשמיד לצמיתות את כל הפריטים בסל המיחזור, לרבות התוכן בכל פריט. אתם בטוחים שאתם מעוניינים לרוקן את סל המיחזור?',
+    'recycle_bin_destroy_confirm' => 'פעולה זו תמחק מהמערכת לצמיתות פריט זה, יחד עם כל פריטי-הבן ברשימה להלן, ולא תוכלו לאחזר תוכל זה. אתם בטוחים שברצונכם למחוק לצמיתות פריט זה?',
+    'recycle_bin_destroy_list' => 'פריטים שיושמדו',
+    'recycle_bin_restore_list' => 'פריטים שיאוחזרו',
+    'recycle_bin_restore_confirm' => 'פעולה זו תאחזר את הפריט שנמחק, לרבות רכיבי-הבן שלו, למיקומו המקורי. אם המיקום המקורי נמחק מאז, וכעת נמצא בסל המיחזור, יש לאחזר גם את פריט-האב.',
+    'recycle_bin_restore_deleted_parent' => 'פריט-האב של פריט זה נמחק. פריטים אלה יישארו מחוקים עד שפריט-אב זה יאוחזר.',
+    'recycle_bin_destroy_notification' => 'נמחקו בסה"כ :count פריטים מסל המיחזור.',
+    'recycle_bin_restore_notification' => 'אוחזרו בסה"כ :count פריטים מסל המיחזור.',
 
     // 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_date' => 'Activity Date',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit' => 'לוג בדיקה',
+    'audit_desc' => 'לוג בדיקה זה מציג רשימה של פעילויות שנוטרו במערכת. רשימה זו לא מסוננת, בשונה מרשימות פעילות דומות במערכת בהן מוחלים מסנני הרשאות.',
+    'audit_event_filter' => 'מסנן אירועים',
+    'audit_event_filter_no_filter' => 'ללא סינון',
+    'audit_deleted_item' => 'פריט שנמחק',
+    'audit_deleted_item_name' => 'שם: :name',
+    'audit_table_user' => 'משתמש',
+    'audit_table_event' => 'אירוע',
+    'audit_table_related' => 'פריט או פרט קשור',
+    'audit_table_date' => 'זמן הפעילות',
+    'audit_date_from' => 'טווח תאריכים החל מ...',
+    'audit_date_to' => 'טווח תאריכים עד ל...',
 
     // Role Settings
     'roles' => 'תפקידים',
@@ -131,17 +136,17 @@ return [
     'role_details' => 'פרטי תפקיד',
     'role_name' => 'שם התפקיד',
     'role_desc' => 'תיאור קצר של התפקיד',
-    'role_external_auth_id' => 'External Authentication IDs',
+    'role_external_auth_id' => 'ID-י אותנטיקציה חיצוניים',
     'role_system' => 'הרשאות מערכת',
     'role_manage_users' => 'ניהול משתמשים',
     'role_manage_roles' => 'ניהול תפקידים והרשאות תפקידים',
     'role_manage_entity_permissions' => 'נהל הרשאות ספרים, פרקים ודפים',
     'role_manage_own_entity_permissions' => 'נהל הרשאות על ספרים, פרקים ודפים בבעלותך',
-    'role_manage_page_templates' => 'Manage page templates',
-    'role_access_api' => 'Access system API',
+    'role_manage_page_templates' => 'נהל תבניות דפים',
+    'role_access_api' => 'גש ל-API המערכת',
     'role_manage_settings' => 'ניהול הגדרות יישום',
     'role_asset' => 'הרשאות משאבים',
-    '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.',
+    'roles_system_warning' => 'שימו לב לכך שגישה לכל אחת משלושת ההרשאות הנ"ל יכולה לאפשר למשתמש לשנות את הפריווילגיות שלהם או של אחרים במערכת. הגדירו תפקידים להרשאות אלה למשתמשים בהם אתם בוטחים בלבד.',
     'role_asset_desc' => 'הרשאות אלו שולטות בגישת ברירת המחדל למשאבים בתוך המערכת. הרשאות של ספרים, פרקים ודפים יגברו על הרשאות אלו.',
     'role_asset_admins' => 'מנהלים מקבלים הרשאה מלאה לכל התוכן אך אפשרויות אלו עלולות להציג או להסתיר אפשרויות בממשק',
     'role_all' => 'הכל',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'פרופיל משתמש',
     'users_add_new' => 'הוסף משתמש חדש',
     'users_search' => 'חפש משתמשים',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'פעילות אחרונה',
     'users_details' => 'פרטי משתמש',
     'users_details_desc' => 'הגדר שם לתצוגה ומייל עבור משתמש זה. כתובת המייל תשמש על מנת להתחבר למערכת',
     'users_details_desc_no_email' => 'הגדר שם לתצוגה כדי שאחרים יוכלו לזהות',
@@ -165,8 +170,8 @@ return [
     'users_role_desc' => 'בחר אילו תפקידים ישויכו למשתמש זה. אם המשתמש משוייך למספר תפקידים, ההרשאות יהיו כלל ההרשאות של כל התפקידים',
     'users_password' => 'סיסמא',
     'users_password_desc' => 'הגדר סיסמא עבור גישה למערכת. על הסיסמא להיות באורך של 5 תווים לפחות',
-    '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_send_invite_text' => 'תוכלו לבחור לשלוח למשתמש זה דוא"ל הזמנה, המאפשר להם להגדיר סיסמה משלהם. אחרת, תוכלו להגדיר את סיסמתם בעצמכם.',
+    'users_send_invite_option' => 'שלח דוא"ל הזמנה למשתמש',
     'users_external_auth_id' => 'זיהוי חיצוני - ID',
     'users_external_auth_id_desc' => 'זיהוי זה יהיה בשימוש מול מערכת ה LDAP שלך',
     'users_password_warning' => 'יש למלא רק אם ברצונך לשנות את הסיסמא.',
@@ -175,10 +180,10 @@ return [
     'users_delete_named' => 'מחק משתמש :userName',
     'users_delete_warning' => 'פעולה זו תמחק את המשתמש \':userName\' מהמערכת',
     'users_delete_confirm' => 'האם ברצונך למחוק משתמש זה?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'העבר בעלות',
+    'users_migrate_ownership_desc' => 'בחרו משתמש כאן במידה ואתם מעוניינים שמשתמש אחר יהפוך לבעלים של כל הפריטים שכרגע בבעלות משתמש זה.',
+    'users_none_selected' => 'לא נבחר משתמש',
+    'users_delete_success' => 'משתמש נמחק בהצלחה',
     'users_edit' => 'עריכת משתמש',
     'users_edit_profile' => 'עריכת פרופיל',
     'users_edit_success' => 'המשתמש עודכן בהצלחה',
@@ -192,32 +197,32 @@ return [
     'users_social_disconnect' => 'ניתוק חשבון',
     'users_social_connected' => 'חשבון :socialAccount חובר בהצלחה לחשבון שלך',
     'users_social_disconnected' => ':socialAccount נותק בהצלחה מהחשבון שלך',
-    'users_api_tokens' => 'API Tokens',
-    '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_api_tokens' => 'אסימוני API',
+    'users_api_tokens_none' => 'לא נוצרו אסימוני API למשתמש זה',
+    'users_api_tokens_create' => 'צור אסימון',
+    'users_api_tokens_expires' => 'פג',
+    'users_api_tokens_docs' => 'תיעוד API',
 
     // 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_create_success' => 'API token successfully created',
-    'user_api_token_update_success' => 'API token successfully updated',
-    '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_delete_success' => 'API token successfully deleted',
+    'user_api_token_create' => 'צור אסימון API',
+    'user_api_token_name' => 'שם',
+    'user_api_token_name_desc' => 'תנו לאסימון שלכם שם קריא, כתזכורת עתידית למטרה המיועדת שלו.',
+    'user_api_token_expiry' => 'תאריך תפוגה',
+    'user_api_token_expiry_desc' => 'הגדירו תאריך בו יפוג תוקף אסימון זה. לאחר תאריך זה, בקשות שיעשו באמצעות אסימון זה לא יעבדו יותר. במידה ושדה זה יושאר ריק, תאריך התפוגה יוגדר לבעוד 100 שנים.',
+    'user_api_token_create_secret_message' => 'מיד לאחר יצירת אסימון זה, יווצרו ויוצגו "ID אסימון" ו"סוד אסימון". הסוד יוצג פעם אחת בלבד, לכן וודאו להעתיק את הערך למקום שמור ובטוח לפני שתמשיכו הלאה.',
+    'user_api_token_create_success' => 'אסימון API נוצר בהצלחה',
+    'user_api_token_update_success' => 'אסימון API עודכן בהצלחה',
+    'user_api_token' => 'אסימון API',
+    'user_api_token_id' => 'ID האסימון',
+    'user_api_token_id_desc' => 'זהו מזהה בלתי ניתן לעריכה לאסימון זה הנוצר על ידי המערכת, אשר יסופק בבקשות API.',
+    'user_api_token_secret' => 'סוד האסימון',
+    'user_api_token_secret_desc' => 'זהו סוד המיוצר על ידי המערכת לאסימון זה, אשר יסופק בבקשות API. סוד זה יוצג פעם אחת בלבד, לכן וודאו להעתיק ערך זה למקום שמור ובטוח.',
+    'user_api_token_created' => 'אסימון נוצר :timeAgo',
+    'user_api_token_updated' => 'אסימון עודכן :timeAgo',
+    'user_api_token_delete' => 'מחק אסימון',
+    'user_api_token_delete_warning' => 'פעולה זו תמחק לחלוטין את אסימון ה-API בשם \':tokenName\' מהמערכת.',
+    'user_api_token_delete_confirm' => 'האם אתם בטוחים שאתם מעוניינים למחוק אסימון API זה?',
+    'user_api_token_delete_success' => 'אסימון API נמחק בהצלחה',
 
     //! If editing translations files directly please ignore this in all
     //! languages apart from en. Content will be auto-copied from en.
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 7c02735ca067f8e7876a016323c24c69d39db7a2..8a18ca27a21c3c85a81a18c891d3ba88553e0247 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'שדה :attribute חייב להיות לפחות :min תווים.',
         'array'   => 'שדה :attribute חייב להיות לפחות :min פריטים.',
     ],
-    'no_double_extension'  => 'השדה :attribute חייב להיות בעל סיומת קובץ אחת בלבד.',
     'not_in'               => 'בחירת ה-:attribute אינה תקפה.',
     'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'שדה :attribute חייב להיות מספר.',
index 5070adccd770dc5e1380836809ac116947ca93ac..116f57527edff31e614ff615200b7001371d800a 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Növekvő sorrend',
     'sort_descending' => 'Csökkenő sorrend',
     'sort_name' => 'Név',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Létrehozás dátuma',
     'sort_updated_at' => 'Frissítés dátuma',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Morzsa',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profil menü',
     'view_profile' => 'Profil megtekintése',
     'edit_profile' => 'Profil szerkesztése',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Információ',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Tartalom',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Probléma esetén a lenti ":actionText" gombra kell kattintani, majd ki kell másolni a lenti webcímet és be kell illeszteni egy böngészőbe:',
     'email_rights' => 'Minden jog fenntartva',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index c248d63a057714d33131d39f6ec4bfd9c34bb23c..293e4ebc455c8713a4ac49d272e2b10a3f2e4179 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Jogosultságok beállítva',
     'search_created_by_me' => 'Általam létrehozott',
     'search_updated_by_me' => 'Általam frissített',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Dátum beállítások',
     'search_updated_before' => 'Frissítve ez előtt',
     'search_updated_after' => 'Frissítve ez után',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Biztosan visszaállítható ez a változat? A oldal jelenlegi tartalma le lesz cserélve.',
     'revision_delete_success' => 'Változat törölve',
     'revision_cannot_delete_latest' => 'A legutolsó változat nem törölhető.'
-];
\ No newline at end of file
+];
index 64229a19f3d3283025093b3c2a159890c82bcb21..a16bef5290a00c3882421bf12020282165d82319 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Oldal nem található',
     'sorry_page_not_found' => 'Sajnáljuk, a keresett oldal nem található.',
     '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' => 'Vissza a kezdőlapra',
     'error_occurred' => 'Hiba örtént',
     'app_down' => ':appName jelenleg nem üzemel',
index 9b148a61e2e11e0c8e343abf98db57555f416697..ba748aafedd5a51da97fcb60cf6d070333e3f430 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Alkalmazás kezdőlapja',
     'app_homepage_desc' => 'A kezdőlapon az alapértelmezés szerinti nézet helyett megjelenő nézet kiválasztása. A kiválasztott oldalakon figyelmen kívül lesznek hagyva az oldal engedélyek.',
     'app_homepage_select' => 'Egy oldal kiválasztása',
+    '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::<key>" 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' => 'Megjegyzések letiltása',
     'app_disable_comments_toggle' => 'Megjegyzések letiltása',
     'app_disable_comments_desc' => 'Megjegyzések letiltása az alkalmazás összes oldalán.<br>A már létező megjegyzések el lesznek rejtve.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 7c378e983cc58cf629c05005e11c9c1b938ce98d..d82e3263274961cdce7bfd5aac33e5d7644c935d 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute legalább :min karakter kell legyen.',
         'array'   => ':attribute legalább :min elem kell legyen.',
     ],
-    'no_double_extension'  => ':attribute csak egy fájlkiterjesztéssel rendelkezhet.',
     'not_in'               => 'A kiválasztott :attribute érvénytelen.',
     'not_regex'            => ':attribute formátuma érvénytelen.',
     'numeric'              => ':attribute szám kell legyen.',
diff --git a/resources/lang/id/activities.php b/resources/lang/id/activities.php
new file mode 100644 (file)
index 0000000..2269cb2
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
+return [
+
+    // Pages
+    'page_create'                 => 'halaman dibuat',
+    'page_create_notification'    => 'Halaman Berhasil dibuat',
+    'page_update'                 => 'halaman diperbaharui',
+    'page_update_notification'    => 'Berhasil mengupdate halaman',
+    'page_delete'                 => 'halaman dihapus',
+    'page_delete_notification'    => 'Berhasil menghapus halaman',
+    'page_restore'                => 'halaman telah dipulihkan',
+    'page_restore_notification'   => 'Berhasil memulihkan halaman',
+    'page_move'                   => 'halaman dipindahkan',
+
+    // Chapters
+    'chapter_create'              => 'membuat bab',
+    'chapter_create_notification' => 'Bab berhasil dibuat',
+    'chapter_update'              => 'bab diperbaharui',
+    'chapter_update_notification' => 'Bab berhasil diupdate',
+    'chapter_delete'              => 'hapus bab',
+    'chapter_delete_notification' => 'Bab berhasil dihapus',
+    'chapter_move'                => 'bab dipindahkan',
+
+    // Books
+    'book_create'                 => 'membuat buku',
+    'book_create_notification'    => 'Buku berhasil dibuat',
+    'book_update'                 => 'update buku',
+    'book_update_notification'    => 'Buku berhasil diupdate',
+    'book_delete'                 => 'hapus buku',
+    'book_delete_notification'    => 'Buku berhasil dihapus',
+    'book_sort'                   => 'urutkan buku',
+    'book_sort_notification'      => 'Buku berhasil diurutkan',
+
+    // Bookshelves
+    'bookshelf_create'            => 'membuat rak',
+    'bookshelf_create_notification'    => 'Rak berhasil dibuat',
+    'bookshelf_update'                 => 'update rak',
+    'bookshelf_update_notification'    => 'Rak berhasil diupdate',
+    'bookshelf_delete'                 => 'hapus rak buku',
+    'bookshelf_delete_notification'    => 'Rak berhasil dihapus',
+
+    // Other
+    'commented_on'                => 'berkomentar pada',
+    'permissions_update'          => 'perbaharui izin',
+];
diff --git a/resources/lang/id/auth.php b/resources/lang/id/auth.php
new file mode 100644 (file)
index 0000000..0fc965d
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
+return [
+
+    'failed' => 'Kredensial tidak cocok dengan catatan kami.',
+    'throttle' => 'Terlalu banyak upaya masuk. Silahkan mencoba lagi dalam :seconds detik.',
+
+    // Login & Register
+    'sign_up' => 'Daftar',
+    'log_in' => 'Gabung',
+    'log_in_with' => 'Masuk dengan :socialDriver',
+    'sign_up_with' => 'Daftar dengan :socialDriver',
+    'logout' => 'Keluar',
+
+    'name' => 'Nama',
+    'username' => 'Nama Pengguna',
+    'email' => 'Email',
+    'password' => 'Kata Sandi',
+    'password_confirm' => 'Konfirmasi Kata Sandi',
+    'password_hint' => 'Harus lebih dari 7 karakter',
+    'forgot_password' => 'Lupa Password?',
+    'remember_me' => 'Ingat saya',
+    'ldap_email_hint' => 'Harap masukkan email yang akan digunakan untuk akun ini.',
+    'create_account' => 'Membuat Akun',
+    'already_have_account' => 'Sudah punya akun?',
+    'dont_have_account' => 'Tidak punya akun?',
+    'social_login' => 'Masuk dengan sosial media',
+    'social_registration' => 'Daftar dengan sosial media',
+    'social_registration_text' => 'Daftar dan masuk menggunakan layanan lain.',
+
+    'register_thanks' => 'Terima kasih telah mendaftar!',
+    'register_confirm' => 'Silakan periksa email Anda dan klik tombol konfirmasi untuk mengakses :appName.',
+    'registrations_disabled' => 'Pendaftaran saat ini dinonaktifkan',
+    'registration_email_domain_invalid' => 'Domain email tersebut tidak memiliki akses ke aplikasi ini',
+    'register_success' => 'Terima kasih telah mendaftar! Anda sekarang terdaftar dan masuk.',
+
+
+    // Password Reset
+    'reset_password' => 'Atur ulang kata sandi',
+    'reset_password_send_instructions' => 'Masukkan email Anda di bawah ini dan Anda akan dikirimi email dengan tautan pengaturan ulang kata sandi.',
+    'reset_password_send_button' => 'Kirim Tautan Atur Ulang',
+    'reset_password_sent' => 'Tautan pengaturan ulang kata sandi akan dikirim ke :email jika alamat email ditemukan di sistem.',
+    'reset_password_success' => 'Kata sandi Anda telah berhasil diatur ulang.',
+    'email_reset_subject' => 'Atur ulang kata sandi :appName anda',
+    'email_reset_text' => 'Anda menerima email ini karena kami menerima permintaan pengaturan ulang kata sandi untuk akun Anda.',
+    'email_reset_not_requested' => 'Jika Anda tidak meminta pengaturan ulang kata sandi, tidak ada tindakan lebih lanjut yang diperlukan.',
+
+
+    // Email Confirmation
+    'email_confirm_subject' => 'Konfirmasikan email Anda di :appName',
+    'email_confirm_greeting' => 'Terima kasih telah bergabung :appName!',
+    'email_confirm_text' => 'Silakan konfirmasi alamat email Anda dengan mengklik tombol di bawah ini:',
+    'email_confirm_action' => 'Konfirmasi email',
+    'email_confirm_send_error' => 'Konfirmasi email diperlukan tetapi sistem tidak dapat mengirim email. Hubungi admin untuk memastikan email disiapkan dengan benar.',
+    'email_confirm_success' => 'Email Anda telah dikonfirmasi!',
+    'email_confirm_resent' => 'Email konfirmasi dikirim ulang, Harap periksa kotak masuk Anda.',
+
+    'email_not_confirmed' => 'Alamat Email Tidak Dikonfirmasi',
+    'email_not_confirmed_text' => 'Alamat email Anda belum dikonfirmasi.',
+    'email_not_confirmed_click_link' => 'Silakan klik link di email yang dikirimkan segera setelah Anda mendaftar.',
+    'email_not_confirmed_resend' => 'Jika Anda tidak dapat menemukan email tersebut, Anda dapat mengirim ulang email konfirmasi dengan mengirimkan formulir di bawah ini.',
+    'email_not_confirmed_resend_button' => 'Mengirimkan kembali email konfirmasi',
+
+    // User Invite
+    'user_invite_email_subject' => 'Anda telah diundang untuk bergabung di :appName!',
+    'user_invite_email_greeting' => 'Sebuah akun telah dibuat untuk Anda di :appName.',
+    'user_invite_email_text' => 'Klik tombol di bawah untuk mengatur kata sandi akun dan mendapatkan akses:',
+    'user_invite_email_action' => 'Atur Kata Sandi Akun',
+    'user_invite_page_welcome' => 'Selamat datang di :appName!',
+    'user_invite_page_text' => 'Untuk menyelesaikan akun Anda dan mendapatkan akses, Anda perlu mengatur kata sandi yang akan digunakan untuk masuk ke :appName pada kunjungan berikutnya.',
+    'user_invite_page_confirm_button' => 'Konfirmasi Kata sandi',
+    'user_invite_success' => 'Atur kata sandi, Anda sekarang memiliki akses ke :appName!'
+];
\ No newline at end of file
diff --git a/resources/lang/id/common.php b/resources/lang/id/common.php
new file mode 100644 (file)
index 0000000..da81381
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
+return [
+
+    // Buttons
+    'cancel' => 'Batalkan',
+    'confirm' => 'Konfirmasi',
+    'back' => 'Kembali',
+    'save' => 'Simpan',
+    'continue' => 'Selanjutnya',
+    'select' => 'Pilih',
+    'toggle_all' => 'Alihkan semua',
+    'more' => 'Lebih',
+
+    // Form Labels
+    'name' => 'Nama',
+    'description' => 'Deskripsi',
+    'role' => 'Wewenang',
+    'cover_image' => 'Sampul Gambar',
+    'cover_image_description' => 'Gambar ini harus berukuran kira-kira 440x250 piksel.',
+    
+    // Actions
+    'actions' => 'Tindakan',
+    'view' => 'Melihat',
+    'view_all' => 'Lihat Semua',
+    'create' => 'Buat',
+    'update' => 'Perbaharui',
+    'edit' => 'Sunting',
+    'sort' => 'Sortir',
+    'move' => 'Pindahkan',
+    'copy' => 'Salin',
+    'reply' => 'Balasan',
+    'delete' => 'Hapus',
+    'delete_confirm' => 'Konfirmasi Penghapusan',
+    'search' => 'Cari',
+    'search_clear' => 'Hapus Pencarian',
+    'reset' => 'Setel Ulang',
+    'remove' => 'Hapus',
+    'add' => 'Tambah',
+    'fullscreen' => 'Layar Penuh',
+
+    // Sort Options
+    'sort_options' => 'Sortir Pilihan',
+    'sort_direction_toggle' => 'Urutkan Arah Toggle',
+    'sort_ascending' => 'Sortir Naik',
+    'sort_descending' => 'Urutkan Menurun',
+    'sort_name' => 'Nama',
+    'sort_default' => 'Bawaan',
+    'sort_created_at' => 'Tanggal dibuat',
+    'sort_updated_at' => 'Tanggal diperbaharui',
+
+    // Misc
+    'deleted_user' => 'Pengguna terhapus',
+    'no_activity' => 'Tidak ada aktifitas untuk ditampilkan',
+    'no_items' => 'Tidak ada item yang tersedia',
+    'back_to_top' => 'Kembali ke atas',
+    'toggle_details' => 'Detail Toggle',
+    'toggle_thumbnails' => 'Alihkan Gambar Mini',
+    'details' => 'Detail',
+    'grid_view' => 'Tampilan bergaris',
+    'list_view' => 'Daftar Tampilan',
+    'default' => 'Default',
+    'breadcrumb' => 'Breadcrumb',
+
+    // Header
+    'header_menu_expand' => 'Perluas Menu Tajuk',
+    'profile_menu' => 'Profile Menu',
+    'view_profile' => 'Tampilkan profil',
+    'edit_profile' => 'Sunting Profil',
+    'dark_mode' => 'Mode Gelap',
+    'light_mode' => 'Mode Cahaya',
+
+    // Layout tabs
+    'tab_info' => 'Informasi',
+    'tab_info_label' => 'Tab Menampilkan Informasi Sekunder',
+    'tab_content' => 'Konten',
+    'tab_content_label' => 'Tab Menampilkan Informasi Utama',
+
+    // Email Content
+    'email_action_help' => 'Jika Anda mengalami masalah saat mengklik tombol ":actionText", salin dan tempel URL di bawah ini ke browser web Anda:',
+    'email_rights' => 'Seluruh hak cipta',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Rahasia pribadi',
+    'terms_of_service' => 'Persyaratan Layanan',
+];
diff --git a/resources/lang/id/components.php b/resources/lang/id/components.php
new file mode 100644 (file)
index 0000000..309e931
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
+return [
+
+    // Image Manager
+    'image_select' => 'Pilih Gambar',
+    'image_all' => 'Semua',
+    'image_all_title' => 'Lihat semua gambar',
+    'image_book_title' => 'Lihat gambar yang diunggah ke buku ini',
+    'image_page_title' => 'Lihat gambar yang diunggah ke halaman ini',
+    'image_search_hint' => 'Cari berdasarkan nama gambar',
+    'image_uploaded' => 'Diunggah :uploadedDate',
+    'image_load_more' => 'Muat lebih banyak',
+    'image_image_name' => 'Muat lebih banyak',
+    'image_delete_used' => 'Gambar ini digunakan untuk halaman dibawah ini.',
+    'image_delete_confirm_text' => 'Anda yakin ingin menghapus gambar ini?',
+    'image_select_image' => 'Pilih gambar',
+    'image_dropzone' => 'Lepaskan gambar atau klik di sini untuk mengunggah',
+    'images_deleted' => 'Gambar Dihapus',
+    'image_preview' => 'Pratinjau Gambar',
+    'image_upload_success' => 'Gambar berhasil diunggah',
+    'image_update_success' => 'Detail gambar berhasil diperbarui',
+    'image_delete_success' => 'Gambar berhasil dihapus',
+    'image_upload_remove' => 'Menghapus',
+
+    // Code Editor
+    'code_editor' => 'Edit Kode',
+    'code_language' => 'Bahasa Kode',
+    'code_content' => 'Konten Kode',
+    'code_session_history' => 'Konten Kode',
+    'code_save' => 'Simpan Kode',
+];
diff --git a/resources/lang/id/entities.php b/resources/lang/id/entities.php
new file mode 100644 (file)
index 0000000..7764c1a
--- /dev/null
@@ -0,0 +1,320 @@
+<?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
+return [
+
+    // Shared
+    'recently_created' => 'Baru saja dibuat',
+    'recently_created_pages' => 'Halaman baru saja dibuat',
+    'recently_updated_pages' => 'Halaman baru saja diperbaharui',
+    'recently_created_chapters' => 'Bab baru saja dibuat',
+    'recently_created_books' => 'Buku baru saja dibuat',
+    'recently_created_shelves' => 'Rak baru saja dibuat',
+    'recently_update' => 'Baru saja diperbaharui',
+    'recently_viewed' => 'Baru saja dilihat',
+    'recent_activity' => 'Aktivitas Terbaru',
+    'create_now' => 'Buat Sekarang',
+    'revisions' => 'Revisi',
+    'meta_revision' => 'Revisi #:revisionCount',
+    'meta_created' => 'Dibuat :timeLength',
+    'meta_created_name' => 'Dibuat :timeLength oleh :user',
+    'meta_updated' => 'Diperbaharui :timeLength',
+    'meta_updated_name' => 'Diperbaharui :timeLength oleh :user',
+    'meta_owned_name' => 'Dimiliki oleh :user',
+    'entity_select' => 'Pilihan Entitas',
+    'images' => 'Gambar-gambar',
+    'my_recent_drafts' => 'Draf Terbaru Saya',
+    'my_recently_viewed' => 'Baru saja saya lihat',
+    'no_pages_viewed' => 'Anda belum melihat halaman apa pun',
+    'no_pages_recently_created' => 'Tidak ada halaman yang baru saja dibuat',
+    'no_pages_recently_updated' => 'Tidak ada halaman yang baru-baru ini diperbarui',
+    'export' => 'Ekspor',
+    'export_html' => 'File Web Berisi',
+    'export_pdf' => 'Dokumen PDF',
+    'export_text' => 'Dokumen Teks Biasa',
+
+    // Permissions and restrictions
+    'permissions' => 'Izin',
+    'permissions_intro' => 'Setelah diaktifkan, izin ini akan menjadi prioritas di atas izin peran yang ditetapkan.',
+    'permissions_enable' => 'Aktifkan Izin Kustom',
+    'permissions_save' => 'Simpan Izin',
+    'permissions_owner' => 'Pemilik',
+
+    // Search
+    'search_results' => 'Hasil Pencarian',
+    'search_total_results_found' => ':count hasil hitung ditemukan |:count hasil hitung total tang di temukan',
+    'search_clear' => 'Bersihkan pencarian',
+    'search_no_pages' => 'Tidak ada halaman yang cocok dengan pencarian ini',
+    'search_for_term' => 'Pencarian untuk :term',
+    'search_more' => 'Hasil lebih',
+    'search_advanced' => 'Pencarian Lanjutan',
+    'search_terms' => 'Cari Istilah',
+    'search_content_type' => 'Tipe Konten',
+    'search_exact_matches' => 'Pertandingan Persis',
+    'search_tags' => 'Pencarian Tag',
+    'search_options' => 'Pilihan',
+    'search_viewed_by_me' => 'Dilihat oleh saya',
+    'search_not_viewed_by_me' => 'Tidak dilihat oleh saya',
+    'search_permissions_set' => 'Izin ditetapkan',
+    'search_created_by_me' => 'Dibuat oleh saya',
+    'search_updated_by_me' => 'Diperbaharui oleh saya',
+    'search_owned_by_me' => 'Milik Saya',
+    'search_date_options' => 'Opsi Tanggal',
+    'search_updated_before' => 'Diperbaharui sebelum',
+    'search_updated_after' => 'Diperbaharui setelah',
+    'search_created_before' => 'Dibuat sebelum',
+    'search_created_after' => 'Dibuat setelah',
+    'search_set_date' => 'Atur Tanggal',
+    'search_update' => 'Perbaharui pencarian',
+
+    // Shelves
+    'shelf' => 'Rak',
+    'shelves' => 'Rak',
+    'x_shelves' => ':count Rak|:count Rak',
+    'shelves_long' => 'Rak Buku',
+    'shelves_empty' => 'Tidak ada rak yang dibuat',
+    'shelves_create' => 'Buat Rak baru',
+    'shelves_popular' => 'Rak Terpopuler',
+    'shelves_new' => 'Rak Baru',
+    'shelves_new_action' => 'Rak Baru',
+    'shelves_popular_empty' => 'Rak paling populer akan muncul di sini.',
+    'shelves_new_empty' => 'Rak yang paling baru dibuat akan muncul di sini.',
+    'shelves_save' => 'Simpan Rak',
+    'shelves_books' => 'Buku di rak ini',
+    'shelves_add_books' => 'Tambahkan buku ke rak ini',
+    'shelves_drag_books' => 'Tarik buku ke sini untuk menambahkannya ke rak ini',
+    'shelves_empty_contents' => 'Rak ini tidak memiliki buku yang ditugaskan padanya',
+    'shelves_edit_and_assign' => 'Edit rak untuk menetapkan buku',
+    'shelves_edit_named' => 'Edit Rak Buku :name',
+    'shelves_edit' => 'Edit Rak Buku',
+    'shelves_delete' => 'Hapus rak buku',
+    'shelves_delete_named' => 'Hapus rak buku :name',
+    'shelves_delete_explain' => "Ini akan menghapus rak buku dengan nama ':name'. Buku yang berisi tidak akan dihapus.",
+    'shelves_delete_confirmation' => 'Apakah Anda yakin ingin menghapus rak buku ini?',
+    'shelves_permissions' => 'Izin Rak Buku',
+    'shelves_permissions_updated' => 'Izin Rak Buku Diperbarui',
+    'shelves_permissions_active' => 'Izin Rak Buku Aktif',
+    'shelves_copy_permissions_to_books' => 'Salin Izin ke Buku',
+    'shelves_copy_permissions' => 'Salin Izin',
+    'shelves_copy_permissions_explain' => 'Ini akan menerapkan setelan izin rak buku ini saat ini ke semua buku yang ada di dalamnya. Sebelum mengaktifkan, pastikan setiap perubahan pada izin rak buku ini telah disimpan.',
+    'shelves_copy_permission_success' => 'Izin rak buku disalin ke :count buku',
+
+    // Books
+    'book' => 'Buku',
+    'books' => 'Semua Buku',
+    'x_books' => ':count Buku|:count Semua Buku',
+    'books_empty' => 'Tidak ada buku yang telah dibuat',
+    'books_popular' => 'Buku Populer',
+    'books_recent' => 'Buku Terbaru',
+    'books_new' => 'Buku baru',
+    'books_new_action' => 'Buku baru',
+    'books_popular_empty' => 'Buku paling populer akan muncul di sini.',
+    'books_new_empty' => 'The most recently created books will appear here.',
+    'books_create' => 'Buat Buku Baru',
+    'books_delete' => 'Hapus Buku',
+    'books_delete_named' => 'Hapus buku :bookName',
+    'books_delete_explain' => 'Ini akan menghapus buku dengan nama \': bookName\'. Semua halaman dan bab akan dihapus.',
+    'books_delete_confirmation' => 'Apakah Anda yakin ingin menghapus buku ini?',
+    'books_edit' => 'Edit Buku',
+    'books_edit_named' => 'Edit Buku :bookName',
+    'books_form_book_name' => 'Nama Buku',
+    'books_save' => 'Simpan Buku',
+    'books_permissions' => 'Izin Buku',
+    'books_permissions_updated' => 'Izin Buku Diperbarui',
+    'books_empty_contents' => 'Tidak ada halaman atau bab yang telah dibuat untuk buku ini.',
+    'books_empty_create_page' => 'Buat halaman baru',
+    'books_empty_sort_current_book' => 'Sortir buku saat ini',
+    'books_empty_add_chapter' => 'Tambahkan satu bab',
+    'books_permissions_active' => 'Izin Buku Aktif',
+    'books_search_this' => 'Cari buku ini',
+    'books_navigation' => 'Navigasi Buku',
+    'books_sort' => 'Sortir Isi Buku',
+    'books_sort_named' => 'Sortir Buku :bookName',
+    'books_sort_name' => 'Diurutkan berdasarkan nama',
+    'books_sort_created' => 'Urutkan berdasarkan Tanggal Dibuat',
+    'books_sort_updated' => 'Urutkan berdasarkan Tanggal Diperbarui',
+    'books_sort_chapters_first' => 'Bab Pertama',
+    'books_sort_chapters_last' => 'Bab Terakhir',
+    'books_sort_show_other' => 'Tunjukkan Buku Lain',
+    'books_sort_save' => 'Simpan Pesanan Baru',
+
+    // Chapters
+    'chapter' => 'Bab',
+    'chapters' => 'Bab',
+    'x_chapters' => ':count Bab |:count Bab',
+    'chapters_popular' => 'Bab Populer',
+    'chapters_new' => 'Bab Baru',
+    'chapters_create' => 'Buat Bab Baru',
+    'chapters_delete' => 'Hapur Bab',
+    'chapters_delete_named' => 'Hapus bab dengan nama :chapterName',
+    'chapters_delete_explain' => 'Ini akan menghapus chapter dengan nama \':chapterName\'. Semua halaman yang ada dalam bab ini juga akan dihapus.',
+    'chapters_delete_confirm' => 'Anda yakin ingin menghapus bab ini?',
+    'chapters_edit' => 'Edit Bab',
+    'chapters_edit_named' => 'Edit Bab :chapterName',
+    'chapters_save' => 'Simpan Bab',
+    'chapters_move' => 'Pindahkan Bab',
+    'chapters_move_named' => 'Pindahkan Bab :chapterName',
+    'chapter_move_success' => 'Bab dipindahkan ke :bookName',
+    'chapters_permissions' => 'Izin Bab',
+    'chapters_empty' => 'Saat ini tidak ada halaman dalam bab ini.',
+    'chapters_permissions_active' => 'Izin Bab Aktif',
+    'chapters_permissions_success' => 'Izin Bab Diperbarui',
+    'chapters_search_this' => 'Cari bab ini',
+
+    // Pages
+    'page' => 'Halaman',
+    'pages' => 'Semua Halaman',
+    'x_pages' => ':count Halaman|:count Semua Halaman',
+    'pages_popular' => 'Halaman Populer',
+    'pages_new' => 'Lembaran baru',
+    'pages_attachments' => 'Lampiran',
+    'pages_navigation' => 'Halaman Navigasi',
+    'pages_delete' => 'Hapus Halaman',
+    'pages_delete_named' => 'Hapus Halaman :pageName',
+    'pages_delete_draft_named' => 'Hapus Halaman Draf :pageName',
+    'pages_delete_draft' => 'Hapus Halaman Draf',
+    'pages_delete_success' => 'Halaman dihapus',
+    'pages_delete_draft_success' => 'Halaman draf dihapus',
+    'pages_delete_confirm' => 'Anda yakin ingin menghapus halaman ini?',
+    'pages_delete_draft_confirm' => 'Anda yakin ingin menghapus halaman draf ini?',
+    'pages_editing_named' => 'Mengedit Halaman :pageName',
+    'pages_edit_draft_options' => 'Opsi Draf',
+    'pages_edit_save_draft' => 'Simpan Draf',
+    'pages_edit_draft' => 'Edit Halaman Draf',
+    'pages_editing_draft' => 'Mengedit Draf',
+    'pages_editing_page' => 'Mengedit Draf',
+    'pages_edit_draft_save_at' => 'Draf disimpan pada ',
+    'pages_edit_delete_draft' => 'Hapus Draf',
+    'pages_edit_discard_draft' => 'Buang Draf',
+    'pages_edit_set_changelog' => 'Setel Changelog',
+    'pages_edit_enter_changelog_desc' => 'Masukkan deskripsi singkat tentang perubahan yang Anda buat',
+    'pages_edit_enter_changelog' => 'Masuk ke Changelog',
+    'pages_save' => 'Simpan Halaman',
+    'pages_title' => 'Judul Halaman',
+    'pages_name' => 'Nama Halaman',
+    'pages_md_editor' => 'Editor',
+    'pages_md_preview' => 'Pratinjau',
+    'pages_md_insert_image' => 'Sisipkan Gambar',
+    'pages_md_insert_link' => 'Sisipkan Tautan Entitas',
+    'pages_md_insert_drawing' => 'Sisipkan Gambar',
+    'pages_not_in_chapter' => 'Halaman tidak dalam satu bab',
+    'pages_move' => 'Pindahkan Halaman',
+    'pages_move_success' => 'Halaman dipindahkan ke ":parentName"',
+    'pages_copy' => 'Salin Halaman',
+    'pages_copy_desination' => 'Salin Tujuan',
+    'pages_copy_success' => 'Halaman berhasil disalin',
+    'pages_permissions' => 'Izin Halaman',
+    'pages_permissions_success' => 'Izin halaman diperbarui',
+    'pages_revision' => 'Revisi',
+    'pages_revisions' => 'Revisi Halaman',
+    'pages_revisions_named' => 'Revisi Halaman untuk :pageName',
+    'pages_revision_named' => 'Revisi Halaman untuk :pageName',
+    'pages_revision_restored_from' => 'Dipulihkan dari #:id; :summary',
+    'pages_revisions_created_by' => 'Dibuat Oleh',
+    'pages_revisions_date' => 'Tanggal Revisi',
+    'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revisi #:id',
+    'pages_revisions_numbered_changes' => 'Revisi #:id Berubah',
+    'pages_revisions_changelog' => 'Changelog',
+    'pages_revisions_changes' => 'Perubahan',
+    'pages_revisions_current' => 'Versi sekarang',
+    'pages_revisions_preview' => 'Pratinjau',
+    'pages_revisions_restore' => 'Mengembalikan',
+    'pages_revisions_none' => 'Halaman ini tidak memiliki revisi',
+    'pages_copy_link' => 'Salin tautan',
+    'pages_edit_content_link' => 'Edit Konten',
+    'pages_permissions_active' => 'Izin Halaman Aktif',
+    'pages_initial_revision' => 'Penerbitan awal',
+    'pages_initial_name' => 'Halaman Baru',
+    'pages_editing_draft_notification' => 'Anda sedang mengedit draf yang terakhir disimpan :timeDiff.',
+    'pages_draft_edited_notification' => 'Halaman ini telah diperbarui sejak saat itu. Anda disarankan untuk membuang draf ini.',
+    'pages_draft_edit_active' => [
+        'start_a' => ':count pengguna sudah mulai mengedit halaman ini',
+        'start_b' => ':userName sudah mulai mengedit halaman ini',
+        'time_a' => 'perubahan di sini disimpan secara instan',
+        'time_b' => 'di akhir :minCount menit',
+        'message' => ':start :time. Berhati-hatilah untuk tidak menimpa pembaruan satu sama lain!',
+    ],
+    'pages_draft_discarded' => 'Draf dibuang, Editor telah diperbarui dengan konten halaman saat ini',
+    'pages_specific' => 'Halaman Tertentu',
+    'pages_is_template' => 'Template Halaman',
+
+    // Editor Sidebar
+    'page_tags' => 'Halaman Tag',
+    'chapter_tags' => 'Bab Tag',
+    'book_tags' => 'Tag Buku',
+    'shelf_tags' => 'Tag Rak',
+    'tag' => 'Tag',
+    'tags' =>  'Semua Tag',
+    'tag_name' =>  'Nama Tag',
+    'tag_value' => 'Nilai Tag (opsional)',
+    'tags_explain' => "Tambahkan beberapa tag untuk mengkategorikan konten Anda dengan lebih baik.\n Anda dapat menetapkan nilai ke tag untuk pengaturan yang lebih mendalam.",
+    'tags_add' => 'Tambahkan tag lain',
+    'tags_remove' => 'Hapus tag ini',
+    'attachments' => 'Lampiran',
+    'attachments_explain' => 'Unggah beberapa file atau lampirkan beberapa tautan untuk ditampilkan di laman Anda. Ini terlihat di sidebar halaman.',
+    'attachments_explain_instant_save' => 'Perubahan di sini disimpan secara instan.',
+    'attachments_items' => 'Item Terlampir',
+    'attachments_upload' => 'Unggah File',
+    'attachments_link' => 'Lampirkan Tautan',
+    'attachments_set_link' => 'Setel Tautan',
+    'attachments_delete' => 'Anda yakin ingin menghapus lampiran ini?',
+    'attachments_dropzone' => 'Jatuhkan file atau klik di sini untuk melampirkan file',
+    'attachments_no_files' => 'Tidak ada file yang telah diunggah',
+    'attachments_explain_link' => 'Anda dapat melampirkan link jika Anda memilih untuk tidak mengupload file. Ini bisa berupa tautan ke halaman lain atau tautan ke file di cloud.',
+    'attachments_link_name' => 'Nama Tautan',
+    'attachment_link' => 'Lampiran Tautan',
+    'attachments_link_url' => 'Tautan ke file',
+    'attachments_link_url_hint' => 'Url situs atau file',
+    'attach' => 'Melampirkan',
+    'attachments_insert_link' => 'Tambahkan Tautan Lampiran ke Halaman',
+    'attachments_edit_file' => 'Edit File',
+    'attachments_edit_file_name' => 'Nama file',
+    'attachments_edit_drop_upload' => 'Lepaskan file atau klik di sini untuk mengupload dan menimpa',
+    'attachments_order_updated' => 'Urutan lampiran diperbarui',
+    'attachments_updated_success' => 'Detail lampiran diperbarui',
+    'attachments_deleted' => 'Lampiran dihapus',
+    'attachments_file_uploaded' => 'File berhasil diunggah',
+    'attachments_file_updated' => 'File berhasil diperbarui',
+    'attachments_link_attached' => 'Tautan berhasil dilampirkan ke halaman',
+    'templates' => 'Template',
+    'templates_set_as_template' => 'Halaman adalah template',
+    'templates_explain_set_as_template' => 'Anda dapat mengatur halaman ini sebagai template sehingga isinya dapat digunakan saat membuat halaman lain. Pengguna lain akan dapat menggunakan template ini jika mereka memiliki izin melihat halaman ini.',
+    'templates_replace_content' => 'Ganti Halaman Konten',
+    'templates_append_content' => 'Tambahkan ke halaman konten',
+    'templates_prepend_content' => 'Tambahkan ke halaman konten',
+
+    // Profile View
+    'profile_user_for_x' => 'Pengguna untuk :time',
+    'profile_created_content' => 'Konten yang Dibuat',
+    'profile_not_created_pages' => ':userName belum membuat halaman apa pun',
+    'profile_not_created_chapters' => ':userName belum membuat bab apa pun',
+    'profile_not_created_books' => ':userName belum membuat buku apa pun',
+    'profile_not_created_shelves' => ':userName belum membuat rak apa pun',
+
+    // Comments
+    'comment' => 'Komentar',
+    'comments' => 'Komentar',
+    'comment_add' => 'Tambah Komentar',
+    'comment_placeholder' => 'Tinggalkan komentar disini',
+    'comment_count' => '{0} Tidak ada komentar |{1} 1 Komentar |[2,*] :count Komentar',
+    'comment_save' => 'Simpan Komentar',
+    'comment_saving' => 'Menyimpan Komentar...',
+    'comment_deleting' => 'Menghapus Komentar...',
+    'comment_new' => 'Komentar Baru',
+    'comment_created' => 'dikomentari oleh :createDiff',
+    'comment_updated' => 'Diperbarui :updateDiff oleh :username',
+    'comment_deleted_success' => 'Komentar telah dihapus',
+    'comment_created_success' => 'Komentar telah di tambahkan',
+    'comment_updated_success' => 'Komentar Telah diperbaharui',
+    'comment_delete_confirm' => 'Anda yakin ingin menghapus komentar ini?',
+    'comment_in_reply_to' => 'Sebagai balasan untuk :commentId',
+
+    // Revision
+    'revision_delete_confirm' => 'Anda yakin ingin menghapus revisi ini?',
+    'revision_restore_confirm' => 'Apakah Anda yakin ingin memulihkan revisi ini? Konten halaman saat ini akan diganti.',
+    'revision_delete_success' => 'Revisi dihapus',
+    'revision_cannot_delete_latest' => 'Tidak dapat menghapus revisi terakhir.'
+];
diff --git a/resources/lang/id/errors.php b/resources/lang/id/errors.php
new file mode 100644 (file)
index 0000000..a153c33
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Text shown in error messaging.
+ */
+return [
+
+    // Permissions
+    'permission' => 'Anda tidak memiliki izin untuk mengakses halaman yang diminta.',
+    'permissionJson' => 'Anda tidak memiliki izin untuk melakukan tindakan yang diminta.',
+
+    // Auth
+    'error_user_exists_different_creds' => 'Pengguna dengan email :email sudah ada tetapi dengan kredensial berbeda.',
+    'email_already_confirmed' => 'Email telah dikonfirmasi, Coba masuk.',
+    'email_confirmation_invalid' => 'Token konfirmasi ini tidak valid atau telah digunakan, Silakan coba mendaftar lagi.',
+    'email_confirmation_expired' => 'Token konfirmasi telah kedaluwarsa, Email konfirmasi baru telah dikirim.',
+    'email_confirmation_awaiting' => 'Alamat email untuk akun yang digunakan perlu dikonfirmasi',
+    'ldap_fail_anonymous' => 'Akses LDAP gagal menggunakan pengikatan anonim',
+    'ldap_fail_authed' => 'Akses LDAP gagal menggunakan detail dn & sandi yang diberikan',
+    '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 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 proses yang dimulai oleh aplikasi ini. Menavigasi kembali setelah masuk dapat menyebabkan masalah ini.',
+    'saml_fail_authed' => 'Login menggunakan :system gagal, sistem tidak memberikan otorisasi yang berhasil',
+    'social_no_action_defined' => 'Tidak ada tindakan yang ditentukan',
+    'social_login_bad_response' => "Kesalahan diterima selama :socialAccount :\n:error",
+    'social_account_in_use' => 'Ini:socialAccount sudah digunakan, Coba masuk melalui opsi :socialAccount.',
+    'social_account_email_in_use' => 'Email :email sudah digunakan. Jika Anda sudah memiliki akun, Anda dapat menghubungkan :socialAccount Anda dari pengaturan profil Anda.',
+    'social_account_existing' => 'Akun ini :socialAccount sudah dilampirkan ke profil Anda.',
+    'social_account_already_used_existing' => 'Akun ini :socialAccount sudah digunakan oleh pengguna lain.',
+    'social_account_not_used' => 'Akun :socialAccount tidak ditautkan ke pengguna mana pun. Harap lampirkan di pengaturan profil Anda. ',
+    'social_account_register_instructions' => 'Jika Anda belum memiliki akun, Anda dapat mendaftarkan akun menggunakan opsi :socialAccount.',
+    'social_driver_not_found' => 'Pengemudi sosial tidak ditemukan',
+    'social_driver_not_configured' => 'Pengaturan sosial :socialAccount Anda tidak dikonfigurasi dengan benar.',
+    'invite_token_expired' => 'Tautan undangan ini telah kedaluwarsa. Sebagai gantinya, Anda dapat mencoba mengatur ulang kata sandi akun Anda.',
+
+    // System
+    'path_not_writable' => 'Jalur file :filePath tidak dapat diunggah ke. Pastikan itu dapat ditulis ke server.',
+    'cannot_get_image_from_url' => 'Tidak bisa mendapatkan gambar dari :url',
+    'cannot_create_thumbs' => 'Server tidak dapat membuat thumbnail. Harap periksa apakah Anda telah memasang ekstensi GD PHP.',
+    'server_upload_limit' => 'Server tidak mengizinkan unggahan dengan ukuran ini. Harap coba ukuran file yang lebih kecil.',
+    'uploaded'  => 'Server tidak mengizinkan unggahan dengan ukuran ini. Harap coba ukuran file yang lebih kecil.',
+    'image_upload_error' => 'Terjadi kesalahan saat mengupload gambar',
+    'image_upload_type_error' => 'Jenis gambar yang diunggah tidak valid',
+    'file_upload_timeout' => 'Waktu unggah file telah habis.',
+
+    // Attachments
+    'attachment_not_found' => 'Lampiran tidak ditemukan',
+
+    // Pages
+    'page_draft_autosave_fail' => 'Gagal menyimpan draf. Pastikan Anda memiliki koneksi internet sebelum menyimpan halaman ini',
+    'page_custom_home_deletion' => 'Tidak dapat menghapus halaman saat disetel sebagai beranda',
+
+    // Entities
+    'entity_not_found' => 'Entitas tidak ditemukan',
+    'bookshelf_not_found' => 'Rak buku tidak ditemukan',
+    'book_not_found' => 'Buku tidak ditemukan',
+    'page_not_found' => 'Halaman tidak ditemukan',
+    'chapter_not_found' => 'Bab tidak ditemukan',
+    'selected_book_not_found' => 'Buku yang dipilih tidak ditemukan',
+    'selected_book_chapter_not_found' => 'Buku atau Bab yang dipilih tidak ditemukan',
+    'guests_cannot_save_drafts' => 'Tamu tidak dapat menyimpan Draf',
+
+    // Users
+    'users_cannot_delete_only_admin' => 'Anda tidak dapat menghapus satu-satunya admin',
+    'users_cannot_delete_guest' => 'Anda tidak dapat menghapus pengguna tamu',
+
+    // Roles
+    'role_cannot_be_edited' => 'Peran ini tidak dapat diedit',
+    'role_system_cannot_be_deleted' => 'Peran ini adalah peran sistem dan tidak dapat dihapus',
+    'role_registration_default_cannot_delete' => 'Peran ini tidak dapat dihapus jika disetel sebagai peran pendaftaran default',
+    'role_cannot_remove_only_admin' => 'Pengguna ini adalah satu-satunya pengguna yang ditetapkan ke peran administrator. Tetapkan peran administrator untuk pengguna lain sebelum mencoba untuk menghapusnya di sini.',
+
+    // Comments
+    'comment_list' => 'Terjadi kesalahan saat mengambil komentar.',
+    'cannot_add_comment_to_draft' => 'Anda tidak dapat menambahkan komentar ke draf.',
+    'comment_add' => 'Terjadi kesalahan saat menambahkan / memperbarui komentar.',
+    'comment_delete' => 'Terjadi kesalahan saat menghapus komentar.',
+    'empty_comment' => 'Tidak dapat menambahkan komentar kosong.',
+
+    // Error pages
+    '404_page_not_found' => 'Halaman tidak ditemukan',
+    'sorry_page_not_found' => 'Maaf, Halaman yang Anda cari tidak dapat ditemukan.',
+    'sorry_page_not_found_permission_warning' => 'Jika Anda mengharapkan halaman ini ada, Anda mungkin tidak memiliki izin untuk melihatnya.',
+    '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' => 'Kembali ke home',
+    'error_occurred' => 'Terjadi kesalahan',
+    'app_down' => ':appName sedang down sekarang',
+    'back_soon' => 'Ini akan segera kembali.',
+
+    // API errors
+    'api_no_authorization_found' => 'Tidak ada token otorisasi yang ditemukan pada permintaan tersebut',
+    'api_bad_authorization_format' => 'Token otorisasi ditemukan pada permintaan tetapi formatnya salah',
+    'api_user_token_not_found' => 'Tidak ditemukan token API yang cocok untuk token otorisasi yang diberikan',
+    'api_incorrect_token_secret' => 'Rahasia yang diberikan untuk token API bekas yang diberikan salah',
+    'api_user_no_api_permission' => 'Pemilik token API yang digunakan tidak memiliki izin untuk melakukan panggilan API',
+    'api_user_token_expired' => 'Token otorisasi yang digunakan telah kedaluwarsa',
+
+    // Settings & Maintenance
+    'maintenance_test_email_failure' => 'Kesalahan dilempar saat mengirim email uji:',
+
+];
diff --git a/resources/lang/id/pagination.php b/resources/lang/id/pagination.php
new file mode 100644 (file)
index 0000000..4898c74
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
+return [
+
+    'previous' => '&laquo; Sebelumnya',
+    'next'     => 'Lanjut &raquo;',
+
+];
diff --git a/resources/lang/id/passwords.php b/resources/lang/id/passwords.php
new file mode 100644 (file)
index 0000000..3ee2e4d
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
+return [
+
+    'password' => 'Kata sandi harus setidaknya delapan karakter dan sesuai dengan konfirmasi.',
+    'user' => "Kami tidak dapat menemukan pengguna dengan alamat email tersebut.",
+    'token' => 'Token setel ulang sandi tidak valid untuk alamat email ini.',
+    'sent' => 'Kami telah mengirimkan email tautan pengaturan ulang kata sandi Anda!',
+    'reset' => 'Kata sandi Anda telah disetel ulang!',
+
+];
diff --git a/resources/lang/id/settings.php b/resources/lang/id/settings.php
new file mode 100644 (file)
index 0000000..7f83fba
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
+return [
+
+    // Common Messages
+    'settings' => 'Pengaturan',
+    'settings_save' => 'Simpan Pengaturan',
+    'settings_save_success' => 'Pengaturan disimpan',
+
+    // App Settings
+    'app_customization' => 'Kustomisasi',
+    'app_features_security' => 'Fitur & Keamanan',
+    'app_name' => 'Nama aplikasi',
+    'app_name_desc' => 'Nama ini ditampilkan di tajuk dan di semua email yang dikirim oleh sistem.',
+    'app_name_header' => 'Tampilkan nama di header',
+    'app_public_access' => 'Akses publik',
+    'app_public_access_desc' => 'Mengaktifkan opsi ini akan memungkinkan pengunjung, yang tidak masuk, untuk mengakses konten dalam contoh BookStack Anda.',
+    'app_public_access_desc_guest' => 'Akses untuk pengunjung umum dapat dikontrol melalui pengguna "Tamu".',
+    'app_public_access_toggle' => 'Izinkan akses publik',
+    'app_public_viewing' => 'Izinkan tontonan publik?',
+    'app_secure_images' => 'Unggahan Gambar Keamanan Lebih Tinggi',
+    'app_secure_images_toggle' => 'Aktifkan unggahan gambar dengan keamanan lebih tinggi',
+    'app_secure_images_desc' => 'Untuk alasan performa, semua gambar bersifat publik. Opsi ini menambahkan string acak yang sulit ditebak di depan url gambar. Pastikan indeks direktori tidak diaktifkan untuk mencegah akses mudah.',
+    'app_editor' => 'Halaman Editor',
+    'app_editor_desc' => 'Pilih editor mana yang akan digunakan oleh semua pengguna untuk mengedit halaman.',
+    'app_custom_html' => 'Kustom Konten HTML Head',
+    'app_custom_html_desc' => 'Konten apa pun yang ditambahkan di sini akan dimasukkan ke bagian bawah <head> bagian dari setiap halaman. Ini berguna untuk mengganti gaya atau menambahkan kode analitik.',
+    'app_custom_html_disabled_notice' => 'Kustom konten HTML Head dinonaktifkan pada halaman pengaturan ini untuk memastikan setiap perubahan yang mengganggu dapat dikembalikan.',
+    'app_logo' => 'Logo Aplikasi',
+    'app_logo_desc' => 'Gambar ini seharusnya memiliki ketinggian 43px Gambar besar akan diperkecil.',
+    'app_primary_color' => 'Warna Utama Aplikasi',
+    'app_primary_color_desc' => 'Menyetel warna utama untuk aplikasi termasuk spanduk, tombol, dan tautan.',
+    'app_homepage' => 'Beranda Aplikasi',
+    'app_homepage_desc' => 'Pilih tampilan untuk ditampilkan di beranda alih-alih tampilan default. Izin halaman diabaikan untuk halaman yang dipilih.',
+    'app_homepage_select' => 'Pilih halaman',
+    'app_footer_links' => 'Link Footer',
+    'app_footer_links_desc' => 'Tambahkan link untuk ditampilkan dalam footer situs. Ini akan ditampilkan di bagian bawah kebanyakan halaman, termasuk yang tidak memerlukan login. Anda dapat menggunakan label "trans::<key>" untuk menggunakan terjemahan yang ditentukan sistem. Sebagai contoh: Menggunakan "trans::common.privacy_policy" akan memberikan teks terjemahan "Privacy Policy" dan akan memberikan teks "Terms of Service".terjemahan trans::common.terms_of_service".',
+    'app_footer_links_label' => 'Link Label',
+    'app_footer_links_url' => 'Link URL',
+    'app_footer_links_add' => 'Tambahkan Link Footer',
+    'app_disable_comments' => 'Nonaktifkan Komentar',
+    'app_disable_comments_toggle' => 'Nonaktifkan komentar',
+    'app_disable_comments_desc' => 'Menonaktifkan komentar di semua halaman dalam aplikasi. <br> Komentar yang ada tidak ditampilkan.',
+
+    // Color settings
+    'content_colors' => 'Warna Konten',
+    'content_colors_desc' => 'Menyetel warna untuk semua elemen dalam hierarki organisasi halaman. Disarankan memilih warna dengan kecerahan yang mirip dengan warna default agar mudah dibaca.',
+    'bookshelf_color' => 'Warna Rak',
+    'book_color' => 'Warna Buku',
+    'chapter_color' => 'Warna Bab',
+    'page_color' => 'Warna Halaman',
+    'page_draft_color' => 'Warna Halaman Draf',
+
+    // Registration Settings
+    'reg_settings' => 'Pendaftaran',
+    'reg_enable' => 'Aktifkan Pendaftaran',
+    'reg_enable_toggle' => 'Aktifkan Pendaftaran',
+    'reg_enable_desc' => 'Saat pendaftaran diaktifkan, pengguna akan dapat mendaftar sendiri sebagai pengguna aplikasi. Setelah pendaftaran, mereka diberi peran pengguna default tunggal.',
+    'reg_default_role' => 'Peran pengguna default setelah pendaftaran',
+    'reg_enable_external_warning' => 'Opsi di atas diabaikan saat otentikasi LDAP atau SAML eksternal aktif. Akun pengguna untuk anggota yang tidak ada akan dibuat secara otomatis jika otentikasi, terhadap sistem eksternal yang digunakan, berhasil.',
+    'reg_email_confirmation' => 'Konfirmasi email',
+    'reg_email_confirmation_toggle' => 'Memerlukan konfirmasi email',
+    'reg_confirm_email_desc' => 'Jika batasan domain digunakan maka konfirmasi email akan diperlukan dan opsi ini akan diabaikan.',
+    'reg_confirm_restrict_domain' => 'Pembatasan Domain',
+    'reg_confirm_restrict_domain_desc' => 'Masukkan daftar domain email yang dipisahkan dengan koma yang ingin Anda batasi pendaftarannya. Pengguna akan dikirimi email untuk mengonfirmasi alamat mereka sebelum diizinkan untuk berinteraksi dengan aplikasi. <br> Perhatikan bahwa pengguna akan dapat mengubah alamat email mereka setelah pendaftaran berhasil.',
+    'reg_confirm_restrict_domain_placeholder' => 'Tidak ada batasan yang ditetapkan',
+
+    // Maintenance settings
+    'maint' => 'Pemeliharaan',
+    'maint_image_cleanup' => 'Gambar Bersihkan',
+    'maint_image_cleanup_desc' => "Pindai halaman & konten revisi untuk memeriksa gambar dan gambar mana yang saat ini digunakan dan gambar mana yang berlebihan. Pastikan Anda membuat database lengkap dan cadangan gambar sebelum menjalankan ini.",
+    'maint_delete_images_only_in_revisions' => 'Hapus juga gambar yang hanya ada di revisi halaman lama',
+    'maint_image_cleanup_run' => 'Jalankan Pembersihan',
+    'maint_image_cleanup_warning' => ':count ditemukan gambar yang berpotensi tidak digunakan. Anda yakin ingin menghapus gambar-gambar ini?',
+    'maint_image_cleanup_success' => ':count gambar yang mungkin tidak digunakan ditemukan dan dihapus!',
+    'maint_image_cleanup_nothing_found' => 'Tidak ada gambar yang tidak digunakan ditemukan, Tidak ada yang dihapus!',
+    'maint_send_test_email' => 'Kirim Email Tes',
+    'maint_send_test_email_desc' => 'Ini mengirimkan email percobaan ke alamat email Anda yang ditentukan di profil Anda.',
+    'maint_send_test_email_run' => 'Kirim email tes',
+    'maint_send_test_email_success' => 'Email dikirim ke :address',
+    'maint_send_test_email_mail_subject' => 'Uji Email',
+    'maint_send_test_email_mail_greeting' => 'Pengiriman email sepertinya berhasil!',
+    'maint_send_test_email_mail_text' => 'Selamat! Saat Anda menerima pemberitahuan email ini, pengaturan email Anda tampaknya telah dikonfigurasi dengan benar.',
+    'maint_recycle_bin_desc' => 'Rak, buku, bab & halaman yang dihapus dikirim ke recycle bin sehingga dapat dipulihkan atau dihapus secara permanen. Item lama di recycle bin dapat dihapus secara otomatis setelah beberapa saat tergantung pada konfigurasi sistem.',
+    'maint_recycle_bin_open' => 'Buka Tempat Sampah',
+
+    // Recycle Bin
+    'recycle_bin' => 'Tempat Sampah',
+    'recycle_bin_desc' => 'Di sini Anda dapat memulihkan item yang telah dihapus atau memilih untuk menghapusnya secara permanen dari sistem. Daftar ini tidak difilter, tidak seperti daftar aktivitas serupa di sistem tempat filter izin diterapkan.',
+    'recycle_bin_deleted_item' => 'Item yang Dihapus',
+    'recycle_bin_deleted_by' => 'Dihapus Oleh',
+    'recycle_bin_deleted_at' => 'Waktu Penghapusan',
+    'recycle_bin_permanently_delete' => 'Hapus Permanen',
+    'recycle_bin_restore' => 'Mengembalikan',
+    'recycle_bin_contents_empty' => 'Hapus Secara Permanen',
+    'recycle_bin_empty' => 'Kosongkan Tempat Sampah',
+    'recycle_bin_empty_confirm' => 'Ini akan menghancurkan secara permanen semua item di tempat sampah termasuk konten yang ada di dalam setiap item. Anda yakin ingin mengosongkan tempat sampah?',
+    'recycle_bin_destroy_confirm' => 'Tindakan ini akan menghapus item ini secara permanen, bersama dengan elemen turunan apa pun yang tercantum di bawah, dari sistem dan Anda tidak akan dapat memulihkan konten ini. Anda yakin ingin menghapus item ini secara permanen?',
+    'recycle_bin_destroy_list' => 'Item yang akan Dihancurkan',
+    'recycle_bin_restore_list' => 'Item yang akan Dipulihkan',
+    'recycle_bin_restore_confirm' => 'Tindakan ini akan memulihkan item yang dihapus, termasuk semua elemen anak, ke lokasi aslinya. Jika lokasi asli telah dihapus, dan sekarang berada di keranjang sampah, item induk juga perlu dipulihkan.',
+    'recycle_bin_restore_deleted_parent' => 'Induk item ini juga telah dihapus. Ini akan tetap dihapus sampai induknya juga dipulihkan.',
+    'recycle_bin_destroy_notification' => 'Total :count item dari tempat sampah.',
+    'recycle_bin_restore_notification' => 'Total :count item yang dipulihkan dari tempat sampah.',
+
+    // Audit Log
+    'audit' => 'Log Audit',
+    'audit_desc' => 'Log audit ini menampilkan daftar aktivitas yang dilacak dalam sistem. Daftar ini tidak difilter, tidak seperti daftar aktivitas serupa di sistem tempat filter izin diterapkan.',
+    'audit_event_filter' => 'Filter Peristiwa',
+    'audit_event_filter_no_filter' => 'Tanpa Filter',
+    'audit_deleted_item' => 'Item yang Dihapus',
+    'audit_deleted_item_name' => 'Nama :name',
+    'audit_table_user' => 'Pengguna',
+    'audit_table_event' => 'Peristiwa',
+    'audit_table_related' => 'Item atau Detail Terkait',
+    'audit_table_date' => 'Tanggal Kegiatan',
+    'audit_date_from' => 'Rentang Tanggal Dari',
+    'audit_date_to' => 'Rentang Tanggal Sampai',
+
+    // Role Settings
+    'roles' => 'Peran',
+    'role_user_roles' => 'Peran Pengguna',
+    'role_create' => 'Buat Peran Baru',
+    'role_create_success' => 'Peran berhasil dibuat',
+    'role_delete' => 'Hapus Peran',
+    'role_delete_confirm' => 'Ini akan menghapus peran dengan nama \':roleName\'.',
+    'role_delete_users_assigned' => 'Peran ini memiliki :userCount pengguna yang ditugaskan padanya. Jika Anda ingin memindahkan pengguna dari peran ini pilih peran baru di bawah.',
+    'role_delete_no_migration' => "Jangan migrasikan pengguna",
+    'role_delete_sure' => 'Anda yakin ingin menghapus peran ini?',
+    'role_delete_success' => 'Peran berhasil dihapus',
+    'role_edit' => 'Edit Peran',
+    'role_details' => 'Detail Peran',
+    'role_name' => 'Nama peran',
+    'role_desc' => 'Deskripsi Singkat Peran',
+    'role_external_auth_id' => 'Otentikasi Eksternal IDs',
+    'role_system' => 'Izin Sistem',
+    'role_manage_users' => 'Kelola pengguna',
+    'role_manage_roles' => 'Kelola peran & izin peran',
+    'role_manage_entity_permissions' => 'Kelola semua izin buku, bab & halaman',
+    'role_manage_own_entity_permissions' => 'Kelola izin di buku, bab & halaman sendiri',
+    'role_manage_page_templates' => 'Kelola template halaman',
+    'role_access_api' => 'Akses Sistem API',
+    'role_manage_settings' => 'Kelola setelan aplikasi',
+    'role_asset' => 'Izin Aset',
+    'roles_system_warning' => 'Ketahuilah bahwa akses ke salah satu dari tiga izin di atas dapat memungkinkan pengguna untuk mengubah hak mereka sendiri atau orang lain dalam sistem. Hanya tetapkan peran dengan izin ini untuk pengguna tepercaya.',
+    'role_asset_desc' => 'Izin ini mengontrol akses default ke aset dalam sistem. Izin pada Buku, Bab, dan Halaman akan menggantikan izin ini.',
+    'role_asset_admins' => 'Admin secara otomatis diberi akses ke semua konten tetapi opsi ini dapat menampilkan atau menyembunyikan opsi UI.',
+    'role_all' => 'Semua',
+    'role_own' => 'Sendiri',
+    'role_controlled_by_asset' => 'Dikendalikan oleh aset tempat mereka diunggah',
+    'role_save' => 'Simpan Peran',
+    'role_update_success' => 'Peran berhasil diperbarui',
+    'role_users' => 'Peran berhasil diperbarui',
+    'role_users_none' => 'Saat ini tidak ada pengguna yang ditugaskan untuk peran ini',
+
+    // Users
+    'users' => 'Pengguna',
+    'user_profile' => 'Profil Pengguna',
+    'users_add_new' => 'Tambahkan pengguna baru',
+    'users_search' => 'Cari Pengguna',
+    'users_latest_activity' => 'Aktivitas Terbaru',
+    'users_details' => 'Detail Pengguna',
+    'users_details_desc' => 'Tetapkan nama tampilan dan alamat email untuk pengguna ini. Alamat email akan digunakan untuk masuk ke aplikasi.',
+    'users_details_desc_no_email' => 'Tetapkan nama tampilan untuk pengguna ini agar orang lain dapat mengenalinya.',
+    'users_role' => 'Peran Pengguna',
+    'users_role_desc' => 'Pilih peran mana yang akan ditetapkan untuk pengguna ini. Jika pengguna ditetapkan ke beberapa peran, izin dari peran tersebut akan bertumpuk dan mereka akan menerima semua kemampuan dari peran yang ditetapkan.',
+    'users_password' => 'Kata Sandi Pengguna',
+    'users_password_desc' => 'Atur kata sandi yang digunakan untuk masuk ke aplikasi. Panjangnya minimal harus 6 karakter.',
+    'users_send_invite_text' => 'Anda dapat memilih untuk mengirimi pengguna ini email undangan yang memungkinkan mereka menyetel sandi mereka sendiri, atau Anda dapat menyetel sandi mereka sendiri.',
+    'users_send_invite_option' => 'Kirim email undangan pengguna',
+    'users_external_auth_id' => 'Otentikasi Eksternal ID',
+    'users_external_auth_id_desc' => 'Ini adalah ID yang digunakan untuk mencocokkan pengguna ini saat berkomunikasi dengan sistem otentikasi eksternal Anda.',
+    'users_password_warning' => 'Hanya isi di bawah ini jika Anda ingin mengubah kata sandi Anda.',
+    'users_system_public' => 'Pengguna ini mewakili semua pengguna tamu yang mengunjungi instance Anda. Ini tidak dapat digunakan untuk masuk tetapi ditetapkan secara otomatis.',
+    'users_delete' => 'Hapus pengguna',
+    'users_delete_named' => 'Hapus Pengguna :userName',
+    'users_delete_warning' => 'Ini sepenuhnya akan menghapus pengguna ini dengan nama \':userName\' dari sistem.',
+    'users_delete_confirm' => 'Apakah Anda yakin ingin menghapus pengguna ini?',
+    'users_migrate_ownership' => 'Migrasikan Kepemilikan',
+    'users_migrate_ownership_desc' => 'Pilih pengguna di sini jika Anda ingin pengguna lain menjadi pemilik semua item yang saat ini dimiliki oleh pengguna ini.',
+    'users_none_selected' => 'Tidak ada pengguna yang dipilih',
+    'users_delete_success' => 'Pengguna berhasil dihapus',
+    'users_edit' => 'Edit Pengguna',
+    'users_edit_profile' => 'Edit Profil',
+    'users_edit_success' => 'Pengguna berhasil diperbarui',
+    'users_avatar' => 'Abatar Pengguna',
+    'users_avatar_desc' => 'Pilih gambar untuk mewakili pengguna ini. berukuran 256px.',
+    'users_preferred_language' => 'Bahasa Pilihan',
+    'users_preferred_language_desc' => 'Opsi ini akan mengubah bahasa yang digunakan untuk antarmuka pengguna aplikasi. Ini tidak akan memengaruhi konten yang dibuat pengguna.',
+    'users_social_accounts' => 'Akun Sosial',
+    'users_social_accounts_info' => 'Di sini Anda dapat menghubungkan akun Anda yang lain untuk login yang lebih cepat dan mudah. Memutuskan akun di sini tidak mencabut akses resmi sebelumnya. Cabut akses dari pengaturan profil Anda pada akun sosial yang terhubung.',
+    'users_social_connect' => 'Hubungkan Akun',
+    'users_social_disconnect' => 'Putuskan Sambungan Akun',
+    'users_social_connected' => ':socialAccount akun berhasil dilampirkan ke profil Anda.',
+    'users_social_disconnected' => ':socialAccount akun berhasil diputuskan dari profil Anda.',
+    'users_api_tokens' => 'Token API',
+    'users_api_tokens_none' => 'Tidak ada token API yang telah dibuat untuk pengguna ini',
+    'users_api_tokens_create' => 'Buat Token',
+    'users_api_tokens_expires' => 'Kedaluwarsa',
+    'users_api_tokens_docs' => 'Dokumentasi API',
+
+    // API Tokens
+    'user_api_token_create' => 'Buat Token API',
+    'user_api_token_name' => 'Nama',
+    'user_api_token_name_desc' => 'Berikan token Anda nama yang dapat dibaca sebagai pengingat masa depan akan tujuan yang dimaksudkan.',
+    'user_api_token_expiry' => 'Tanggal kadaluarsa',
+    'user_api_token_expiry_desc' => 'Setel tanggal token ini kedaluwarsa. Setelah tanggal ini, permintaan yang dibuat menggunakan token ini tidak akan berfungsi lagi. Mengosongkan bidang ini akan menetapkan masa berlaku 100 tahun ke depan.',
+    'user_api_token_create_secret_message' => 'Segera setelah membuat token ini, "Token ID" & "Token Secret" akan dibuat dan ditampilkan. Rahasianya hanya akan ditampilkan satu kali jadi pastikan untuk menyalin nilainya ke tempat yang aman dan terlindungi sebelum melanjutkan.',
+    'user_api_token_create_success' => 'Token API berhasil dibuat',
+    'user_api_token_update_success' => 'Token API berhasil diperbarui',
+    'user_api_token' => 'Token API',
+    'user_api_token_id' => 'Token ID',
+    'user_api_token_id_desc' => 'Ini adalah pengenal yang dibuat oleh sistem yang tidak dapat diedit untuk token ini yang perlu disediakan dalam permintaan API.',
+    'user_api_token_secret' => 'Token Secret',
+    'user_api_token_secret_desc' => 'Ini adalah rahasia yang dihasilkan sistem untuk token ini yang perlu disediakan dalam permintaan API. Ini hanya akan ditampilkan kali ini jadi salin nilai ini ke tempat yang aman dan terlindungi.',
+    'user_api_token_created' => 'Token dibuat :timeAgo',
+    'user_api_token_updated' => 'Token diperbarui :timeAgo',
+    'user_api_token_delete' => 'Hapus Token',
+    'user_api_token_delete_warning' => 'Ini akan sepenuhnya menghapus token API ini dengan nama \': tokenName\' dari sistem.',
+    'user_api_token_delete_confirm' => 'Anda yakin ingin menghapus token API ini?',
+    'user_api_token_delete_success' => 'Token API berhasil dihapus',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Katalan',
+        'cs' => 'Česky',
+        'da' => 'Dansk',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'he' => 'עברית',
+        'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
+        'it' => 'Italian',
+        'ja' => '日本語',
+        'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
+        'nl' => 'Nederlands',
+        'nb' => 'Norsk (Bokmål)',
+        'pl' => 'Polski',
+        'pt' => 'Português',
+        'pt_BR' => 'Português do Brasil',
+        'ru' => 'Русский',
+        'sk' => 'Slovensky',
+        'sl' => 'Slovenščina',
+        'sv' => 'Svenska',
+        'tr' => 'Türkçe',
+        'uk' => 'Українська',
+        'vi' => 'Tiếng Việt',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+    ]
+    //!////////////////////////////////
+];
diff --git a/resources/lang/id/validation.php b/resources/lang/id/validation.php
new file mode 100644 (file)
index 0000000..10ac290
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
+return [
+
+    // Standard laravel validation lines
+    'accepted'             => ':attribute harus diterima.',
+    'active_url'           => ':attribute bukan URL yang valid.',
+    'after'                => ':attribute harus setelah tanggal :date.',
+    'alpha'                => ':attribute hanya boleh berisi huruf.',
+    'alpha_dash'           => ':attribute hanya boleh berisi huruf, angka, tanda hubung, dan garis bawah.',
+    'alpha_num'            => ':attribute hanya boleh berisi huruf dan angka.',
+    'array'                => ':attribute harus berupa larik.',
+    'before'               => ':attribute harus tanggal sebelum :date.',
+    'between'              => [
+        'numeric' => ':attribute harus di antara :min dan :max.',
+        'file'    => ':attribute harus diantara :min dan :max kilobyte.',
+        'string'  => ':attribute harus memiliki karakter antara :min dan :max.',
+        'array'   => ':attribute harus memiliki item antara :min dan :max.',
+    ],
+    'boolean'              => ':attribute bidang harus berisi benar atau salah.',
+    'confirmed'            => ':attribute konfirmasi tidak sama.',
+    'date'                 => ':attribute bukan tanggal yang valid.',
+    'date_format'          => ':attribute tidak sesuai dengan format :format.',
+    'different'            => ':attribute dan :other harus berbeda.',
+    'digits'               => ':attribute harus :digits digit.',
+    'digits_between'       => ':attribute harus diantara :min dan :max digit.',
+    'email'                => ':attrtibute Harus alamat e-mail yang valid.',
+    'ends_with' => ':attribute harus diakhiri dengan salah satu dari berikut ini: :values',
+    'filled'               => ':attribute bidang diperlukan.',
+    'gt'                   => [
+        'numeric' => ':attribute harus lebih besar dari :value.',
+        'file'    => ':attribute harus lebih besar dari :value kilobyte.',
+        'string'  => ':attribute harus lebih besar dari :value karakter.',
+        'array'   => ':attribute harus memiliki lebih dari item :value.',
+    ],
+    'gte'                  => [
+        'numeric' => ':attribute harus lebih besar dari atau sama dengan :value.',
+        'file'    => ':attribute harus lebih besar dari atau sama dengan :value kilobyte.',
+        'string'  => ':attribute harus lebih besar dari atau sama dengan karakter :value.',
+        'array'   => ':attribute harus memiliki :value item atau lebih.',
+    ],
+    'exists'               => ':attribute yang dipilih tidak valid.',
+    'image'                => ':attribute harus berupa gambar.',
+    'image_extension'      => ':attribute harus memiliki ekstensi gambar yang valid & didukung.',
+    'in'                   => ':attribute yang dipilih tidak valid.',
+    'integer'              => ':attribute harus berupa bilangan bulat.',
+    'ip'                   => ':attribute harus berupa alamat IP yang valid.',
+    'ipv4'                 => ':attribute harus berupa alamat IPv4 yang valid.',
+    'ipv6'                 => ':attribute harus berupa alamat IPv6 yang valid.',
+    'json'                 => ':attribute harus berupa string JSON yang valid.',
+    'lt'                   => [
+        'numeric' => ':attribute harus kurang dari :value.',
+        'file'    => ':attribute harus kurang dari :value kilobyte.',
+        'string'  => ':attribute harus kurang dari :value karakter.',
+        'array'   => ':attribute harus memiliki kurang dari :value item.',
+    ],
+    'lte'                  => [
+        'numeric' => ':attribute harus kurang dari atau sama dengan :value.',
+        'file'    => ':attribute harus kurang dari atau sama dengan :value kilobyte.',
+        'string'  => ':attribute harus kurang dari atau sama dengan :value karakter.',
+        'array'   => ':attribute tidak boleh memiliki lebih dari :value item.',
+    ],
+    'max'                  => [
+        'numeric' => ':attribute tidak boleh lebih dari :max.',
+        'file'    => ':attribute tidak boleh lebih dari :max kilobyte.',
+        'string'  => ':attribute tidak boleh lebih dari :max karakter.',
+        'array'   => ':attribute tidak boleh memiliki lebih dari :max item.',
+    ],
+    'mimes'                => ':attribute harus berupa file dengan tipe: :value.',
+    'min'                  => [
+        'numeric' => ':attribute minimal harus :min.',
+        'file'    => ':attribute minimal harus :min kilobyte.',
+        'string'  => ':attribute setidaknya harus :min karakter.',
+        'array'   => ':attribute minimal harus memiliki :min item.',
+    ],
+    'not_in'               => ':attribute yang dipilih tidak valid.',
+    'not_regex'            => ':attribute format tidak valid.',
+    'numeric'              => ':attribute harus berupa nomot.',
+    'regex'                => 'Format :attribute tidak valid.',
+    'required'             => ':attribute bidang harus diisi.',
+    'required_if'          => ':attribute Bidang harus diisi saat :other atau :value.',
+    'required_with'        => 'Bidang :attribute harus diisi jika ada :nilai.',
+    'required_with_all'    => 'Bidang :attribute harus diisi jika ada :values.',
+    'required_without'     => 'Bidang :attribute harus diisi jika :values tidak ada.',
+    'required_without_all' => 'Bidang :attribute harus diisi jika tidak ada :value yang ada.',
+    'same'                 => ':attribute dan :other harus sama.',
+    'safe_url'             => 'Tautan yang diberikan mungkin tidak aman.',
+    'size'                 => [
+        'numeric' => ':attribute harus berukuran :size.',
+        'file'    => ':attribute harus berukuran :size kilobyte.',
+        'string'  => ':attribute harus memiliki karakter berukuran :size.',
+        'array'   => ':attribute harus mengandung :size item.',
+    ],
+    'string'               => ':attribute harus berupa string.',
+    'timezone'             => ':attribute harus menjadi zona yang valid.',
+    'unique'               => ':attribute sudah diambil.',
+    'url'                  => ':attribute format tidak valid.',
+    'uploaded'             => 'File tidak dapat diunggah. Server mungkin tidak menerima file dengan ukuran ini.',
+
+    // Custom validation lines
+    'custom' => [
+        'password-confirm' => [
+            'required_with' => 'Konfirmasi kata sandi diperlukan',
+        ],
+    ],
+
+    // Custom validation attributes
+    'attributes' => [],
+];
index 24b484181ef75f34c16350b0ee454163dd0a1e73..adf2888fc2e6bc4d13e9f07bc4e897befbbfaa62 100755 (executable)
@@ -6,7 +6,7 @@
 return [
 
     // Pages
-    'page_create'                 => 'ha creato la pagina',
+    'page_create'                 => 'pagina creata',
     'page_create_notification'    => 'Pagina Creata Correttamente',
     'page_update'                 => 'ha aggiornato la pagina',
     'page_update_notification'    => 'Pagina Aggiornata Correttamente',
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'ha commentato in',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => 'autorizzazioni aggiornate',
 ];
index 9676962bdc7f94c5f04b03a1a82e7b4b522da686..f24df1db90d9b335a12a8f24b3e23b5504b738c8 100755 (executable)
@@ -19,7 +19,7 @@ return [
     'description' => 'Descrizione',
     'role' => 'Ruolo',
     'cover_image' => 'Immagine di copertina',
-    'cover_image_description' => 'Questa immagine dovrebbe essere approssimatamente 440x250px.',
+    'cover_image_description' => 'Questa immagine dovrebbe essere approssimativamente 440x250px.',
     
     // Actions
     'actions' => 'Azioni',
@@ -33,7 +33,7 @@ return [
     'copy' => 'Copia',
     'reply' => 'Rispondi',
     'delete' => 'Elimina',
-    'delete_confirm' => 'Confirm Deletion',
+    'delete_confirm' => 'Conferma Eliminazione',
     'search' => 'Cerca',
     'search_clear' => 'Pulisci Ricerca',
     'reset' => 'Azzera',
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Ordine Ascendente',
     'sort_descending' => 'Ordine Discendente',
     'sort_name' => 'Nome',
+    'sort_default' => 'Predefinito',
     'sort_created_at' => 'Data Creazione',
     'sort_updated_at' => 'Data Aggiornamento',
 
@@ -64,7 +65,8 @@ return [
     'breadcrumb' => 'Navigazione',
 
     // Header
-    'profile_menu' => 'Menu del profilo',
+    'header_menu_expand' => 'Espandi Menù Intestazione',
+    'profile_menu' => 'Menù del profilo',
     'view_profile' => 'Visualizza Profilo',
     'edit_profile' => 'Modifica Profilo',
     'dark_mode' => 'Modalità Scura',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Tab: Mostra Informazioni Secondarie',
     'tab_content' => 'Contenuto',
+    'tab_content_label' => 'Tab: Mostra Contenuto Principale',
 
     // Email Content
     'email_action_help' => 'Se hai problemi nel cliccare il pulsante ":actionText", copia e incolla lo URL sotto nel tuo browser:',
     'email_rights' => 'Tutti i diritti riservati',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Norme sulla privacy',
+    'terms_of_service' => 'Condizioni del Servizio',
 ];
index aa1c8f4a68fea41b80bb5b590ec11ba72944b226..63637aa484fccf69450ce473aad73e8584767663 100755 (executable)
@@ -15,7 +15,7 @@ return [
     'image_load_more' => 'Carica Altre',
     'image_image_name' => 'Nome Immagine',
     'image_delete_used' => 'Questa immagine è usata nelle pagine elencate.',
-    'image_delete_confirm_text' => 'Are you sure you want to delete this image?',
+    'image_delete_confirm_text' => 'Sei sicuro di voler eliminare questa immagine?',
     'image_select_image' => 'Seleziona Immagine',
     'image_dropzone' => 'Rilascia immagini o clicca qui per caricarle',
     'images_deleted' => 'Immagini Eliminate',
index 2d8766655218455247a398dfb4f4a6afeb76d7c4..bf954c950aa1f6e6cda14dde4ac3ce123ed1d7c0 100755 (executable)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Creato :timeLength da :user',
     'meta_updated' => 'Aggiornato :timeLength',
     'meta_updated_name' => 'Aggiornato :timeLength da :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Creati da :user',
     'entity_select' => 'Selezione Entità',
     'images' => 'Immagini',
     'my_recent_drafts' => 'Bozze Recenti',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Una volta abilitati, questi permessi avranno la priorità su tutti gli altri.',
     'permissions_enable' => 'Abilita Permessi Custom',
     'permissions_save' => 'Salva Permessi',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Proprietario',
 
     // Search
     'search_results' => 'Risultati Ricerca',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Permessi impostati',
     'search_created_by_me' => 'Creati da me',
     'search_updated_by_me' => 'Aggiornati da me',
+    'search_owned_by_me' => 'Creati da me',
     'search_date_options' => 'Opzioni Data',
     'search_updated_before' => 'Aggiornati prima del',
     'search_updated_after' => 'Aggiornati dopo il',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Crea un nuovo capitolo',
     'chapters_delete' => 'Elimina Capitolo',
     'chapters_delete_named' => 'Elimina il capitolo :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_explain' => 'Procedendo si eliminerà il capitolo denominato \':chapterName\'. Anche le pagine in esso contenute saranno eliminate.',
     'chapters_delete_confirm' => 'Sei sicuro di voler eliminare questo capitolo?',
     'chapters_edit' => 'Elimina Capitolo',
     'chapters_edit_named' => 'Modifica il capitolo :chapterName',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Versioni Pagina',
     'pages_revisions_named' => 'Versioni della pagina :pageName',
     'pages_revision_named' => 'Versione della pagina :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Ripristinato da #:id; :summary',
     'pages_revisions_created_by' => 'Creata Da',
     'pages_revisions_date' => 'Data Versione',
     'pages_revisions_number' => '#',
@@ -268,7 +269,7 @@ return [
     'attachments_link_url' => 'Link al file',
     'attachments_link_url_hint' => 'Url del sito o del file',
     'attach' => 'Allega',
-    'attachments_insert_link' => 'Add Attachment Link to Page',
+    'attachments_insert_link' => 'Aggiungi Link Allegato alla Pagina',
     'attachments_edit_file' => 'Modifica File',
     'attachments_edit_file_name' => 'Nome File',
     'attachments_edit_drop_upload' => 'Rilascia file o clicca qui per caricare e sovrascrivere',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Sei sicuro di voler ripristinare questa revisione? Il contenuto della pagina verrà rimpiazzato.',
     'revision_delete_success' => 'Revisione cancellata',
     'revision_cannot_delete_latest' => 'Impossibile eliminare l\'ultima revisione.'
-];
\ No newline at end of file
+];
index 3e48ad762a942488b3c1d8bf99269fa2e3fb092a..67497f67557eddab2d0cd2e89c3e9d357f73661c 100755 (executable)
@@ -83,20 +83,23 @@ return [
     '404_page_not_found' => 'Pagina Non Trovata',
     'sorry_page_not_found' => 'La pagina che stavi cercando non è stata trovata.',
     'sorry_page_not_found_permission_warning' => 'Se pensi che questa pagina possa esistere, potresti non avere i permessi per visualizzarla.',
+    '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' => 'Ritorna alla home',
     'error_occurred' => 'C\'è Stato un errore',
     'app_down' => ':appName è offline',
     'back_soon' => 'Ritornerà presto.',
 
     // API errors
-    'api_no_authorization_found' => 'No authorization token found on the request',
+    'api_no_authorization_found' => 'Nessun token di autorizzazione trovato nella richiesta',
     'api_bad_authorization_format' => 'Un token di autorizzazione è stato trovato nella richiesta, ma il formato sembra non corretto',
-    '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_token_not_found' => 'Nessun token API valido è stato trovato nel token di autorizzazione fornito',
+    'api_incorrect_token_secret' => 'Il token segreto fornito per il token API utilizzato non è corretto',
     'api_user_no_api_permission' => 'Il proprietario del token API utilizzato non ha il permesso di effettuare chiamate API',
-    'api_user_token_expired' => 'The authorization token used has expired',
+    'api_user_token_expired' => 'Il token di autorizzazione utilizzato è scaduto',
 
     // Settings & Maintenance
-    'maintenance_test_email_failure' => 'Error thrown when sending a test email:',
+    'maintenance_test_email_failure' => 'Si è verificato un errore durante l\'invio di una e-mail di prova:',
 
 ];
index 604e02fe2c6b0a47298624b3db025737864a26af..7099d54f37d118cd043776b2cc06c70bd211bd53 100755 (executable)
@@ -8,8 +8,8 @@ return [
 
     'password' => 'La password deve avere almeno sei caratteri e corrispondere alla conferma.',
     'user' => "Non possiamo trovare un utente per quella mail.",
-    'token' => 'The password reset token is invalid for this email address.',
+    'token' => 'Il token per reimpostare la password non è valido per questo indirizzo email.',
     'sent' => 'Ti abbiamo inviato via mail il link per reimpostare la password!',
-    'reset' => 'La tua password è stata resettata!',
+    'reset' => 'La tua password è stata reimpostata!',
 
 ];
index f925de9620d3af06f896b8490fb7099c8ad72aae..64118a0327ba40a8a8cfce8dbb678c4f35fae2c3 100755 (executable)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Homepage Applicazione',
     'app_homepage_desc' => 'Seleziona una pagina da mostrare nella home anzichè quella di default. I permessi della pagina sono ignorati per quella selezionata.',
     'app_homepage_select' => 'Seleziona una pagina',
+    'app_footer_links' => 'Link in basso',
+    'app_footer_links_desc' => 'Aggiungi link da mostrare in basso nel sito. Questi saranno visibili in fondo alla maggior parte delle pagine, incluse quelle che non richiedono un autenticazione. Puoi usare l\'etichetta "trans::<chiave>" per utilizzare le traduzioni implementate nella piattaforma. Esempio: usando "trans::common.privacy_policy" mostrerà il testo tradotto "Norme sulla privacy" e "trans::common.terms_of_service" mostrerà il testo tradotto "Condizioni del Servizio".',
+    'app_footer_links_label' => 'Etichetta del Link',
+    'app_footer_links_url' => 'URL del Link',
+    'app_footer_links_add' => 'Aggiungi Link in basso',
     'app_disable_comments' => 'Disattiva commenti',
     'app_disable_comments_toggle' => 'Disabilita commenti',
     'app_disable_comments_desc' => 'Disabilita i commenti su tutte le pagine nell\'applicazione. I commenti esistenti non sono mostrati. ',
@@ -44,7 +49,7 @@ return [
     // Color settings
     'content_colors' => 'Colori del contenuto',
     'content_colors_desc' => 'Imposta i colori per tutti gli elementi nella gerarchia della pagina. È raccomandato scegliere colori con una luminosità simile a quelli di default per una maggiore leggibilità.',
-    'bookshelf_color' => 'Colore delle libreria',
+    'bookshelf_color' => 'Colore della libreria',
     'book_color' => 'Colore del libro',
     'chapter_color' => 'Colore del capitolo',
     'page_color' => 'Colore della Pagina',
@@ -56,7 +61,7 @@ return [
     'reg_enable_toggle' => 'Abilita registrazione',
     'reg_enable_desc' => 'Quando la registrazione è abilitata, l\utente sarà in grado di registrarsi all\'applicazione. Al momento della registrazione gli verrà associato un ruolo utente predefinito.',
     'reg_default_role' => 'Ruolo predefinito dopo la registrazione',
-    '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_enable_external_warning' => 'L\'opzione precedente viene ignorata se l\'autenticazione esterna tramite LDAP o SAML è attiva. Se l\'autenticazione (effettuata sul sistema esterno) sarà valida, gli account di eventuali membri non registrati saranno creati in automatico.',
     'reg_email_confirmation' => 'Conferma Email',
     'reg_email_confirmation_toggle' => 'Richiedi conferma email',
     'reg_confirm_email_desc' => 'Se la restrizione per dominio è usata la conferma della mail sarà richiesta e la scelta ignorata.',
@@ -68,7 +73,7 @@ return [
     'maint' => 'Manutenzione',
     'maint_image_cleanup' => 'Pulizia Immagini',
     'maint_image_cleanup_desc' => "Esegue la scansione del contenuto delle pagine e delle revisioni per verificare quali immagini e disegni sono attualmente in uso e quali immagini sono ridondanti. Assicurati di creare backup completo del database e delle immagini prima di eseguire la pulizia.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Elimina anche le immagini che esistono solo nelle vecchie revisioni della pagina',
     'maint_image_cleanup_run' => 'Esegui Pulizia',
     'maint_image_cleanup_warning' => ':count immagini potenzialmente inutilizzate sono state trovate. Sei sicuro di voler eliminare queste immagini?',
     'maint_image_cleanup_success' => ':count immagini potenzialmente inutilizzate trovate e eliminate!',
@@ -86,35 +91,35 @@ return [
     // Recycle Bin
     'recycle_bin' => 'Cestino',
     '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_item' => 'Elimina Elemento',
     'recycle_bin_deleted_by' => 'Cancellato da',
     'recycle_bin_deleted_at' => 'Orario Cancellazione',
     'recycle_bin_permanently_delete' => 'Elimina Definitivamente',
     'recycle_bin_restore' => 'Ripristina',
-    'recycle_bin_contents_empty' => 'The recycle bin is currently empty',
+    'recycle_bin_contents_empty' => 'Al momento il cestino è vuoto',
     'recycle_bin_empty' => 'Svuota Cestino',
-    '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_empty_confirm' => 'Questa operazione cancellerà definitivamente tutti gli elementi presenti nel cestino, inclusi i contenuti relativi a ciascun elemento. Sei sicuro di voler svuotare il cestino?',
+    'recycle_bin_destroy_confirm' => 'Questa operazione eliminerà permanentemente questo elemento (insieme a tutti i relativi elementi elencati qui sotto) dal sistema e non sarà più possibile recuperarlo. Sei sicuro di voler eliminare permanentemente questo elemento?',
+    'recycle_bin_destroy_list' => 'Elementi da Eliminare definitivamente',
+    'recycle_bin_restore_list' => 'Elementi da Ripristinare',
     '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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin_restore_deleted_parent' => 'L\'elemento padre di questo elemento è stato eliminato. Questo elemento rimarrà eliminato fino a che l\'elemento padre non sarà ripristinato.',
+    'recycle_bin_destroy_notification' => 'Eliminati :count elementi dal cestino.',
+    'recycle_bin_restore_notification' => 'Ripristinati :count elementi dal cestino.',
 
     // 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' => 'Registro di Controllo',
+    'audit_desc' => 'Questo registro di controllo mostra la lista delle attività registrate dal sistema. Questa lista, a differenza di altre liste del sistema a cui vengono applicate dei filtri, è integrale.',
+    'audit_event_filter' => 'Filtra Eventi',
+    'audit_event_filter_no_filter' => 'Nessun Filtro',
+    'audit_deleted_item' => 'Elimina Elemento',
+    'audit_deleted_item_name' => 'Nome: :name',
     'audit_table_user' => 'Utente',
     'audit_table_event' => 'Evento',
-    'audit_table_related' => 'Related Item or Detail',
-    'audit_table_date' => 'Activity Date',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit_table_related' => 'Elemento o Dettaglio correlato',
+    'audit_table_date' => 'Data attività',
+    'audit_date_from' => 'Dalla data',
+    'audit_date_to' => 'Alla data',
 
     // Role Settings
     'roles' => 'Ruoli',
@@ -138,7 +143,7 @@ return [
     'role_manage_entity_permissions' => 'Gestire tutti i permessi di libri, capitoli e pagine',
     'role_manage_own_entity_permissions' => 'Gestire i permessi sui propri libri, capitoli e pagine',
     'role_manage_page_templates' => 'Gestisci template pagine',
-    'role_access_api' => 'Access system API',
+    'role_access_api' => 'API sistema d\'accesso',
     'role_manage_settings' => 'Gestire impostazioni app',
     'role_asset' => 'Permessi Entità',
     '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.',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'Profilo Utente',
     'users_add_new' => 'Aggiungi Nuovo Utente',
     'users_search' => 'Cerca Utenti',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Ultima Attività',
     'users_details' => 'Dettagli Utente',
     'users_details_desc' => 'Imposta un nome e un indirizzo email per questo utente. L\'indirizzo email verrà utilizzato per accedere all\'applicazione.',
     'users_details_desc_no_email' => 'Imposta un nome per questo utente così gli altri possono riconoscerlo.',
@@ -175,7 +180,7 @@ return [
     'users_delete_named' => 'Elimina l\'utente :userName',
     'users_delete_warning' => 'Questo eliminerà completamente l\'utente \':userName\' dal sistema.',
     'users_delete_confirm' => 'Sei sicuro di voler eliminare questo utente?',
-    'users_migrate_ownership' => 'Migrate Ownership',
+    'users_migrate_ownership' => 'Cambia Proprietario',
     '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' => 'Nessun utente selezionato',
     'users_delete_success' => 'Utente rimosso con successo',
@@ -192,11 +197,11 @@ return [
     'users_social_disconnect' => 'Disconnetti Account',
     'users_social_connected' => 'L\'account :socialAccount è stato connesso correttamente al tuo profilo.',
     'users_social_disconnected' => 'L\'account :socialAccount è stato disconnesso correttamente dal tuo profilo.',
-    'users_api_tokens' => 'API Tokens',
+    'users_api_tokens' => 'Token API',
     'users_api_tokens_none' => 'No API tokens have been created for this user',
     'users_api_tokens_create' => 'Crea Token',
     'users_api_tokens_expires' => 'Scade',
-    'users_api_tokens_docs' => 'API Documentation',
+    'users_api_tokens_docs' => 'Documentazione API',
 
     // API Tokens
     'user_api_token_create' => 'Crea Token API',
@@ -205,17 +210,17 @@ return [
     'user_api_token_expiry' => 'Data di scadenza',
     '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_create_success' => 'API token successfully created',
-    'user_api_token_update_success' => 'API token successfully updated',
+    'user_api_token_create_success' => 'Token API creato correttamente',
+    'user_api_token_update_success' => 'Token API aggiornato correttamente',
     'user_api_token' => 'Token API',
     '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' => 'Token Segreto',
     '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 Aggiornato :timeAgo',
     'user_api_token_updated' => 'Token Aggiornato :timeAgo',
     'user_api_token_delete' => 'Elimina Token',
-    'user_api_token_delete_warning' => 'This will fully delete this API token with the name \':tokenName\' from the system.',
+    'user_api_token_delete_warning' => 'Questa operazione eliminerà irreversibilmente dal sistema il token API denominato \':tokenName\'.',
     'user_api_token_delete_confirm' => 'Sei sicuri di voler eliminare questo token API?',
     'user_api_token_delete_success' => 'Token API eliminato correttamente',
 
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Catalano',
         'cs' => 'Česky',
         'da' => 'Danese',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 2e460cdc382715a74492197fe0d31da98364de5b..69d023688ea0357270b3ba007e8091f53ca47005 100755 (executable)
@@ -78,7 +78,6 @@ return [
         'string'  => 'Il campo :attribute deve essere almeno :min caratteri.',
         'array'   => 'Il campo :attribute deve contenere almeno :min elementi.',
     ],
-    'no_double_extension'  => ':attribute deve avere solo un\'estensione.',
     'not_in'               => 'Il :attribute selezionato non è valido.',
     'not_regex'            => 'Il formato di :attribute non è valido.',
     'numeric'              => ':attribute deve essere un numero.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => 'Il campo :attribute è richiesto quando :values non è presente.',
     'required_without_all' => 'Il campo :attribute è richiesto quando nessuno dei :values sono presenti.',
     'same'                 => ':attribute e :other devono corrispondere.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => 'Il link inserito potrebbe non essere sicuro.',
     'size'                 => [
         'numeric' => 'Il campo :attribute deve essere :size.',
         'file'    => 'Il campo :attribute deve essere :size kilobytes.',
index 7932dc7135bbf9fc5192b8e7df0405a67503d1dd..836b963011a66e4fb44c8880f5b7c7feb7748d30 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Sort Ascending',
     'sort_descending' => 'Sort Descending',
     'sort_name' => 'Name',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Created Date',
     'sort_updated_at' => 'Updated Date',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profile Menu',
     'view_profile' => 'プロフィール表示',
     'edit_profile' => 'プロフィール編集',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Content',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => '":actionText" をクリックできない場合、以下のURLをコピーしブラウザで開いてください:',
     'email_rights' => 'All rights reserved',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index d220d5e38e756915d0fea34a9a64e0820159c05e..8760b25a3f70877be28c6869d0e62a697853372c 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => '権限が設定されている',
     'search_created_by_me' => '自分が作成した',
     'search_updated_by_me' => '自分が更新した',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Date Options',
     'search_updated_before' => '以前に更新',
     'search_updated_after' => '以降に更新',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
     'revision_delete_success' => 'リビジョンを削除しました',
     'revision_cannot_delete_latest' => '最新のリビジョンを削除できません。'
-];
\ No newline at end of file
+];
index 983e07a3a524aed553fb875fa070aed5a466b0d1..4d1776f1296380e980e2eac7346f3c0c8dd15e38 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'ページが見つかりません',
     'sorry_page_not_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' => 'ホームに戻る',
     'error_occurred' => 'エラーが発生しました',
     'app_down' => ':appNameは現在停止しています',
index 31ab22804ebc02b53041461f566850218a3b2efb..91d19a8eb070bd05ec883e707deecb0473e0971b 100644 (file)
@@ -12,18 +12,18 @@ return [
     'settings_save_success' => '設定を保存しました',
 
     // App Settings
-    'app_customization' => 'Customization',
-    'app_features_security' => 'Features & Security',
+    'app_customization' => 'カスタマイズ',
+    'app_features_security' => '機能とセキュリティ',
     'app_name' => 'アプリケーション名',
     'app_name_desc' => 'この名前はヘッダーやEメール内で表示されます。',
     'app_name_header' => 'ヘッダーにアプリケーション名を表示する',
     'app_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_access_desc' => 'このオプションを有効にすると、ログインしていない訪問者があなたのBookStackインスタンスのコンテンツにアクセスできるようになります。',
+    'app_public_access_desc_guest' => '一般の訪問者のアクセスは、「ゲスト」ユーザー権限を通じて制御することができます。',
+    'app_public_access_toggle' => 'パブリックアクセスを許可',
     'app_public_viewing' => 'アプリケーションを公開する',
     'app_secure_images' => '画像アップロード時のセキュリティを強化',
-    'app_secure_images_toggle' => 'Enable higher security image uploads',
+    'app_secure_images_toggle' => 'より高いセキュリティの画像アップロードを可能にする',
     'app_secure_images_desc' => 'パフォーマンスの観点から、全ての画像が公開になっています。このオプションを有効にすると、画像URLの先頭にランダムで推測困難な文字列が追加され、アクセスを困難にします。',
     'app_editor' => 'ページエディタ',
     'app_editor_desc' => 'ここで選択されたエディタを全ユーザが使用します。',
@@ -37,13 +37,18 @@ return [
     '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' => 'ページを選択',
+    'app_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::<key>" 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' => '表示するテキスト',
+    'app_footer_links_url' => 'リンク先の URL',
+    'app_footer_links_add' => 'Add Footer Link',
     'app_disable_comments' => 'コメントを無効にする',
     'app_disable_comments_toggle' => 'コメントを無効にする',
     'app_disable_comments_desc' => 'アプリケーション内のすべてのページのコメントを無効にします。既存のコメントは表示されません。',
 
     // Color settings
     'content_colors' => 'コンテンツの色',
-    'content_colors_desc' => 'Sets 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' => 'Shelf Color',
     'book_color' => 'Book Color',
     'chapter_color' => 'Chapter Color',
@@ -52,13 +57,13 @@ return [
 
     // Registration Settings
     'reg_settings' => '登録設定',
-    'reg_enable' => 'Enable Registration',
-    'reg_enable_toggle' => 'Enable registration',
+    'reg_enable' => '登録を有効にする',
+    'reg_enable_toggle' => '登録を有効にする',
     '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' => '新規登録時のデフォルト役割',
-    '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_enable_external_warning' => '外部のLDAPまたはSAML認証が有効の場合、上記のオプションは無視されます。存在しないメンバーのユーザーアカウントは、使用している外部システムでの認証に成功した場合に自動的に作成されます。',
+    'reg_email_confirmation' => '確認メール',
+    'reg_email_confirmation_toggle' => 'メールによる確認を行う',
     'reg_confirm_email_desc' => 'ドメイン制限を有効にしている場合はEメール認証が必須となり、この項目は無視されます。',
     'reg_confirm_restrict_domain' => 'ドメイン制限',
     'reg_confirm_restrict_domain_desc' => '特定のドメインのみ登録できるようにする場合、以下にカンマ区切りで入力します。設定された場合、Eメール認証が必須になります。<br>登録後、ユーザは自由にEメールアドレスを変更できます。',
@@ -67,10 +72,10 @@ return [
     // Maintenance settings
     'maint' => 'メンテナンス',
     '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_desc' => "ページや履歴の内容をスキャンして、どの画像や図面が現在使用されているか、どの画像が余っているかをチェックします。この機能を実行する前に、データベースと画像の完全なバックアップを作成してください。",
+    'maint_delete_images_only_in_revisions' => 'また、古いページのリビジョンにしか存在しない画像も削除します。',
     'maint_image_cleanup_run' => 'クリーンアップを実行',
-    'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
+    'maint_image_cleanup_warning' => ':count 個、使用されていない可能性のある画像が見つかりました。これらの画像を削除してもよろしいですか?',
     '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' => 'テストメールを送信',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 61057f17ee00d6e3aedfddaf453af17d99f0f5a3..7d9987ce073e22a0137fdef9a216cf9f4cc84d5c 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attributeは:min文字以上である必要があります。',
         'array'   => ':attributeは:min個以上である必要があります。',
     ],
-    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => '選択された:attributeは不正です。',
     'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attributeは数値である必要があります。',
index fda7e4ef35ebb21aeca7c763841254356f5c75c5..2762c7eb4dc022fdc6e70978a97f78f42d49c5fc 100644 (file)
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => '댓글 쓰기',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => '업데이트된 권한',
 ];
index 43156260224a02beaf79a5c12bf81df0540189b1..3c40f12e257bf4d9c89f3a82f7f6796d5c5cfde6 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => '오름차 순서',
     'sort_descending' => '내림차 순서',
     'sort_name' => '제목',
+    'sort_default' => 'Default',
     'sort_created_at' => '만든 날짜',
     'sort_updated_at' => '수정한 날짜',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => '탐색 경로',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => '프로필',
     'view_profile' => '프로필 보기',
     'edit_profile' => '프로필 바꾸기',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => '정보',
+    'tab_info_label' => '탭: 보조 정보 표시',
     'tab_content' => '내용',
+    'tab_content_label' => '탭: 주요 내용 표시',
 
     // Email Content
     'email_action_help' => ':actionText를 클릭할 수 없을 때는 웹 브라우저에서 다음 링크로 접속할 수 있습니다.',
     'email_rights' => '모든 권리 소유',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => '개인정보처리방침',
+    'terms_of_service' => '이용약관',
 ];
index 71815cdd7c3ab544738f8a33fb904eadf2a8786a..94a62c67a7926567070db9f904a51a61b71847c4 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => '권한 설정함',
     'search_created_by_me' => '내가 만듦',
     'search_updated_by_me' => '내가 수정함',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => '날짜',
     'search_updated_before' => '이전에 수정함',
     'search_updated_after' => '이후에 수정함',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => '챕터 만들기',
     'chapters_delete' => '챕터 삭제하기',
     'chapters_delete_named' => ':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_explain' => '\':chapterName\'(을)를 지웁니다. 해당 챕터에 있는 모든 문서도 삭제됩니다.',
     'chapters_delete_confirm' => '이 챕터를 지울 건가요?',
     'chapters_edit' => '챕터 바꾸기',
     'chapters_edit_named' => ':chapterName 바꾸기',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => '문서 수정본',
     'pages_revisions_named' => ':pageName 수정본',
     'pages_revision_named' => ':pageName 수정본',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => '#:id 에서; :summary 복원',
     'pages_revisions_created_by' => '만든 사용자',
     'pages_revisions_date' => '수정한 날짜',
     'pages_revisions_number' => 'No.',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => '이 수정본을 되돌릴 건가요? 현재 판본을 바꿉니다.',
     'revision_delete_success' => '수정본 지움',
     'revision_cannot_delete_latest' => '현재 판본은 지울 수 없습니다.'
-];
\ No newline at end of file
+];
index 093288c83a7f59f52c3db9dceec1475be5320bc4..b2a2c7a3a130214d2214e2797ead06d8abeec28a 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => '404 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.',
     'return_home' => '처음으로 돌아가기',
     'error_occurred' => '문제가 생겼습니다.',
     'app_down' => ':appName에 문제가 있는 것 같습니다',
index 9fec26956757fbb45c6d8ddd94545c5a25c38bfd..923d9e12646d3f601e193f6bb0798e5a5bce34ac 100755 (executable)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => '처음 페이지',
     'app_homepage_desc' => '고른 페이지에 설정한 권한은 무시합니다.',
     'app_homepage_select' => '문서 고르기',
+    'app_footer_links' => '푸터 링크',
+    'app_footer_links_desc' => '사이트 푸터에 표시할 링크들을 추가합니다. 로그인이 필요하지 않은 페이지들을 포함하여 대부분의 페이지 하단에 표시됩니다. 시스템 정의 번역을 사용하기 위해 "trans::<key>"를 사용할 수 있습니다. 예를 들어: "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' => '모든 페이지에서 댓글을 숨깁니다.',
@@ -68,7 +73,7 @@ return [
     'maint' => '데이터',
     'maint_image_cleanup' => '이미지 정리',
     'maint_image_cleanup_desc' => "중복한 이미지를 찾습니다. 실행하기 전에 이미지를 백업하세요.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => '오래된 문서 수정본에만 있는 이미지도 삭제하기',
     'maint_image_cleanup_run' => '실행',
     'maint_image_cleanup_warning' => '이미지 :count개를 지울 건가요?',
     'maint_image_cleanup_success' => '이미지 :count개 삭제함',
@@ -80,38 +85,38 @@ return [
     'maint_send_test_email_mail_subject' => '테스트 메일',
     'maint_send_test_email_mail_greeting' => '이메일 전송이 성공하였습니다.',
     'maint_send_test_email_mail_text' => '축하합니다! 이 메일을 받음으로 이메일 설정이 정상적으로 되었음을 확인하였습니다.',
-    '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_recycle_bin_desc' => '삭제된 서가, 책자, 챕터 & 문서들을 휴지통으로 보내져 복구하거나 또는 영구적으로 삭제할 수 있습니다. 휴지통의 오래된 항목은 시스템 구성에 따라 잠시 후 자동으로 삭제될 수 있습니다.',
+    'maint_recycle_bin_open' => '휴지통 열기',
 
     // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin' => '휴지통',
+    'recycle_bin_desc' => '여기서 삭제된 항목을 복원하거나 시스템에서 영구적으로 제거하도록 선택할 수 있습니다. 이 목록은 권한 필터가 적용되는 시스템의 유사한 활동 목록과 달리 필터링되지 않습니다.',
+    'recycle_bin_deleted_item' => '삭제된 항목',
+    'recycle_bin_deleted_by' => '삭제자',
+    'recycle_bin_deleted_at' => '삭제 시간',
+    'recycle_bin_permanently_delete' => '영구적으로 삭제하기',
+    'recycle_bin_restore' => '복원하기',
+    'recycle_bin_contents_empty' => '휴지통은 현재 비어있습니다.',
+    'recycle_bin_empty' => '휴지통 비우기',
+    'recycle_bin_empty_confirm' => '각 항목에 포함된 내용을 포함하여 휴지통의 모든 항목이 영구히 삭제됩니다. 휴지통을 비우시겠습니까?',
+    'recycle_bin_destroy_confirm' => '이 작업을 수행하면 아래 나열된 하위 요소와 함께 이 항목이 시스템에서 영구적으로 삭제되고 이 내용을 복원할 수 없습니다. 이 항목을 완전히 삭제하시겠습니까?',
+    'recycle_bin_destroy_list' => '삭제할 항목들',
+    'recycle_bin_restore_list' => '복원할 항목들',
+    'recycle_bin_restore_confirm' => '이 작업을 수행하면 하위 요소를 포함하여 삭제된 항목이 원래 위치로 복원됩니다. 원래 위치가 삭제되고 현재 휴지통에 있는 경우 상위 항목도 복원해야 합니다.',
+    'recycle_bin_restore_deleted_parent' => '이 항목의 상위 항목도 삭제되었습니다. 상위 항목도 복원될 때까지 삭제된 상태로 유지됩니다.',
+    'recycle_bin_destroy_notification' => '휴지통에서 총 :count 개의 항목들이 삭제되었습니다.',
+    'recycle_bin_restore_notification' => '휴지통에서 총 :count 개의 항목들이 복원되었습니다.',
 
     // Audit Log
     'audit' => '감사 기록',
-    '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_desc' => '이 감사 로그는 시스템에서 추적한 활동 목록을 표시합니다. 이 목록은 권한 필터가 적용되는 시스템의 유사한 활동 목록과 달리 필터링되지 않습니다.',
     'audit_event_filter' => '이벤트 필터',
     'audit_event_filter_no_filter' => '필터 없음',
     'audit_deleted_item' => '삭제된 항목',
     'audit_deleted_item_name' => '이름: :name',
     'audit_table_user' => '사용자',
     'audit_table_event' => '이벤트',
-    'audit_table_related' => 'Related Item or Detail',
+    'audit_table_related' => '관련 항목 또는 세부 정보',
     'audit_table_date' => '활동 날짜',
     'audit_date_from' => '날짜 범위 시작',
     'audit_date_to' => '날짜 범위 끝',
@@ -141,7 +146,7 @@ return [
     'role_access_api' => '시스템 접근 API',
     'role_manage_settings' => '사이트 설정 관리',
     'role_asset' => '권한 항목',
-    '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.',
+    'roles_system_warning' => '위의 세 가지 권한 중 하나에 액세스하면 사용자가 자신의 권한이나 시스템 내 다른 사용자의 권한을 변경할 수 있습니다. 이러한 권한이 있는 역할만 신뢰할 수 있는 사용자에게 할당합니다.',
     'role_asset_desc' => '책자, 챕터, 문서별 권한은 이 설정에 우선합니다.',
     'role_asset_admins' => 'Admin 권한은 어디든 접근할 수 있지만 이 설정은 사용자 인터페이스에서 해당 활동을 표시할지 결정합니다.',
     'role_all' => '모든 항목',
@@ -157,7 +162,7 @@ return [
     'user_profile' => '사용자 프로필',
     'users_add_new' => '사용자 만들기',
     'users_search' => '사용자 검색',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => '최근 활동',
     'users_details' => '사용자 정보',
     'users_details_desc' => '메일 주소로 로그인합니다.',
     'users_details_desc_no_email' => '사용자 이름을 바꿉니다.',
@@ -175,10 +180,10 @@ return [
     'users_delete_named' => ':userName 삭제',
     'users_delete_warning' => ':userName에 관한 데이터를 지웁니다.',
     'users_delete_confirm' => '이 사용자를 지울 건가요?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => '소유권 이전',
+    'users_migrate_ownership_desc' => '다른 사용자가 현재 이 사용자가 소유하고 있는 모든 항목의 소유자가 되려면 여기서 사용자를 선택하십시오.',
+    'users_none_selected' => '선택된 사용자가 없습니다.',
+    'users_delete_success' => '사용자가 성공적으로 삭제되었습니다.',
     'users_edit' => '사용자 수정',
     'users_edit_profile' => '프로필 바꾸기',
     'users_edit_success' => '프로필 바꿈',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => '히브리어',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 64c4a4345b256d7d1c31f4dc9ea7466a3fc6e647..78b17a1d23adc97d9e54f432c7edc3d4238510ea 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute(을)를 적어도 :value바이트로 구성하세요.',
         'array'   => ':attribute(을)를 적어도 :value개로 구성하세요..',
     ],
-    'no_double_extension'  => ':attribute(이)가 단일한 확장자를 가져야 합니다.',
     'not_in'               => '고른 :attribute(이)가 유효하지 않습니다.',
     'not_regex'            => ':attribute(은)는 유효하지 않은 형식입니다.',
     'numeric'              => ':attribute(을)를 숫자로만 구성하세요.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => ':values(이)가 없을 때 :attribute(을)를 구성해야 합니다.',
     'required_without_all' => ':values(이)가 모두 없을 때 :attribute(을)를 구성해야 합니다.',
     'same'                 => ':attribute(와)과 :other(을)를 똑같이 구성하세요.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => '제공된 링크가 안전하지 않을 수 있습니다.',
     'size'                 => [
         'numeric' => ':attribute(을)를 :size(으)로 구성하세요.',
         'file'    => ':attribute(을)를 :size킬로바이트로 구성하세요.',
diff --git a/resources/lang/lv/activities.php b/resources/lang/lv/activities.php
new file mode 100644 (file)
index 0000000..8f99e0f
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
+return [
+
+    // Pages
+    'page_create'                 => 'izveidoja lapu',
+    'page_create_notification'    => 'Lapa Veiksmīgi Izveidota',
+    'page_update'                 => 'atjaunoja lapu',
+    'page_update_notification'    => 'Lapa Veiksmīgi Atjaunota',
+    'page_delete'                 => 'izdzēsa lapu',
+    'page_delete_notification'    => 'Lapa Veiksmīgi Dzēsta',
+    'page_restore'                => 'atjaunoja lapu',
+    'page_restore_notification'   => 'Lapa Veiksmīgi Atjaunota',
+    'page_move'                   => 'pārvietoja lapu',
+
+    // Chapters
+    'chapter_create'              => 'izveidoja nodaļu',
+    'chapter_create_notification' => 'Nodaļa Veiksmīgi Izveidota',
+    'chapter_update'              => 'atjaunoja nodaļu',
+    'chapter_update_notification' => 'Nodaļa Veiksmīgi Atjaunota',
+    'chapter_delete'              => 'izdzēsa nodaļu',
+    'chapter_delete_notification' => 'Nodaļa Veiksmīgi Dzēsta',
+    'chapter_move'                => 'pārvietoja nodaļu',
+
+    // Books
+    'book_create'                 => 'izveidoja grāmatu',
+    'book_create_notification'    => 'Grāmata Veiksmīgi Izveidota',
+    'book_update'                 => 'atjaunoja grāmatu',
+    'book_update_notification'    => 'Grāmata Veiksmīgi Atjaunota',
+    'book_delete'                 => 'izdzēsa grāmatu',
+    'book_delete_notification'    => 'Grāmata Veiksmīgi Dzēsta',
+    'book_sort'                   => 'kārtoja grāmatu',
+    'book_sort_notification'      => 'Grāmata Veiksmīgi Pārkārtota',
+
+    // Bookshelves
+    'bookshelf_create'            => 'izveidoja Plauktu',
+    'bookshelf_create_notification'    => 'Plaukts Veiksmīgi Izveidots',
+    'bookshelf_update'                 => 'atjaunoja plauktu',
+    'bookshelf_update_notification'    => 'Plaukts Veiksmīgi Atjaunots',
+    'bookshelf_delete'                 => 'izdzēsa plauktu',
+    'bookshelf_delete_notification'    => 'Plaukts Veiksmīgi Dzēsts',
+
+    // Other
+    'commented_on'                => 'komentēts',
+    'permissions_update'          => 'atjaunoja atļaujas',
+];
diff --git a/resources/lang/lv/auth.php b/resources/lang/lv/auth.php
new file mode 100644 (file)
index 0000000..dc84a2d
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
+return [
+
+    'failed' => 'Šie reģistrācijas dati neatbilst mūsu ierakstiem.',
+    'throttle' => 'Pārāk daudz pieteikšanās mēģinājumu. Lūdzu, mēģiniet vēlreiz pēc :seconds seconds.',
+
+    // Login & Register
+    'sign_up' => 'Reģistrēties',
+    'log_in' => 'Ielogoties',
+    'log_in_with' => 'Ielogoties ar :socialDriver',
+    'sign_up_with' => 'Pieteikties ar :socialDriver',
+    'logout' => 'Iziet',
+
+    'name' => 'Vārds',
+    'username' => 'Lietotājvārds',
+    'email' => 'E-pasts',
+    'password' => 'Parole',
+    'password_confirm' => 'Apstiprināt paroli',
+    'password_hint' => 'Jābūt vismaz 8 rakstzīmēm',
+    'forgot_password' => 'Aizmirsta parole?',
+    'remember_me' => 'Atcerēties mani',
+    'ldap_email_hint' => 'Lūdzu ievadiet e-pastu, kuru izmantosiet šim profilam.',
+    'create_account' => 'Izveidot profilu',
+    'already_have_account' => 'Jau ir profils?',
+    'dont_have_account' => 'Nav profila?',
+    'social_login' => 'Pieteikšanās ar sociālo tīklu profilu',
+    'social_registration' => 'Reģistrēšanās ar sociālo profilu',
+    'social_registration_text' => 'Reģistrēties vai pieteikties izmantojot citu servisu.',
+
+    'register_thanks' => 'Paldies par reģistrāciju!',
+    'register_confirm' => 'Lūdzu, pārbaudiet savu e-pastu un nospiediet apstiprināšanas pogu, lai piekļūtu :appName.',
+    'registrations_disabled' => 'Reģistrācija ir izslēgta',
+    'registration_email_domain_invalid' => 'E-pasta domēnam nav piekļuves pie šīs aplikācijas',
+    'register_success' => 'Paldies par reģistrēšanos! Tagad varat pieslēgties.',
+
+
+    // Password Reset
+    'reset_password' => 'Atiestatīt paroli',
+    'reset_password_send_instructions' => 'Ievadiet savu e-pastu zemāk un nosūtīsim e-pastu ar paroles atiestatīšanas saiti.',
+    'reset_password_send_button' => 'Nosūtīt atiestatīšanas saiti',
+    'reset_password_sent' => 'Paroles atiestatīšanas saite tiks nosūtīta uz :email, ja šāds e-pasts būs derīgs.',
+    'reset_password_success' => 'Jūsu parole ir veiksmīgi atiestatīta.',
+    'email_reset_subject' => 'Atiestatīt :appName paroli',
+    'email_reset_text' => 'Jūs saņemat šo e-pastu, jo mēs saņēmām Jūsu profila paroles atiestatīšanas pieprasījumu.',
+    'email_reset_not_requested' => 'Ja Jūs nepieprasījāt paroles atiestatīšanu, tad tālākas darbības nav nepieciešamas.',
+
+
+    // Email Confirmation
+    'email_confirm_subject' => 'Apstiprinat savu :appName e-pastu',
+    'email_confirm_greeting' => 'Paldies, ka pievienojāties :appName!',
+    'email_confirm_text' => 'Lūdzu apstipriniet savu e-pastu nospiežot zemāk redzamo pogu:',
+    'email_confirm_action' => 'Apstiprināt e-pastu',
+    'email_confirm_send_error' => 'E-pasta apriprināšana ir nepieciešama, bet sistēma nevarēja e-pastu nosūtīt. Lūdzu sazinaties ar administratoru, lai pārliecinātos, ka e-pasts ir iestatīts pareizi.',
+    'email_confirm_success' => 'Jūsu e-pasts ir apstiprināts!',
+    'email_confirm_resent' => 'Apstiprinājuma vēstule tika nosūtīta. Lūdzu, pārbaudiet jūsu e-pastu.',
+
+    'email_not_confirmed' => 'E-pasts nav apstiprināts',
+    'email_not_confirmed_text' => 'Jūsu e-pasta adrese vēl nav apstiprināta.',
+    'email_not_confirmed_click_link' => 'Lūdzu, noklikšķiniet uz saiti nosūtītajā e-pastā pēc reģistrēšanās.',
+    'email_not_confirmed_resend' => 'Ja neredzi e-pastu, tad vari atkārtoti nosūtīt apstiprinājuma e-pastu iesniedzot zemāk redzamo formu.',
+    'email_not_confirmed_resend_button' => 'Atkārtoti nosūtīt apstiprinājuma e-pastu',
+
+    // User Invite
+    'user_invite_email_subject' => 'Tu esi uzaicināts pievienoties :appName!',
+    'user_invite_email_greeting' => 'Jūsu :appName profils ir izveidots.',
+    'user_invite_email_text' => 'Lūdzu, nospiediet zemāk redzamo pogu, lai izveidotu paroli un iegūtu piekļuvi:',
+    'user_invite_email_action' => 'Iestatīt profila paroli',
+    'user_invite_page_welcome' => 'Sveicināti :appName!',
+    'user_invite_page_text' => 'Lai pabeigtu profila izveidi un piekļūtu :appName ir jāizveido parole.',
+    'user_invite_page_confirm_button' => 'Apstiprināt paroli',
+    'user_invite_success' => 'Parole iestatīta, tagad varat piekļūt :appName!'
+];
\ No newline at end of file
diff --git a/resources/lang/lv/common.php b/resources/lang/lv/common.php
new file mode 100644 (file)
index 0000000..0066431
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
+return [
+
+    // Buttons
+    'cancel' => 'Atcelt',
+    'confirm' => 'Apstiprināt',
+    'back' => 'Atpakaļ',
+    'save' => 'Saglabāt',
+    'continue' => 'Turpināt',
+    'select' => 'Atlasīt',
+    'toggle_all' => 'Iezīmēt visus',
+    'more' => 'Vairāk',
+
+    // Form Labels
+    'name' => 'Nosaukums',
+    'description' => 'Apraksts',
+    'role' => 'Loma',
+    'cover_image' => 'Vāka attēls',
+    'cover_image_description' => 'Šim attēlam būtu jābūt aptuveni 440x250px.',
+    
+    // Actions
+    'actions' => 'Darbības',
+    'view' => 'Skatīt',
+    'view_all' => 'Skatīt visus',
+    'create' => 'Izveidot',
+    'update' => 'Atjaunināt',
+    'edit' => 'Rediģēt',
+    'sort' => 'Kārtot',
+    'move' => 'Pārvietot',
+    'copy' => 'Kopēt',
+    'reply' => 'Atbildēt',
+    'delete' => 'Dzēst',
+    'delete_confirm' => 'Apstipriniet dzēšanu',
+    'search' => 'Meklēt',
+    'search_clear' => 'Notīrīt meklēšanu',
+    'reset' => 'Atiestatīt',
+    'remove' => 'Noņemt',
+    'add' => 'Pievienot',
+    'fullscreen' => 'Pilnekrāns',
+
+    // Sort Options
+    'sort_options' => 'Kārtošanas Opcijas',
+    'sort_direction_toggle' => 'Pārslēgt kārtošanas virzienu',
+    'sort_ascending' => 'Kārtot Augoši',
+    'sort_descending' => 'Kārtot Dilstoši',
+    'sort_name' => 'Vārds',
+    'sort_default' => 'Noklusējums',
+    'sort_created_at' => 'Izveidošanas Datums',
+    'sort_updated_at' => 'Atjaunināšanas datums',
+
+    // Misc
+    'deleted_user' => 'Dzēsts lietotājs',
+    'no_activity' => 'Nav skatāmu darbību',
+    'no_items' => 'Vienumi nav pieejami',
+    'back_to_top' => 'Uz augšu',
+    'toggle_details' => 'Rādīt aprakstu',
+    'toggle_thumbnails' => 'Iezīmēt sīkatēlus',
+    'details' => 'Sīkāka informācija',
+    'grid_view' => 'Režģa Skats',
+    'list_view' => 'Saraksta Skats',
+    'default' => 'Noklusējums',
+    'breadcrumb' => 'Navigācija',
+
+    // Header
+    'header_menu_expand' => 'Izvērst galvenes izvēlni',
+    'profile_menu' => 'Profila izvēlne',
+    'view_profile' => 'Apskatīt profilu',
+    'edit_profile' => 'Rediģēt profilu',
+    'dark_mode' => 'Tumšais režīms',
+    'light_mode' => 'Gaišais režīms',
+
+    // Layout tabs
+    'tab_info' => 'Informācija',
+    'tab_info_label' => 'Tab: Rādīt sekundāro informāciju',
+    'tab_content' => 'Saturs',
+    'tab_content_label' => 'Tab: Rādīt galveno saturu',
+
+    // Email Content
+    'email_action_help' => 'Ja ir problēmas noklikšķināt ":actionText" pogu, nokopē un ievieto saiti savā interneta pārlūkā:',
+    'email_rights' => 'Visas tiesības aizsargātas',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privātuma politika',
+    'terms_of_service' => 'Pakalpojuma noteikumi',
+];
diff --git a/resources/lang/lv/components.php b/resources/lang/lv/components.php
new file mode 100644 (file)
index 0000000..b66461d
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
+return [
+
+    // Image Manager
+    'image_select' => 'Attēla izvēle',
+    '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_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',
+    '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_upload_remove' => 'Noņemt',
+
+    // Code Editor
+    'code_editor' => 'Rediģēt kodu',
+    'code_language' => 'Koda valoda',
+    'code_content' => 'Koda teksts',
+    'code_session_history' => 'Sesijas vēsture',
+    'code_save' => 'Saglabāt kodu',
+];
diff --git a/resources/lang/lv/entities.php b/resources/lang/lv/entities.php
new file mode 100644 (file)
index 0000000..2639e22
--- /dev/null
@@ -0,0 +1,320 @@
+<?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
+return [
+
+    // Shared
+    'recently_created' => 'Nesen izveidots',
+    'recently_created_pages' => 'Nesen izveidotās lapas',
+    'recently_updated_pages' => 'Nesen atjauninātās lapas',
+    'recently_created_chapters' => 'Nesen izveidotās nodaļas',
+    'recently_created_books' => 'Nesen izveidotās grāmatas',
+    'recently_created_shelves' => 'Nesen izveidotie plaukti',
+    'recently_update' => 'Nesen atjaunināts',
+    'recently_viewed' => 'Nesen skatītie',
+    'recent_activity' => 'Pēdējās aktivitātes',
+    'create_now' => 'Izveidot tagad',
+    'revisions' => 'Revīzijas',
+    'meta_revision' => 'Revīzija #:revisionCount',
+    'meta_created' => 'Izveidots :timeLength',
+    'meta_created_name' => ':user izveidojis pirms :timeLength',
+    'meta_updated' => 'Atjaunināts :timeLength',
+    'meta_updated_name' => ':user atjauninājis pirms :timeLength',
+    'meta_owned_name' => 'Īpašnieks :user',
+    'entity_select' => 'Izvēlēties vienumu',
+    'images' => 'Attēli',
+    'my_recent_drafts' => 'Mani melnraksti',
+    'my_recently_viewed' => 'Mani nesen skatītie',
+    'no_pages_viewed' => 'Neviena lapa vēl nav skatīta',
+    'no_pages_recently_created' => 'Nav radīta neviena lapa',
+    'no_pages_recently_updated' => 'Nav atjaunināta neviena lapa',
+    'export' => 'Eksportēt',
+    'export_html' => 'Pilna satura web fails',
+    'export_pdf' => 'PDF fails',
+    'export_text' => 'Vienkāršs teksta fails',
+
+    // Permissions and restrictions
+    'permissions' => 'Atļaujas',
+    'permissions_intro' => 'Kolīdz ieslēgtas, šīs atļaujas ņems prioritāti pār jebkurām citām uzstādītajām atļaujām.',
+    'permissions_enable' => 'Ieslēgt pielāgotās atļaujas',
+    'permissions_save' => 'Saglabāt atļaujas',
+    'permissions_owner' => 'Īpašnieks',
+
+    // Search
+    'search_results' => 'Meklēšanas rezultāti',
+    'search_total_results_found' => ':count meklēšanas rezultāts|:count meklēšanas rezultāti',
+    'search_clear' => 'Notīrīt meklēšanu',
+    'search_no_pages' => 'Neviena lapa neatbilst meklēšanai',
+    'search_for_term' => 'Meklēt :term',
+    'search_more' => 'Vairāk rezultāti',
+    'search_advanced' => 'Paplašināta meklēšana',
+    'search_terms' => 'Meklēšanas parametri',
+    'search_content_type' => 'Satura tips',
+    'search_exact_matches' => 'Precīza atbilstība',
+    'search_tags' => 'Birku meklēšana',
+    'search_options' => 'Iestatījumi',
+    'search_viewed_by_me' => 'Manis apskatītie',
+    'search_not_viewed_by_me' => 'Neesmu skatījis',
+    'search_permissions_set' => 'Iestatītās atļaujas',
+    'search_created_by_me' => 'Manis izveidotie',
+    'search_updated_by_me' => 'Manis atjauninātie',
+    'search_owned_by_me' => 'Es esmu īpašnieks',
+    'search_date_options' => 'Datuma iestatījumi',
+    'search_updated_before' => 'Atjaunināts pirms',
+    'search_updated_after' => 'Atjaunināts pēc',
+    'search_created_before' => 'Izveidots pirms',
+    'search_created_after' => 'Izveidots pēc',
+    'search_set_date' => 'Norādīt datumu',
+    'search_update' => 'Atjaunināt meklētāju',
+
+    // Shelves
+    'shelf' => 'Plaukts',
+    'shelves' => 'Plaukti',
+    'x_shelves' => ':count Plaukts|:count Plaukti',
+    'shelves_long' => 'Grāmatu plautki',
+    'shelves_empty' => 'Neviens plaukts nav izveidots',
+    'shelves_create' => 'Izveidot jaunu plauktu',
+    'shelves_popular' => 'Populāri plaukti',
+    'shelves_new' => 'Jauni plaukti',
+    'shelves_new_action' => 'Jauns plaukts',
+    'shelves_popular_empty' => 'Populārākie plaukti tiks rādīti šeit.',
+    'shelves_new_empty' => 'Pēdējie izveidotie plaukti tiks rādīti šeit.',
+    'shelves_save' => 'Saglabāt plauktu',
+    'shelves_books' => 'Grāmatas šajā plauktā',
+    'shelves_add_books' => 'Pievienot grāmatas šim plauktam',
+    'shelves_drag_books' => 'Ievelciet grāmatas šeit, lai novietotu tās šajā plauktā',
+    'shelves_empty_contents' => 'Šim gŗamatplauktam nav pievienotu grāmatu',
+    'shelves_edit_and_assign' => 'Labot plauktu, lai tam pievienotu grāmatas',
+    'shelves_edit_named' => 'Labot grāmatplauktu :name',
+    'shelves_edit' => 'Labot grāmatplauktu',
+    'shelves_delete' => 'Dzēst grāmatplauktu',
+    'shelves_delete_named' => 'Dzēst grāmatplauktu :name',
+    'shelves_delete_explain' => "Tiks dzēsts grāmatplaukts ar nosaukumu \":name\". Tajā ievietotās grāmatas netiks dzēstas.",
+    'shelves_delete_confirmation' => 'Vai esat pārliecināts, ka vēlaties dzēst šo grāmatplauktu?',
+    'shelves_permissions' => 'Grāmatplaukta atļaujas',
+    'shelves_permissions_updated' => 'Grāmatplaukta atļaujas atjauninātas',
+    'shelves_permissions_active' => 'Grāmatplaukta atļaujas ir aktīvas',
+    'shelves_copy_permissions_to_books' => 'Kopēt grāmatplaukta atļaujas uz grāmatām',
+    'shelves_copy_permissions' => 'Kopēt atļaujas',
+    'shelves_copy_permissions_explain' => 'Šis piemēros pašreizējās grāmatplaukta piekļuves tiesības visām tajā esošajām grāmatām. Pirms ieslēgšanas pārliecinieties, ka ir saglabātas izmaiņas grāmatplaukta piekļuves tiesībām.',
+    'shelves_copy_permission_success' => 'Grāmatplaukta atļaujas ir pārkopētas uz :count grāmatām',
+
+    // Books
+    'book' => 'Grāmata',
+    'books' => 'Grāmatas',
+    'x_books' => ':count grāmata|:count grāmatas',
+    'books_empty' => 'Neviena grāmata nav izveidota',
+    'books_popular' => 'Populārās grāmatas',
+    'books_recent' => 'Nesenās grāmatas',
+    'books_new' => 'Jaunas grāmatas',
+    'books_new_action' => 'Jauna grāmata',
+    'books_popular_empty' => 'Populārākās grāmatas tiks rādītas šeit.',
+    'books_new_empty' => 'Pēdējās izveidotās grāmatas tiks rādītas šeit.',
+    'books_create' => 'Izveidot jaunu grāmatu',
+    'books_delete' => 'Dzēst grāmatu',
+    'books_delete_named' => 'Dzēst grāmatu :bookName',
+    'books_delete_explain' => 'Šī darbība izdzēsīs grāmatu \':bookName\'. Visas lapas un nodaļas tiks izdzēstas.',
+    'books_delete_confirmation' => 'Vai esat pārliecināts, ka vēlaties dzēst šo grāmatu?',
+    'books_edit' => 'Labot grāmatu',
+    'books_edit_named' => 'Labot grāmatu :bookName',
+    'books_form_book_name' => 'Grāmatas nosaukums',
+    'books_save' => 'Saglabāt grāmatu',
+    '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.',
+    'books_empty_create_page' => 'Izveidot jaunu lapu',
+    'books_empty_sort_current_book' => 'Kārtot šo grāmatu',
+    'books_empty_add_chapter' => 'Pievienot nodaļu',
+    'books_permissions_active' => 'Grāmatas atļaujas ir aktīvas',
+    'books_search_this' => 'Meklēt šajā grāmatā',
+    'books_navigation' => 'Grāmatas navigācija',
+    'books_sort' => 'Kārtot grāmatas saturu',
+    'books_sort_named' => 'Kārtot grāmatu :bookName',
+    'books_sort_name' => 'Kārtot pēc nosaukuma',
+    'books_sort_created' => 'Kārtot pēc izveidošanas datuma',
+    'books_sort_updated' => 'Kārtot pēc atjaunināšanas datuma',
+    'books_sort_chapters_first' => 'Nodaļas pirmās',
+    'books_sort_chapters_last' => 'Nodaļas pēdējās',
+    'books_sort_show_other' => 'Rādīt citas grāmatas',
+    'books_sort_save' => 'Saglabāt jauno kārtību',
+
+    // Chapters
+    'chapter' => 'Nodaļa',
+    'chapters' => 'Nodaļas',
+    'x_chapters' => ':count nodaļa|:count nodaļas',
+    'chapters_popular' => 'Populāras nodaļas',
+    'chapters_new' => 'Jauna nodaļa',
+    'chapters_create' => 'Izveidot jaunu nodaļu',
+    'chapters_delete' => 'Dzēst nodaļu',
+    'chapters_delete_named' => 'Dzēst nodaļu :chapterName',
+    'chapters_delete_explain' => 'Šī darbība dzēsīs nodaļu \':chapterName\'. Visas tajā esošās lapas arī tiks dzēstas.',
+    'chapters_delete_confirm' => 'Vai esat pārliecināts, ka vēlaties dzēst šo nodaļu?',
+    'chapters_edit' => 'Labot nodaļu',
+    'chapters_edit_named' => 'Labot nodaļu :chapterName',
+    'chapters_save' => 'Saglabāt nodaļu',
+    'chapters_move' => 'Pārvietot nodaļu',
+    'chapters_move_named' => 'Pārvietot nodaļu :chapterName',
+    'chapter_move_success' => 'Nodaļa pārviedota uz :bookName',
+    'chapters_permissions' => 'Nodaļas atļaujas',
+    'chapters_empty' => 'Šajā nodaļā nav pievienotu lapu.',
+    'chapters_permissions_active' => 'Nodaļas atļaujas ir aktīvas',
+    'chapters_permissions_success' => 'Nodaļas atļaujas ir atjauninātas',
+    'chapters_search_this' => 'Meklēt šajā nodaļā',
+
+    // Pages
+    'page' => 'Lapa',
+    'pages' => 'Lapas',
+    'x_pages' => ':count lapa|:count lapas',
+    'pages_popular' => 'Populātas lapas',
+    'pages_new' => 'Jauna lapa',
+    'pages_attachments' => 'Pielikumi',
+    'pages_navigation' => 'Lapas navigācija',
+    'pages_delete' => 'Dzēst lapu',
+    'pages_delete_named' => 'Dzēst lapu :pageName',
+    'pages_delete_draft_named' => 'Dzēst :pageName melnrakstu',
+    'pages_delete_draft' => 'Dzēst melnrakstu',
+    'pages_delete_success' => 'Lapa ir dzēsta',
+    'pages_delete_draft_success' => 'Melnraksts ir dzēsts',
+    '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',
+    'pages_edit_draft_options' => 'Melnraksta iestatījumi',
+    'pages_edit_save_draft' => 'Saglabāt melnrakstu',
+    'pages_edit_draft' => 'Labot melnrakstu',
+    'pages_editing_draft' => 'Labo melnrakstu',
+    'pages_editing_page' => 'Labo lapu',
+    'pages_edit_draft_save_at' => 'Melnraksts saglabāts ',
+    'pages_edit_delete_draft' => 'Dzēst melnrakstu',
+    'pages_edit_discard_draft' => 'Atmest malnrakstu',
+    'pages_edit_set_changelog' => 'Pievienot izmaiņu aprakstu',
+    'pages_edit_enter_changelog_desc' => 'Ievadi nelielu aprakstu par vaiktajām izmaiņām',
+    'pages_edit_enter_changelog' => 'Izmaiņu apraksts',
+    'pages_save' => 'Saglabāt lapu',
+    'pages_title' => 'Lapas virsraksts',
+    'pages_name' => 'Lapas nosaukums',
+    'pages_md_editor' => 'Redaktors',
+    'pages_md_preview' => 'Priekšskatījums',
+    'pages_md_insert_image' => 'Ievietot attēlu',
+    'pages_md_insert_link' => 'Ievietot vienuma saiti',
+    'pages_md_insert_drawing' => 'Ievietot zīmējumu',
+    'pages_not_in_chapter' => 'Lapa nav nodaļā',
+    'pages_move' => 'Pārvietot lapu',
+    'pages_move_success' => 'Lapa pārvietota uz ":parentName"',
+    'pages_copy' => 'Kopēt lapu',
+    'pages_copy_desination' => 'Kopijas mērķa vieta',
+    'pages_copy_success' => 'Lapa veiksmīgi nokopēta',
+    'pages_permissions' => 'Lapas atļaujas',
+    'pages_permissions_success' => 'Lapas atļaujas atjauninātas',
+    'pages_revision' => 'Revīzijas',
+    'pages_revisions' => 'Lapas revīzijas',
+    'pages_revisions_named' => ':pageName lapas revīzijas',
+    'pages_revision_named' => ':pageName lapas revīzija',
+    'pages_revision_restored_from' => 'Atjaunots no #:id; :summary',
+    'pages_revisions_created_by' => 'Izveidoja',
+    'pages_revisions_date' => 'Revīzijas datums',
+    'pages_revisions_number' => '#',
+    'pages_revisions_numbered' => 'Revīzija #:id',
+    'pages_revisions_numbered_changes' => 'Revīzijas #:id izmaiņas',
+    'pages_revisions_changelog' => 'Izmaiņu žurnāls',
+    'pages_revisions_changes' => 'Izmaiņas',
+    'pages_revisions_current' => 'Tekošā versija',
+    'pages_revisions_preview' => 'Priekšskatījums',
+    'pages_revisions_restore' => 'Atjaunot',
+    'pages_revisions_none' => 'Šai lapai nav revīziju',
+    'pages_copy_link' => 'Kopēt saiti',
+    'pages_edit_content_link' => 'Labot saturu',
+    'pages_permissions_active' => 'Lapas atļaujas ir aktīvas',
+    'pages_initial_revision' => 'Sākotnējā publikācija',
+    'pages_initial_name' => 'Jauna lapa',
+    'pages_editing_draft_notification' => 'Jūs pašlaik veicat izmaiņas melnrakstā, kurš pēdējo reizi ir saglabāts :timeDiff.',
+    'pages_draft_edited_notification' => 'Šī lapa ir tikusi atjaunināta. Šo melnrakstu ieteicams atmest.',
+    'pages_draft_edit_active' => [
+        'start_a' => ':count lietotāji pašlaik veic izmaiņas šajā lapā',
+        'start_b' => ':userName veic izmaiņas šajā lapā',
+        'time_a' => 'kopš šī lapa pēdējo reizi ir atjaunināta',
+        'time_b' => 'pēdējās :minCount minūtēs',
+        'message' => ':start :time. Esat uzmanīgi, lai neaizstātu viens otra izmaiņas!',
+    ],
+    'pages_draft_discarded' => 'Melnraksts ir atcelts, redaktors ir atjaunināts ar pašreizējo lapas saturu',
+    'pages_specific' => 'Konkrēta lapa',
+    'pages_is_template' => 'Lapas šablons',
+
+    // Editor Sidebar
+    'page_tags' => 'Lapas birkas',
+    'chapter_tags' => 'Nodaļas birkas',
+    'book_tags' => 'Grāmatas birkas',
+    'shelf_tags' => 'Plauktu birkas',
+    'tag' => 'Birka',
+    'tags' =>  'Birkas',
+    'tag_name' =>  'Birkas nosaukums',
+    'tag_value' => 'Birkas papildvērtība (neobligāta)',
+    'tags_explain' => "Pievieno birkas, lai precīzāk grupētu saturu.\n Tu vari pievienot papildus vērtību birkai vēl precīzākai grupēšanai.",
+    'tags_add' => 'Pievienot vēlvienu birku',
+    'tags_remove' => 'Noņemt šo birku',
+    'attachments' => 'Pielikumi',
+    'attachments_explain' => 'Augšupielādējiet dažus failus vai pievieno saites, kas tiks parādītas jūsu lapā. Tie būs redzami lapas sānjoslā.',
+    'attachments_explain_instant_save' => 'Izmaiņas šeit tiek saglabātas nekavējoties.',
+    'attachments_items' => 'Pievienotie vienumi',
+    'attachments_upload' => 'Augšupielādēt failu',
+    'attachments_link' => 'Pievienot saiti',
+    'attachments_set_link' => 'Uzstādīt saiti',
+    'attachments_delete' => 'Vai tiešām vēlaties dzēst šo pielikumu?',
+    'attachments_dropzone' => 'Ievilkt failus vai klikšķināt šeit, lai pievieotu failus',
+    'attachments_no_files' => 'Neviens fails nav augšupielādēts',
+    'attachments_explain_link' => 'Ja nevēlaties augšupielādēt failu, varat pievienot saiti. Tā var būt saite uz citu lapu vai saite uz failu mākonī.',
+    'attachments_link_name' => 'Saites nosaukums',
+    'attachment_link' => 'Pielikuma saite',
+    'attachments_link_url' => 'Saite uz failu',
+    'attachments_link_url_hint' => 'Web lapas vai faila URL',
+    'attach' => 'Pievienot',
+    'attachments_insert_link' => 'Pievienot pielikuma saiti lapai',
+    'attachments_edit_file' => 'Rediģēt failu',
+    'attachments_edit_file_name' => 'Faila nosaukums',
+    'attachments_edit_drop_upload' => 'Ievelc failus vai spied šeit, lai augšupielādētu vai aizstātu failus',
+    'attachments_order_updated' => 'Pielikuma secība ir atjaunināta',
+    'attachments_updated_success' => 'Pielikuma informācja ir atjaunināta',
+    'attachments_deleted' => 'Pielikums dzēsts',
+    'attachments_file_uploaded' => 'Fails veiksmīgi augšupielādēts',
+    'attachments_file_updated' => 'Fails veiksmīgi atjaunināts',
+    'attachments_link_attached' => 'Hipersaite veismīgi pievienota lapai',
+    'templates' => 'Šabloni',
+    'templates_set_as_template' => 'Šī lapa ir šablons',
+    'templates_explain_set_as_template' => 'Jūs varat iestatīt šo lapu kā veidni, lai tās saturs tiktu izmantots, veidojot citas lapas. Citi lietotāji varēs izmantot šo veidni, ja viņiem būs atļauja piekļūt šai lapai.',
+    'templates_replace_content' => 'Aizstāt lapas saturu',
+    'templates_append_content' => 'Pievienot lapas saturam (beigās)',
+    'templates_prepend_content' => 'Pievienot lapas saturam (sākumā)',
+
+    // Profile View
+    'profile_user_for_x' => 'Lietotājs jau :time',
+    'profile_created_content' => 'Izveidotais saturs',
+    'profile_not_created_pages' => ':userName nav izveidojis lapas',
+    'profile_not_created_chapters' => ':userName nav izveidojis nodalas',
+    'profile_not_created_books' => ':userName nav izveidojis grāmatas',
+    'profile_not_created_shelves' => ':userName nav izveidojis grāmatplauktus',
+
+    // Comments
+    'comment' => 'Komentārs',
+    'comments' => 'Komentāri',
+    'comment_add' => 'Pievienot komentāru',
+    'comment_placeholder' => 'Pievieno komentāru',
+    'comment_count' => '{0} Nav komentāru |{1} 1 Komentārs|[2,*] :count Komentāri',
+    'comment_save' => 'Saglabāt komentāru',
+    'comment_saving' => 'Saglabā komentāru...',
+    'comment_deleting' => 'Dzēš komentāru...',
+    'comment_new' => 'Jauns komentārs',
+    'comment_created' => 'komentējis :createDiff',
+    'comment_updated' => ':username atjauninājis pirms :updateDiff',
+    'comment_deleted_success' => 'Komentārs ir dzēsts',
+    'comment_created_success' => 'Komentārs ir pievienots',
+    'comment_updated_success' => 'Komentārs ir atjaunināts',
+    'comment_delete_confirm' => 'Vai esat pārliecināts, ka vēlaties dzēst šo komentāru?',
+    'comment_in_reply_to' => 'Atbildēt uz :commentId',
+
+    // Revision
+    'revision_delete_confirm' => 'Vai esat pārliecināts, ka vēlaties dzēst šo revīziju?',
+    'revision_restore_confirm' => 'Vai esat pārliecināts, ka vēlaties atjaunot šo revīziju? Tekošais lapas saturs tiks aizstāts.',
+    'revision_delete_success' => 'Revīzija dzēsta',
+    'revision_cannot_delete_latest' => 'Nevar dzēst tekošo revīziju.'
+];
diff --git a/resources/lang/lv/errors.php b/resources/lang/lv/errors.php
new file mode 100644 (file)
index 0000000..74c797d
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Text shown in error messaging.
+ */
+return [
+
+    // Permissions
+    'permission' => 'Jums nav atļauts piekļūt šai lapai.',
+    'permissionJson' => 'Jums nav atļauts veikt konkrēto darbību.',
+
+    // Auth
+    'error_user_exists_different_creds' => 'Lietotājs ar epastu :email bet ar citiem piekļuves datiem jau eksistē.',
+    'email_already_confirmed' => 'Epasts jau ir apstiprināts, mēģini ielogoties.',
+    'email_confirmation_invalid' => 'Šis apstiprinājuma žetons nav derīgs vai jau ir izmantots. Lūdzu, mēģiniet reģistrēties vēlreiz.',
+    'email_confirmation_expired' => 'Apstiprinājuma žetona derīguma termiņš ir beidzies. Ir nosūtīts jauns apstiprinājuma e-pasts.',
+    'email_confirmation_awaiting' => 'Šī konta e-pasta adresei ir nepieciešms apstiprinājums',
+    'ldap_fail_anonymous' => 'LDAP piekļuve neveiksmīga izmantojot anonymous bind',
+    'ldap_fail_authed' => 'LDAP piekļuve neveiksmīga izmantojot norādīto dn un paroli',
+    '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',
+    'social_no_action_defined' => 'Darbības nav definētas',
+    'social_login_bad_response' => "Saņemta kļūda izmantojot :socialAccount piekļuvi:\n:error",
+    'social_account_in_use' => 'Šis :socialAccount konts jau tiek izmantots, mēģiniet ieiet ar :socialAccount piekļuves iespēju.',
+    'social_account_email_in_use' => 'Šis epasts :email jau tiek izmantots. Ja jums jau ir konts, jūs varat pieslēgt savu :socialAccount kontu savos profila uzstādījumos.',
+    'social_account_existing' => 'Šis :socialAccount konts jau ir piesaistīts jūsu profilam.',
+    'social_account_already_used_existing' => 'Šo :socialAccount konts jau ir piesaistīts citam lietotājam.',
+    'social_account_not_used' => 'Šis :socialAccount konts nav piesaistīts nevienam lietotājām. Lūdzu pievienojiet to savos profila uzstādījumos. ',
+    'social_account_register_instructions' => 'Ja jums vēl nav savs konts, jūs varat reģistrēt kontu izmantojot :socialAccount piekļuvi.',
+    'social_driver_not_found' => 'Sociālā tīkla savienojums nav atrasts',
+    'social_driver_not_configured' => 'Jūsu :socialAccount sociālie iestatījumi nav uzstādīti pareizi.',
+    'invite_token_expired' => 'Šī uzaicinājuma saite ir novecojusi. Tā vietā jūs varat mēģināt atiestatīt sava konta paroli.',
+
+    // System
+    'path_not_writable' => 'Faila ceļā :filePath nav iespējams ielādēt failus. Lūdzu pārliecinieties, ka serverim tur ir rakstīšanas tiesības.',
+    '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.',
+    'uploaded'  => 'Serveris neatļauj šāda izmēra failu ielādi. Lūdzu mēģiniet mazāka izmēra failu.',
+    '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',
+    'file_upload_timeout' => 'Faila augšupielādē ir iestājies noilgums.',
+
+    // Attachments
+    'attachment_not_found' => 'Pielikums nav atrasts',
+
+    // Pages
+    'page_draft_autosave_fail' => 'Neizdevās saglabāt uzmetumu. Pārliecinieties, ka jūsu interneta pieslēgums ir aktīvs pirms saglabājiet šo lapu',
+    'page_custom_home_deletion' => 'Nav iespējams izdzēst lapu kamēr tā ir uzstādīta kā sākumlapa',
+
+    // Entities
+    'entity_not_found' => 'Vienība nav atrasta',
+    'bookshelf_not_found' => 'Grāmatplaukts nav atrasts',
+    'book_not_found' => 'Grāmata nav atrasta',
+    'page_not_found' => 'Lapa nav atrasta',
+    'chapter_not_found' => 'Nodaļa nav atrasta',
+    'selected_book_not_found' => 'Iezīmētā grāmata nav atrasta',
+    'selected_book_chapter_not_found' => 'Izvēlētā grāmata vai nodaļa nav atrasta',
+    'guests_cannot_save_drafts' => 'Viesi nevar saglabāt melnrakstus',
+
+    // Users
+    'users_cannot_delete_only_admin' => 'Jūs nevarat dzēst vienīgo administratoru',
+    'users_cannot_delete_guest' => 'Jūs nevarat dzēst lietotāju "viesis"',
+
+    // Roles
+    'role_cannot_be_edited' => 'Šo lomu nevar rediģēt',
+    'role_system_cannot_be_deleted' => 'Šī ir sistēmas loma un nevar tikt izdzēsta',
+    'role_registration_default_cannot_delete' => 'Šī loma nevar tikt izdzēsta, kamēr tā uzstādīta kā noklusētā reģistrācijas loma',
+    'role_cannot_remove_only_admin' => 'Šis ir vienīgais lietotājs, kam norādīta administratora loma. Pievienojiet administratora lomu citam lietotājam pirms mēģiniet to izslēgt šeit.',
+
+    // Comments
+    'comment_list' => 'Radās kļūda ielasot komentārus.',
+    'cannot_add_comment_to_draft' => 'Melnrakstam nevar pievienot komentārus.',
+    'comment_add' => 'Radās kļūda pievienojot/atjaunojot komentāru.',
+    'comment_delete' => 'Radās kļūda dzēšot komentāru.',
+    'empty_comment' => 'Nevar pievienot tukšu komentāru.',
+
+    // Error pages
+    '404_page_not_found' => 'Lapa nav atrasta',
+    'sorry_page_not_found' => 'Atvainojiet, meklētā lapa nav atrasta.',
+    'sorry_page_not_found_permission_warning' => 'Ja šai lapai būtu bijis te jābūt, jums var nebūt pietiekamas piekļuves tiesības, lai to apskatītu.',
+    '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' => 'Atgriezties uz sākumu',
+    'error_occurred' => 'Radusies kļūda',
+    'app_down' => ':appName pagaidām nav pieejams',
+    'back_soon' => 'Drīz būs atkal pieejams.',
+
+    // API errors
+    'api_no_authorization_found' => 'Pieprasījumā nav atrasts autorizācijas žetons',
+    'api_bad_authorization_format' => 'Pieprasījumā atrasts autorizācijas žetons, taču tā formāts nav pareizs',
+    'api_user_token_not_found' => 'Nav atrasts norādītajam autorizācijas žetonam atbilstošs API žetons',
+    'api_incorrect_token_secret' => 'Norādītā slepenā atslēga izmantotajam API žetonam nav pareiza',
+    'api_user_no_api_permission' => 'Izmantotā API žetona īpašniekam nav tiesības veikt API izsaukumus',
+    'api_user_token_expired' => 'Autorizācijas žetona derīguma termiņš ir izbeidzies',
+
+    // Settings & Maintenance
+    'maintenance_test_email_failure' => 'Radusies kļūda sūtot testa epastu:',
+
+];
diff --git a/resources/lang/lv/pagination.php b/resources/lang/lv/pagination.php
new file mode 100644 (file)
index 0000000..c46d6dc
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
+return [
+
+    'previous' => '&laquo; Iepriekšējais',
+    'next'     => 'Nākamais &raquo;',
+
+];
diff --git a/resources/lang/lv/passwords.php b/resources/lang/lv/passwords.php
new file mode 100644 (file)
index 0000000..7d93957
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
+return [
+
+    'password' => 'Parolēm jābūt vismaz astoņu simbolu garām un jāatbilst apstiprinājumam.',
+    'user' => "Mēs nevaram atrast lietotāju ar šādu e-pasta adresi.",
+    'token' => 'Paroles atiestatīšanas atslēga neatbilst šai e-pasta adresei.',
+    'sent' => 'Esam nosūtījuši paroles atiestatīšanas saiti!',
+    'reset' => 'Parole ir atiestatīta!',
+
+];
diff --git a/resources/lang/lv/settings.php b/resources/lang/lv/settings.php
new file mode 100644 (file)
index 0000000..decc25c
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
+return [
+
+    // Common Messages
+    'settings' => 'Iestatījumi',
+    'settings_save' => 'Saglabāt iestatījumus',
+    'settings_save_success' => 'Iestatījumi saglabāti',
+
+    // App Settings
+    'app_customization' => 'Pielāgojumi',
+    'app_features_security' => 'Funkcijas un drošība',
+    'app_name' => 'Lietotnes nosaukums',
+    'app_name_desc' => 'Šis vārds tiks rādīts navigācijas joslā un sistēmas sūtītajis e-pastos.',
+    'app_name_header' => 'Rādīt vārdu navigācijas joslā',
+    'app_public_access' => 'Publiska piekļuve',
+    'app_public_access_desc' => 'Šīs opcijas ieslēgšana ļaus neautorizētiem apmeklētājiem piekļūt jūsu BookStack saturam.',
+    'app_public_access_desc_guest' => 'Publisku apmeklētāju piekļuvi var kontrolēt "Guest" (Viesa) lietotāja uzstādījumos.',
+    'app_public_access_toggle' => 'Atļaut publisku piekļuvi',
+    'app_public_viewing' => 'Atļaut publisku piekļuvi?',
+    'app_secure_images' => 'Paaugstinātas drošības attēlu ielāde',
+    'app_secure_images_toggle' => 'Ieslēgt paaugstinātas drošības attēlu ielādi',
+    'app_secure_images_desc' => 'Ātrdarbības nolūkos attēli ir publiski pieejami. Šī opcija pievieno nejaušu grūti uzminamu teksta virkni attēlu adresēs. Pārliecinieties kā ir izslēgta direktoriju pārlūkošana, lai nepieļautu vieglu piekļuvi šiem failiem.',
+    'app_editor' => 'Lapas redaktors',
+    'app_editor_desc' => 'Izvēlēties kurš redaktors tiks izmatots lapu rediģēšanai visiem lietotājiem.',
+    'app_custom_html' => 'Pielāgot HTML head saturu',
+    'app_custom_html_desc' => 'Šis saturs tiks pievienots <head> sadaļas apakšā visām lapām. Tas ir noderīgi papildinot CSS stilus vai pievienojot analītikas kodu.',
+    'app_custom_html_disabled_notice' => 'Pielāgots HTML head saturs ir izslēgts šajā uzstādījumu lapā, lai nodrošinātu, ka iespējams atcelt jebkādas kritiskas izmaiņas.',
+    'app_logo' => 'Lietotnes logo',
+    'app_logo_desc' => 'Attēlam jābūt 43px augstam. <br>Lielāki attēli tiks samazināti.',
+    'app_primary_color' => 'Galvenā aplikācijas krāsa',
+    'app_primary_color_desc' => 'Uzstāda primāro krāsu aplikācijai, ieskaitot banneri, pogas un saites.',
+    'app_homepage' => 'Aplikācijas sākumlapa',
+    'app_homepage_desc' => 'Izvēlēties skatu, ko rādīt sākumlapā noklusētā skata vietā. Lapas piekļuves tiesības izvēlētajai lapai netiks ņemtas vērā.',
+    'app_homepage_select' => 'Izvēlēties lapu',
+    'app_footer_links' => 'Kājenes saites',
+    'app_footer_links_desc' => 'Pievienot saites, ko attēlot lapas kājenē. Tās tiks attēlotas lielākās daļas lapu apakšā, ieskaitot tās, kas pieejamas bez reģistrācijas. Jūs varat izmantot nosaukumu "trans::<key>", lai izmantotu sistēmā definētus tulkojumus. Piemēram, "trans::common.privacy_policy" tiks aizvietots ar tulkoto tekstu "Privātuma politika" un "trans::common.terms_of_service" kļūs par "Lietošanas noteikumi".',
+    'app_footer_links_label' => 'Saites nosaukums',
+    'app_footer_links_url' => 'Saites URL',
+    'app_footer_links_add' => 'Pievienot kājenes saiti',
+    'app_disable_comments' => 'Izslēgt komentārus',
+    'app_disable_comments_toggle' => 'Izslēgt komentārus',
+    'app_disable_comments_desc' => 'Atslēdz komentārus visās aplikācijas lapās.<br> Jau eksistējoši komentāri netiks attēloti.',
+
+    // Color settings
+    'content_colors' => 'Satura krāsas',
+    '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.',
+    'bookshelf_color' => 'Plaukta krāsa',
+    'book_color' => 'Grāmatas krāsa',
+    'chapter_color' => 'Nodaļas krāsa',
+    'page_color' => 'Lapas krāsa',
+    'page_draft_color' => 'Lapas uzmetuma krāsa',
+
+    // Registration Settings
+    'reg_settings' => 'Reģistrācija',
+    'reg_enable' => 'Iespējot reģistrāciju',
+    'reg_enable_toggle' => 'Iespējot reģistrāciju',
+    'reg_enable_desc' => 'Kad reģistrācija ir ieslēgta, lietotāji varēs paši reģistrēties kā aplikācijas lietotāji. Pēc reģistrācijas tiem tiks piešķirta noklusētā lietotāja loma.',
+    'reg_default_role' => 'Noklusētā lietotāja loma pēc reģistrācijas',
+    'reg_enable_external_warning' => 'Šis uzstādījums tiek ignorēts kamēr tiek izmantota ārēja LDAP vai SAML autentifikācija. Tiks izveidoti lietotāju konti neeksistējošiem leitotājiem, ja autentifikācija pret ārējo sistēmu būs veiksmīga.',
+    'reg_email_confirmation' => 'E-pasta apstiprinājums',
+    'reg_email_confirmation_toggle' => 'Pieprasīt epasta apstiprināšanu',
+    'reg_confirm_email_desc' => 'Ja ieslēgts domēnu ierobežojums, tad būs nepieciešama epasta apstiprināšana un šis uzstādījums tiks ignorēts.',
+    'reg_confirm_restrict_domain' => 'Domēnu ierobežojums',
+    'reg_confirm_restrict_domain_desc' => 'Ievadiet ar komatiem atdalītu sarakstu ar epasta domēniem, kam jūs gribētu atļaut reģistrāciju. Lietotājiem tiks nosūtīts epasts, lai apstiprinātu tā adresi pirms tiks ļauts darboties ar aplikāciju. <br> Ņemiet vērā, ka lietotāji varēs nomainīt savu epasta adresi pēc veiksmīgas reģistrācijas.',
+    'reg_confirm_restrict_domain_placeholder' => 'Nav ierobežojumu',
+
+    // Maintenance settings
+    'maint' => 'Apkope',
+    'maint_image_cleanup' => 'Tīrīt neizmantotās bildes',
+    'maint_image_cleanup_desc' => "Pārbauda lapu un lapu versiju saturu, lai noteiktu, kuri attēli pašlaik tiek izmantoti, un kuri nav nepieciešami. Pārliecinieties, ka ir veikta pilna datubāzes un attēlu rezerves kopija pirms šīs darbības.",
+    'maint_delete_images_only_in_revisions' => 'Dzēst arī attēlus, kas izmantoti tikai vecās lapu satura versijās',
+    'maint_image_cleanup_run' => 'Veikt tīrīšanu',
+    'maint_image_cleanup_warning' => ':count iespējami neizmantoti attēli atrasti. Vai tiešām vēlaties izdzēst šos attēlus?',
+    'maint_image_cleanup_success' => ':count iespējami neizmantoti attēli atrasti un izdzēsti!',
+    'maint_image_cleanup_nothing_found' => 'Nav atrasti neizmantoti attēli, nekas netika izdzēsts!',
+    'maint_send_test_email' => 'Nosūtīt testa epastu',
+    'maint_send_test_email_desc' => 'Nosūtīt testa epastu uz jūsu profilā norādīto epasta adresi.',
+    'maint_send_test_email_run' => 'Nosūtīt testa epastu',
+    'maint_send_test_email_success' => 'Epasts nosūtīts uz :address',
+    'maint_send_test_email_mail_subject' => 'Testa epasts',
+    'maint_send_test_email_mail_greeting' => 'Izskatās, ka epasta piegāde strādā!',
+    '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',
+
+    // Recycle Bin
+    'recycle_bin' => 'Miskaste',
+    'recycle_bin_desc' => 'Te jūs varat atjaunot dzēstās vienības vai arī izdzēst tās no sistēmas pilnībā. Šis saraksts nav filtrēts atšķirībā no līdzīgiem darbību sarakstiem sistēmā, kur ir piemēroti piekļuves tiesību filtri.',
+    'recycle_bin_deleted_item' => 'Dzēsta vienība',
+    'recycle_bin_deleted_by' => 'Izdzēsa',
+    'recycle_bin_deleted_at' => 'Dzēšanas laiks',
+    'recycle_bin_permanently_delete' => 'Neatgriezeniski izdzēst',
+    'recycle_bin_restore' => 'Atjaunot',
+    'recycle_bin_contents_empty' => 'Miskaste ir tukša',
+    'recycle_bin_empty' => 'Iztīrīt miskasti',
+    'recycle_bin_empty_confirm' => 'Šī darbība pilnībā dzēsīs visas vienības miskastē, ieskaitot saturu, kas ievietots katrā no šīm vienībām. Vai tiešām vēlaties dzēst visu miskastes saturu?',
+    'recycle_bin_destroy_confirm' => 'Šī darbība pilnībā izdzēsis šo vienību kopā ar tai pakārtotajiem elementiem no sistēmas, un jūs nevarēsiet šo saturu atjaunot. Vai tiešām vēlaties pilnībā izdzēst šo vienību?',
+    'recycle_bin_destroy_list' => 'Dzēšamās vienības',
+    'recycle_bin_restore_list' => 'Atjaunojamās vienības',
+    'recycle_bin_restore_confirm' => 'Šī darbība atjaunos dzēsto vienību, tai skaitā visus tai pakārtotos elementus, uz tās sākotnējo atrašanās vietu. Ja sākotnējā atrašanās vieta ir izdzēsta un atrodas miskastē, būs nepieciešams atjaunot arī to.',
+    'recycle_bin_restore_deleted_parent' => 'Šo elementu saturošā vienība arī ir dzēsta. Tas paliks dzēsts līdz šī saturošā vienība arī ir atjaunota.',
+    'recycle_bin_destroy_notification' => 'Dzēstas kopā :count vienības no miskastes.',
+    'recycle_bin_restore_notification' => 'Atjaunotas kopā :count vienības no miskastes.',
+
+    // Audit Log
+    'audit' => 'Auditācijas pieraksti',
+    'audit_desc' => 'Šie auditācijas pieraksti attēlo sarakstu ar sistēmā reģistrētajām aktivitātēm. Šis saraksts nav filtrēts atšķirībā no līdzīgiem aktivitāšu sarakstiem sistēmā, kur ir piemēroti atļauto darbību filtri.',
+    'audit_event_filter' => 'Notikumu filtrs',
+    'audit_event_filter_no_filter' => 'Bez filtra',
+    'audit_deleted_item' => 'Dzēsta vienība',
+    'audit_deleted_item_name' => 'Vārds: :name',
+    'audit_table_user' => 'Lietotājs',
+    'audit_table_event' => 'Notikums',
+    'audit_table_related' => 'Saistīta vienība vai detaļa',
+    'audit_table_date' => 'Notikuma datums',
+    'audit_date_from' => 'Datums no',
+    'audit_date_to' => 'Datums līdz',
+
+    // Role Settings
+    'roles' => 'Grupas',
+    'role_user_roles' => 'Lietotāju grupas',
+    'role_create' => 'Izveidot jaunu grupu',
+    'role_create_success' => 'Grupa veiksmīgi izveidota',
+    'role_delete' => 'Dzēst grupu',
+    'role_delete_confirm' => 'Loma \':roleName\' tiks dzēsta.',
+    'role_delete_users_assigned' => 'Šajā grupā ir pievienoti :userCount lietotāji. Ja vēlaties pārvietot lietotājus no šīs grupas, tad izvēlaties kādu no zemāk redzamajām grupām.',
+    'role_delete_no_migration' => "Nepārvietot lietotājus",
+    'role_delete_sure' => 'Vai tiešām vēlaties dzēst grupu?',
+    'role_delete_success' => 'Grupa veiksmīgi dzēsta',
+    'role_edit' => 'Rediģēt grupu',
+    'role_details' => 'Informācija par grupu',
+    'role_name' => 'Grupas nosaukums',
+    'role_desc' => 'Īss grupas apaksts',
+    'role_external_auth_id' => 'Ārējais autentifikācijas ID',
+    'role_system' => 'Sistēmas atļaujas',
+    'role_manage_users' => 'Pārvaldīt lietotājus',
+    'role_manage_roles' => 'Pārvaldīt grupas un grupu atļaujas',
+    'role_manage_entity_permissions' => 'Pārvaldīt visu grāmatu, nodaļu un lapu atļaujas',
+    'role_manage_own_entity_permissions' => 'Pārvaldīt atļaujas savām grāmatām, nodaļām un lapām',
+    'role_manage_page_templates' => 'Pārvaldīt lapas veidnes',
+    'role_access_api' => 'Piekļūt sistēmas API',
+    'role_manage_settings' => 'Pārvaldīt iestatījumus',
+    'role_asset' => 'Resursa piekļuves tiesības',
+    'roles_system_warning' => 'Jebkuras no trīs augstāk redzamajām atļaujām dod iespēju lietotājam mainīt savas un citu lietotāju sistēmas atļaujas. Pievieno šīs grupu atļaujas tikai tiem lietotājiem, kuriem uzticies.',
+    'role_asset_desc' => 'Šīs piekļuves tiesības kontrolē noklusēto piekļuvi sistēmas resursiem. Grāmatām, nodaļām un lapām norādītās tiesības būs pārākas par šīm.',
+    'role_asset_admins' => 'Administratoriem automātiski ir piekļuve visam saturam, bet šie uzstādījumi var noslēpt vai parādīt lietotāja saskarnes iespējas.',
+    'role_all' => 'Visi',
+    'role_own' => 'Savi',
+    'role_controlled_by_asset' => 'Kontrolē resurss, uz ko tie ir augšupielādēti',
+    'role_save' => 'Saglabāt grupu',
+    'role_update_success' => 'Grupa veiksmīgi atjaunināta',
+    'role_users' => 'Lietotāji šajā grupā',
+    'role_users_none' => 'Pagaidām neviens lietotājs nav pievienots šai grupai',
+
+    // Users
+    'users' => 'Lietotāji',
+    'user_profile' => 'Lietotāja profils',
+    'users_add_new' => 'Pievienot jaunu lietotāju',
+    'users_search' => 'Meklēt lietotājus',
+    'users_latest_activity' => 'Pēdējās aktivitātes',
+    'users_details' => 'Lietotāja informācija',
+    'users_details_desc' => 'Uzstādīt attēlojamo vārdu un epast adresi šim lietotājam. Epasta adresi varēs izmantot, lai piekļūtu aplikācijai.',
+    'users_details_desc_no_email' => 'Uzstādiet attēlojamu vārdu šim lietotājam, lai citi varētu viņu atpazīt.',
+    'users_role' => 'Lietotāju grupas',
+    'users_role_desc' => 'Izvēlēties kurām grupām pievienot lietotāju. Ja lietotājs ir pievienots vairākām grupām, tad lietotājam būs pieejamas visu grupu atļaujas.',
+    'users_password' => 'Lietotāja parole',
+    'users_password_desc' => 'Uzstādiet paroli, ar ko piekļūt aplikācijai. Tai jābūt vismaz 6 simbolus garai.',
+    'users_send_invite_text' => 'Jūs varat izvēlētes vai nosūtīt šim lietotājam uzaicinājuma epastu, kas ļauj tam uzstādīt savu paroli pašam, vai arī varat uzstādīt paroli tagad.',
+    'users_send_invite_option' => 'Nosūtīt lietotāja uzaicinājuma epastu',
+    'users_external_auth_id' => 'Ārējais autentifikācijas ID',
+    'users_external_auth_id_desc' => 'Šis ir identifikators, kas tiks izmantots, lai atpazītu lietotāju, sazinoaties ar jūsu ārējo autentifikācijas sistēmu.',
+    'users_password_warning' => 'Aizpildiet tikai tad, ja vēlaties mainīt savu paroli.',
+    'users_system_public' => 'Šis lietotājs apzīmē visus viesus, kas apmeklēs jūsu lapu. To nevar izmantot lapas piekļuvei un tas tiek norādīts automātiski.',
+    'users_delete' => 'Dzēst lietotāju',
+    'users_delete_named' => 'Dzēst lietotāju :userName',
+    'users_delete_warning' => 'Šī darbība pilnībā izdzēsīs lietotāju \':userName\' no sistēmas.',
+    'users_delete_confirm' => 'Vai tiešām vēlaties dzēst šo lietotāju?',
+    'users_migrate_ownership' => 'Pārcelt īpašumtiesības',
+    'users_migrate_ownership_desc' => 'Izvēlieties lietotāju, ja vēlaties citam lietotājam pārcelt pašlaik šim lietotājam piederošās vienības.',
+    'users_none_selected' => 'Nav izvēlēts lietotājs',
+    'users_delete_success' => 'Lietotājs veiksmīgi dzēsts',
+    'users_edit' => 'Rediģēt lietotāju',
+    'users_edit_profile' => 'Rediģēt profilu',
+    'users_edit_success' => 'Lietotājs veiksmīgi atjaunināts',
+    'users_avatar' => 'Lietotāja attēls',
+    'users_avatar_desc' => 'Izvēlieties attēlu šim lietotājam. Tam vajadzētu būt apmēram 256px kvadrātam.',
+    'users_preferred_language' => 'Vēlamā valoda',
+    'users_preferred_language_desc' => 'Šis uzstādījums nomainīs valodu, kas izmantota aplikācijas lietotāja saskarnē. Tas neietekmēs neko no lietotāju radītā satura.',
+    'users_social_accounts' => 'Sociālie konti',
+    '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_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',
+    'users_api_tokens_none' => 'Šim lietotājam nav izveidotu API žetonu',
+    'users_api_tokens_create' => 'Izveidot žetonu',
+    'users_api_tokens_expires' => 'Derīguma termiņš',
+    'users_api_tokens_docs' => 'API dokumentācija',
+
+    // API Tokens
+    'user_api_token_create' => 'Izveidot API žetonu',
+    'user_api_token_name' => 'Vārds',
+    'user_api_token_name_desc' => 'Uzstādiet nolasāmu nosaukumu savam žetonam, lai nākotnē atgadinātu par tā pielietojumu.',
+    'user_api_token_expiry' => 'Derīgs līdz',
+    'user_api_token_expiry_desc' => 'Uzstādiet datumu, kad beidzas žetona derīguma termiņš. Pieprasījumi, kas veikti pēc šī datuma ar šo žetonu vairs nedarbosies. Atstājot lauku tukšu, tiks uzstādīts derīguma termiņš 100 gadu nākotnē.',
+    'user_api_token_create_secret_message' => 'Uzreiz pēc žetona izveidošanas tiks parādīts žetona ID un žetona noslēpums. Šis noslēpums tiks attēlots tikai vienreiz, tāpēc pārliecinieties, ka tā vērtība ir nokopēta uz kādu citu drošu vietu pirms turpināšanas.',
+    'user_api_token_create_success' => 'API žetons veiksmīgi izveidots',
+    'user_api_token_update_success' => 'API žetons veiksmīgi atjaunināts',
+    'user_api_token' => 'API žetons',
+    'user_api_token_id' => 'Žetona ID',
+    'user_api_token_id_desc' => 'Šis ir neizmaināms sistēmas ģenerēts identifikators šim žetonam, kas būs jānorāda API pieprasījumos.',
+    'user_api_token_secret' => 'Žetona noslēpums',
+    'user_api_token_secret_desc' => 'Šis ir sistēmas ģenerēts noslēpums šim žetonam, ko būs nepieciešams norādīt API pieprasījumos. Tas tiks attēlots tikai vienu reizi, tāpēc nokopējiet to uz kādu citu drošu vietu.',
+    'user_api_token_created' => 'Žetons izveidots :timeAgo',
+    'user_api_token_updated' => 'Žetons atjaunināts :timeAgo',
+    'user_api_token_delete' => 'Dzēst žetonu',
+    'user_api_token_delete_warning' => 'Šī darbība pilnībā izdzēsīs API žetonu \':tokenName\' no sistēmas.',
+    'user_api_token_delete_confirm' => 'Vai tiešām vēlaties dzēst šo API žetonu?',
+    'user_api_token_delete_success' => 'API žetons veiksmīgi dzēsts',
+
+    //! If editing translations files directly please ignore this in all
+    //! languages apart from en. Content will be auto-copied from en.
+    //!////////////////////////////////
+    'language_select' => [
+        'en' => 'English',
+        'ar' => 'العربية',
+        'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Katalāņu',
+        'cs' => 'Česky',
+        'da' => 'Dansk',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
+        'es' => 'Español',
+        'es_AR' => 'Español Argentina',
+        'fr' => 'Français',
+        'he' => 'עברית',
+        'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
+        'it' => 'Italian',
+        'ja' => '日本語',
+        'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
+        'nl' => 'Nederlands',
+        'nb' => 'Norsk (Bokmål)',
+        'pl' => 'Polski',
+        'pt' => 'Português',
+        'pt_BR' => 'Português do Brasil',
+        'ru' => 'Русский',
+        'sk' => 'Slovensky',
+        'sl' => 'Slovenščina',
+        'sv' => 'Svenska',
+        'tr' => 'Türkçe',
+        'uk' => 'Українська',
+        'vi' => 'Tiếng Việt',
+        'zh_CN' => '简体中文',
+        'zh_TW' => '繁體中文',
+    ]
+    //!////////////////////////////////
+];
diff --git a/resources/lang/lv/validation.php b/resources/lang/lv/validation.php
new file mode 100644 (file)
index 0000000..6de2b39
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
+return [
+
+    // Standard laravel validation lines
+    'accepted'             => ':attribute ir jāapstiprina.',
+    'active_url'           => ':attribute nav derīgs URL.',
+    'after'                => ':attribute ir jābūt datumam pēc :date.',
+    'alpha'                => ':attribute var saturēt tikai burtus.',
+    'alpha_dash'           => ':attribute var saturēt tikai burtus, ciparus, domuzīmes un apakš svītras.',
+    'alpha_num'            => ':attribute var saturēt tikai burtus un ciparus.',
+    'array'                => ':attribute ir jābūt masīvam.',
+    'before'               => ':attribute jābūt datumam pirms :date.',
+    'between'              => [
+        'numeric' => ':attribute jābūt starp :min un :max.',
+        'file'    => ':attribute jābūt starp :min un :max kilobaitiem.',
+        'string'  => ':attribute jābūt starp :min un :max rakstzīmēm.',
+        'array'   => 'Atribūtam jābūt starp: min un: max vienumiem.',
+    ],
+    'boolean'              => ':attribute jābūt True vai False.',
+    'confirmed'            => ':attribute apstiprinājums nesakrīt.',
+    'date'                 => ':attribute nav derīgs datums.',
+    'date_format'          => ':attribute neatbilst formātam :format.',
+    'different'            => ':attribute un :other jābūt atšķirīgiem.',
+    'digits'               => ':attribute jābūt :digits cipariem.',
+    'digits_between'       => ':attribute jābūt starp :min un :max cipariem.',
+    'email'                => ':attribute jābūt derīgai e-pasta adresei.',
+    'ends_with' => ':attribute jābeidzas ar vienu no :values',
+    'filled'               => ':attribute lauks ir obligāts.',
+    'gt'                   => [
+        'numeric' => ':attribute jābūt lielākam kā :value.',
+        'file'    => ':attribute jābūt lielākam kā :value kilobaitiem.',
+        'string'  => ':attribute jābūt lielākam kā :value rakstzīmēm.',
+        'array'   => ':attribute jāsatur vairāk kā :value vienības.',
+    ],
+    'gte'                  => [
+        'numeric' => ':attribute jābūt lielākam vai vienādam ar :value.',
+        'file'    => ':attribute jābūt lielākam vai vienādam ar :value kilobaitiem.',
+        'string'  => ':attribute jābūt lielākam vai vienādam ar :value rakstzīmēm.',
+        'array'   => ':attribute jāsatur :value vai vairāk vienumus.',
+    ],
+    'exists'               => 'Izvēlētais :attribute ir nederīgs.',
+    'image'                => ':attribute jābūt attēlam.',
+    'image_extension'      => ':attribute jābūt derīgam un atbalstītam bildes paplašinājumam.',
+    'in'                   => 'Iezīmētais :attribute ir nederīgs.',
+    'integer'              => ':attribute ir jābūt veselam skaitlim.',
+    'ip'                   => ':attribute jābūt derīgai IP adresei.',
+    'ipv4'                 => ':attribute jābūt derīgai IPv4 adresei.',
+    'ipv6'                 => ':attribute jābūt derīgai IPv6 adresei.',
+    'json'                 => ':attribute jābūt derīgai JSON virknei.',
+    'lt'                   => [
+        'numeric' => ':attribute jābūt mazākam par :value.',
+        'file'    => ':attribute jābūt mazāk kā :value kilobaitiem.',
+        'string'  => ':attribute jābūt mazāk kā :value rakstzīmēm.',
+        'array'   => ':attribute jāsatur mazāk kā :value vienības.',
+    ],
+    'lte'                  => [
+        'numeric' => ':attribute jābūt mazākam vai vienādam ar :value.',
+        'file'    => ':attribute jābūt mazākam vai vienādam ar :value kilobaitiem.',
+        'string'  => ':attribute jābūt mazākam vai vienādam ar :value rakstzīmēm.',
+        'array'   => ':attribute nedrīkst pārsniegt :value vienības.',
+    ],
+    'max'                  => [
+        'numeric' => ':attribute nevar būt lielāks kā :max.',
+        'file'    => ':attribute nedrīkst būt lielāks kā :max kilobaiti.',
+        'string'  => ':attribute nedrīkst būt lielāks kā :max rakstzīmēm.',
+        'array'   => ':attribute nedrīkst būt lielāks kā :max vienumi.',
+    ],
+    'mimes'                => ':attribute jābūt faila tipam: :values.',
+    'min'                  => [
+        'numeric' => ':attribute ir jābūt vismaz :min.',
+        'file'    => ':attribute jābūt vismaz :min kilobaitiem.',
+        'string'  => ':attribute ir jābūt vismaz :min rakstzīmēm.',
+        'array'   => ':attribute ir jābūt vismaz :min vienībām.',
+    ],
+    'not_in'               => 'Izvēlētais: atribūts ir nederīgs.',
+    'not_regex'            => ':attribute formāts nav derīgs.',
+    'numeric'              => ':attribute ir jābūt skaitlim.',
+    'regex'                => ':attribute formāts nav derīgs.',
+    'required'             => ':attribute lauks ir obligāts.',
+    'required_if'          => ':attribute lauks ir nepieciešams, kad :other ir :value.',
+    'required_with'        => ':attribute lauks ir obligāts, ja ir :values.',
+    'required_with_all'    => ':attribute lauks ir obligāts, ja ir :values.',
+    'required_without'     => ':attribute lauks ir obligāts, ja nav :values.',
+    'required_without_all' => ':attribute lauks ir obligāts, ja nav neviena no :values.',
+    'same'                 => ':attribute un :other jāsakrīt.',
+    'safe_url'             => 'Norādītā saite var būt nedroša.',
+    'size'                 => [
+        'numeric' => ':attribute ir jābūt :size.',
+        'file'    => ':attribute jābūt :size kilobaiti.',
+        'string'  => ':attribute jābūt :size rakstzīmēm.',
+        'array'   => ':attribute jāsatur :size vienības.',
+    ],
+    'string'               => ':attribute jābūt teksta virknei.',
+    'timezone'             => ':attribute jābūt derīgai zonai.',
+    'unique'               => ':attribute jau ir aizņemts.',
+    'url'                  => ':attribute formāts nav derīgs.',
+    'uploaded'             => 'Fails netika ielādēts. Serveris nevar pieņemt šāda izmēra failus.',
+
+    // Custom validation lines
+    'custom' => [
+        'password-confirm' => [
+            'required_with' => 'Nepieciešams paroles apstiprinājums',
+        ],
+    ],
+
+    // Custom validation attributes
+    'attributes' => [],
+];
index e2516ab7492b360e345747f80ed39b551b0cd6e8..bf23393abf464f531cd1f7e6f97a7c11ab6bb13f 100644 (file)
@@ -46,5 +46,5 @@ return [
 
     // Other
     'commented_on'                => 'kommenterte på',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => 'oppdaterte tilganger',
 ];
index 5fab437876af7ef88cf5a428604fae0ad24f98eb..6d87e54395d180bdfae80bb3c9764815c515081f 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Stigende sortering',
     'sort_descending' => 'Synkende sortering',
     'sort_name' => 'Navn',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Dato opprettet',
     'sort_updated_at' => 'Dato oppdatert',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Brødsmuler',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profilmeny',
     'view_profile' => 'Vis profil',
     'edit_profile' => 'Endre Profile',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informasjon',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Innhold',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Om du har problemer med å trykke på «:actionText»-knappen, bruk nettadressen under for å gå direkte dit:',
     'email_rights' => 'Kopibeskyttet',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index dfee93e1f16f685f2fc0d736cb27d65fc6cd230f..9ce2d3cd14f0ac0fb25195b06e13d092b65819e7 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Opprettet :timeLength av :user',
     'meta_updated' => 'Oppdatert :timeLength',
     'meta_updated_name' => 'Oppdatert :timeLength av :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Eies av :user',
     'entity_select' => 'Velg entitet',
     'images' => 'Bilder',
     'my_recent_drafts' => 'Mine nylige utkast',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Når disse er tillatt, vil disse tillatelsene ha prioritet over alle angitte rolletillatelser.',
     'permissions_enable' => 'Aktiver egendefinerte tillatelser',
     'permissions_save' => 'Lagre tillatelser',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Eier',
 
     // Search
     'search_results' => 'Søkeresultater',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Tilganger er angitt',
     'search_created_by_me' => 'Opprettet av meg',
     'search_updated_by_me' => 'Oppdatert av meg',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Datoalternativer',
     'search_updated_before' => 'Oppdatert før',
     'search_updated_after' => 'Oppdatert etter',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Sidens revisjoner',
     'pages_revisions_named' => 'Revisjoner for :pageName',
     'pages_revision_named' => 'Revisjoner for :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Gjenopprettet fra #:id; :summary',
     'pages_revisions_created_by' => 'Skrevet av',
     'pages_revisions_date' => 'Revideringsdato',
     'pages_revisions_number' => '#',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Vil du gjenopprette revisjonen? Innholdet på siden vil bli overskrevet med denne revisjonen.',
     'revision_delete_success' => 'Revisjonen ble slettet',
     'revision_cannot_delete_latest' => 'CKan ikke slette siste revisjon.'
-];
\ No newline at end of file
+];
index 4e5c07f65e7a5e5a255cd3d41a92c484a75594a5..971dbf1cadb2a3302c6b9a359ad2b745330933d0 100644 (file)
@@ -28,7 +28,7 @@ return [
     'social_account_in_use' => 'Denne :socialAccount kontoen er allerede registrert, Prøv å logge inn med :socialAccount alternativet.',
     'social_account_email_in_use' => 'E-posten :email er allerede i bruk. Har du allerede en konto hos :socialAccount kan dette angis fra profilsiden din.',
     'social_account_existing' => 'Denne :socialAccount er allerede koblet til din konto.',
-    'social_account_already_used_existing' => 'This :socialAccount account is already used by another user.',
+    'social_account_already_used_existing' => 'Denne :socialAccount kontoen brukes allerede av noen andre.',
     'social_account_not_used' => 'Denne :socialAccount konten er ikke koblet til noen konto, angi denne i profilinnstillingene dine. ',
     'social_account_register_instructions' => 'Har du ikke en konto her ennå, kan du benytte :socialAccount alternativet for å registrere deg.',
     'social_driver_not_found' => 'Autentiseringstjeneste fra sosiale medier er ikke installert',
@@ -83,6 +83,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' => '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' => 'Gå til hovedside',
     'error_occurred' => 'En feil oppsto',
     'app_down' => ':appName er nede for øyeblikket',
index e952225dd294c7a20e86341b8ace75ffc6abec96..4bd35776d59e3b130abab83cfc802ca90dc60e70 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Applikasjonens hjemmeside',
     'app_homepage_desc' => 'Velg en visning som skal vises på hjemmesiden i stedet for standardvisningen. Sidetillatelser ignoreres for utvalgte sider.',
     'app_homepage_select' => 'Velg en side',
+    '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::<key>" 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' => 'Deaktiver kommentarer',
     'app_disable_comments_toggle' => 'Deaktiver kommentarer',
     'app_disable_comments_desc' => 'Deaktiver kommentarer på tvers av alle sidene i applikasjonen. <br> Eksisterende kommentarer vises ikke.',
@@ -65,10 +70,10 @@ return [
     'reg_confirm_restrict_domain_placeholder' => 'Ingen begrensninger er satt',
 
     // Maintenance settings
-    'maint' => 'Maintenance',
+    '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_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    '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?',
     'maint_image_cleanup_success' => ':count potensielt ubrukte bilder funnet og slettet!',
@@ -80,27 +85,27 @@ return [
     'maint_send_test_email_mail_subject' => 'Test-e-post',
     'maint_send_test_email_mail_greeting' => 'E-postsending ser ut til å fungere!',
     'maint_send_test_email_mail_text' => 'Gratulerer! Da du mottok dette e-postvarselet, ser det ut til at e-postinnstillingene dine er konfigurert riktig.',
-    '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_recycle_bin_desc' => 'Slettede hyller, bøker, kapitler og sider kastes i papirkurven så de kan bli gjenopprettet eller slettet permanent. Eldre utgaver i papirkurven kan slettes automatisk etter en stund, avhengig av systemkonfigurasjonen.',
+    'maint_recycle_bin_open' => 'Åpne papirkurven',
 
     // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin' => 'Papirkurven',
+    'recycle_bin_desc' => 'Her kan du gjenopprette ting du har kastet i papirkurven eller velge å slette dem permanent fra systemet. Denne listen er ikke filtrert i motsetning til lignende lister i systemet hvor tilgangskontroll overholdes.',
+    'recycle_bin_deleted_item' => 'Kastet element',
+    'recycle_bin_deleted_by' => 'Kastet av',
+    'recycle_bin_deleted_at' => 'Kastet den',
+    'recycle_bin_permanently_delete' => 'Slett permanent',
+    'recycle_bin_restore' => 'Gjenopprett',
+    '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_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.',
+    'recycle_bin_restore_deleted_parent' => 'Det overordnede elementet var også kastet i papirkurven. Disse elementene vil forbli kastet inntil det overordnede også hentes opp igjen.',
+    'recycle_bin_destroy_notification' => 'Slettet :count elementer fra papirkurven.',
+    'recycle_bin_restore_notification' => 'Gjenopprettet :count elementer fra papirkurven.',
 
     // Audit Log
     'audit' => 'Revisjonslogg',
@@ -111,7 +116,7 @@ return [
     'audit_deleted_item_name' => 'Navn: :name',
     'audit_table_user' => 'Kontoholder',
     'audit_table_event' => 'Hendelse',
-    'audit_table_related' => 'Related Item or Detail',
+    'audit_table_related' => 'Relaterte elementer eller detaljer',
     'audit_table_date' => 'Aktivitetsdato',
     'audit_date_from' => 'Datoperiode fra',
     'audit_date_to' => 'Datoperiode til',
@@ -153,11 +158,11 @@ return [
     'role_users_none' => 'Ingen kontoholdere er gitt denne rollen',
 
     // Users
-    'users' => 'Users',
+    'users' => 'Brukere',
     'user_profile' => 'Profil',
     'users_add_new' => 'Register ny konto',
     'users_search' => 'Søk i kontoer',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Siste aktivitet',
     'users_details' => 'Kontodetaljer',
     'users_details_desc' => 'Angi et visningsnavn og en e-postadresse for denne kontoholderen. E-postadressen vil bli brukt til å logge på applikasjonen.',
     'users_details_desc_no_email' => 'Angi et visningsnavn for denne kontoholderen slik at andre kan gjenkjenne dem.',
@@ -175,9 +180,9 @@ return [
     'users_delete_named' => 'Slett kontoen :userName',
     'users_delete_warning' => 'Dette vil fullstendig slette denne brukeren med navnet «:userName» fra systemet.',
     'users_delete_confirm' => 'Er du sikker på at du vil slette denne kontoen?',
-    '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_migrate_ownership' => 'Overfør eierskap',
+    'users_migrate_ownership_desc' => 'Velg en bruker her, som du ønsker skal ta eierskap over alle elementene som er eid av denne brukeren.',
+    'users_none_selected' => 'Ingen bruker valgt',
     'users_delete_success' => 'Konto slettet',
     'users_edit' => 'Rediger konto',
     'users_edit_profile' => 'Rediger profil',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 87e6c7529615f748901d2ce17ba225e22a7901e4..d06240c7cef1dd11337d66b962062089fe628c08 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute må være på minst :min tegn.',
         'array'   => ':attribute må minst ha :min ting.',
     ],
-    'no_double_extension'  => ':attribute kan bare ha en formattype spesifisert.',
     'not_in'               => 'Den valgte :attribute er ugyldig.',
     'not_regex'            => ':attribute format er ugyldig.',
     'numeric'              => ':attribute må være et nummer.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => ':attribute feltet er påkrevt når :values ikke er tilgjengelig.',
     'required_without_all' => ':attribute feltet er påkrevt når ingen av :values er tilgjengelig.',
     'same'                 => ':attribute og :other må samsvare.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => 'Den angitte lenken kan være farlig.',
     'size'                 => [
         'numeric' => ':attribute må være :size.',
         'file'    => ':attribute må være :size kilobytes.',
index 634ea1202f8837eeb83474c888ad0cc53c9fd633..f8fb2c34539e44df97391581b338afaad0dc8114 100644 (file)
@@ -7,43 +7,43 @@ return [
 
     // Pages
     'page_create'                 => 'maakte pagina',
-    'page_create_notification'    => 'Pagina Succesvol Aangemaakt',
-    'page_update'                 => 'veranderde pagina',
-    'page_update_notification'    => 'Pagina Succesvol Bijgewerkt',
+    'page_create_notification'    => 'Pagina succesvol aangemaakt',
+    'page_update'                 => 'wijzigde pagina',
+    'page_update_notification'    => 'Pagina succesvol bijgewerkt',
     'page_delete'                 => 'verwijderde pagina',
-    'page_delete_notification'    => 'Pagina Succesvol Verwijderd',
+    'page_delete_notification'    => 'Pagina succesvol verwijderd',
     'page_restore'                => 'herstelde pagina',
-    'page_restore_notification'   => 'Pagina Succesvol Hersteld',
+    'page_restore_notification'   => 'Pagina succesvol hersteld',
     'page_move'                   => 'verplaatste pagina',
 
     // Chapters
     'chapter_create'              => 'maakte hoofdstuk',
-    'chapter_create_notification' => 'Hoofdstuk Succesvol Aangemaakt',
-    'chapter_update'              => 'veranderde hoofdstuk',
-    'chapter_update_notification' => 'Hoofdstuk Succesvol Bijgewerkt',
+    'chapter_create_notification' => 'Hoofdstuk succesvol aangemaakt',
+    'chapter_update'              => 'wijzigde hoofdstuk',
+    'chapter_update_notification' => 'Hoofdstuk succesvol bijgewerkt',
     'chapter_delete'              => 'verwijderde hoofdstuk',
-    'chapter_delete_notification' => 'Hoofdstuk Succesvol Verwijderd',
+    'chapter_delete_notification' => 'Hoofdstuk succesvol verwijderd',
     'chapter_move'                => 'verplaatste hoofdstuk',
 
     // Books
     'book_create'                 => 'maakte boek',
-    'book_create_notification'    => 'Boek Succesvol Aangemaakt',
-    'book_update'                 => 'veranderde boek',
-    'book_update_notification'    => 'Boek Succesvol Bijgewerkt',
+    'book_create_notification'    => 'Boek succesvol aangemaakt',
+    'book_update'                 => 'wijzigde boek',
+    'book_update_notification'    => 'Boek succesvol bijgewerkt',
     'book_delete'                 => 'verwijderde boek',
-    'book_delete_notification'    => 'Boek Succesvol Verwijderd',
+    'book_delete_notification'    => 'Boek succesvol verwijderd',
     'book_sort'                   => 'sorteerde boek',
-    'book_sort_notification'      => 'Boek Succesvol Gesorteerd',
+    'book_sort_notification'      => 'Boek succesvol gesorteerd',
 
     // Bookshelves
-    'bookshelf_create'            => 'maakte Boekenplank',
-    'bookshelf_create_notification'    => 'Boekenplank Succesvol Aangemaakt',
-    'bookshelf_update'                 => 'veranderde boekenplank',
-    'bookshelf_update_notification'    => 'Boekenplank Succesvol Bijgewerkt',
+    'bookshelf_create'            => 'maakte boekenplank',
+    'bookshelf_create_notification'    => 'Boekenplank succesvol aangemaakt',
+    'bookshelf_update'                 => 'wijzigde boekenplank',
+    'bookshelf_update_notification'    => 'Boekenplank succesvol bijgewerkt',
     'bookshelf_delete'                 => 'verwijderde boekenplank',
-    'bookshelf_delete_notification'    => 'Boekenplank Succesvol Verwijderd',
+    'bookshelf_delete_notification'    => 'Boekenplank succesvol verwijderd',
 
     // Other
-    'commented_on'                => 'reactie op',
-    'permissions_update'          => 'updated permissions',
+    'commented_on'                => 'reageerde op',
+    'permissions_update'          => 'wijzigde permissies',
 ];
index ce0be87edfb2b201e640669591df021cfea536f7..fcff3fd48f0a5b8c4bdf4bbb29595c86dabe0b18 100644 (file)
@@ -7,7 +7,7 @@
 return [
 
     'failed' => 'Deze inloggegevens zijn niet bij ons bekend.',
-    'throttle' => 'Te veel loginpogingen! Probeer het opnieuw na :seconds seconden.',
+    'throttle' => 'Te veel login pogingen! Probeer het opnieuw na :seconds seconden.',
 
     // Login & Register
     'sign_up' => 'Registreren',
@@ -20,34 +20,34 @@ return [
     'username' => 'Gebruikersnaam',
     'email' => 'E-mail',
     'password' => 'Wachtwoord',
-    'password_confirm' => 'Wachtwoord Bevestigen',
+    'password_confirm' => 'Wachtwoord bevestigen',
     'password_hint' => 'Minimaal 8 tekens',
     'forgot_password' => 'Wachtwoord vergeten?',
     'remember_me' => 'Mij onthouden',
-    'ldap_email_hint' => 'Geef een email op waarmee je dit account wilt gebruiken.',
-    'create_account' => 'Account Aanmaken',
+    'ldap_email_hint' => 'Geef een emailadres op voor dit account.',
+    'create_account' => 'Account aanmaken',
     'already_have_account' => 'Heb je al een account?',
     'dont_have_account' => 'Nog geen account?',
     'social_login' => 'Aanmelden via een sociaal netwerk',
-    'social_registration' => 'Social Registratie',
-    'social_registration_text' => 'Registreer en log in met een andere dienst.',
+    'social_registration' => 'Social registratie',
+    'social_registration_text' => 'Registreer en log in met een andere service.',
 
     'register_thanks' => 'Bedankt voor het registreren!',
     'register_confirm' => 'Controleer je e-mail en bevestig je registratie om in te loggen op :appName.',
     'registrations_disabled' => 'Registratie is momenteel niet mogelijk',
     'registration_email_domain_invalid' => 'Dit e-maildomein is niet toegestaan',
-    'register_success' => 'Bedankt voor het inloggen. Je bent ook geregistreerd.',
+    'register_success' => 'Bedankt voor het aanmelden! Je bent nu geregistreerd en aangemeld.',
 
 
     // Password Reset
-    'reset_password' => 'Wachtwoord Herstellen',
+    'reset_password' => 'Wachtwoord herstellen',
     'reset_password_send_instructions' => 'Geef je e-mail en we sturen je een link om je wachtwoord te herstellen',
-    'reset_password_send_button' => 'Link Sturen',
+    'reset_password_send_button' => 'Link sturen',
     'reset_password_sent' => 'Een link om het wachtwoord te resetten zal verstuurd worden naar :email als dat e-mailadres in het systeem gevonden is.',
     'reset_password_success' => 'Je wachtwoord is succesvol hersteld.',
     'email_reset_subject' => 'Herstel je wachtwoord van :appName',
-    'email_reset_text' => 'Je ontvangt deze e-mail zodat je je wachtwoord kunt herstellen.',
-    'email_reset_not_requested' => 'Als je jouw wachtwoord niet wilt wijzigen, doe dan niets.',
+    'email_reset_text' => 'Je ontvangt deze e-mail omdat je een wachtwoord herstel verzoek had verzonden.',
+    'email_reset_not_requested' => 'Als je geen wachtwoord herstel hebt aangevraagd, hoef je niets te doen.',
 
 
     // Email Confirmation
@@ -56,14 +56,14 @@ return [
     'email_confirm_text' => 'Bevestig je registratie door op onderstaande knop te drukken:',
     'email_confirm_action' => 'Bevestig je e-mail',
     'email_confirm_send_error' => 'E-mail bevestiging is vereisd maar het systeem kon geen mail verzenden. Neem contact op met de beheerder.',
-    'email_confirm_success' => 'Je e-mailadres is bevestigt!',
+    'email_confirm_success' => 'Je e-mailadres is bevestigd!',
     'email_confirm_resent' => 'De bevestigingse-mails is opnieuw verzonden. Controleer je inbox.',
 
-    'email_not_confirmed' => 'E-mail nog niet bevestigd',
+    'email_not_confirmed' => 'E-mailadres nog niet bevestigd',
     'email_not_confirmed_text' => 'Je e-mailadres is nog niet bevestigd.',
     'email_not_confirmed_click_link' => 'Klik op de link in de e-mail die vlak na je registratie is verstuurd.',
     'email_not_confirmed_resend' => 'Als je deze e-mail niet kunt vinden kun je deze met onderstaande formulier opnieuw verzenden.',
-    'email_not_confirmed_resend_button' => 'Bevestigingsmail Opnieuw Verzenden',
+    'email_not_confirmed_resend_button' => 'Bevestigingsmail opnieuw verzenden',
 
     // User Invite
     'user_invite_email_subject' => 'Je bent uitgenodigd voor :appName!',
index 7a2d633c65e61b2de0204f35f13dbf384351eb15..cfdc1a1a5f7046a538803c640f1dcc98af237441 100644 (file)
@@ -11,7 +11,7 @@ return [
     'save' => 'Opslaan',
     'continue' => 'Doorgaan',
     'select' => 'Kies',
-    'toggle_all' => 'Toggle Alles',
+    'toggle_all' => 'Toggle alles',
     'more' => 'Meer',
 
     // Form Labels
@@ -19,12 +19,12 @@ return [
     'description' => 'Beschrijving',
     'role' => 'Rol',
     'cover_image' => 'Omslagfoto',
-    'cover_image_description' => 'Deze afbeelding moet ongeveer 300x170px zijn.',
+    'cover_image_description' => 'Deze afbeelding moet ongeveer 440x250px zijn.',
     
     // Actions
     'actions' => 'Acties',
     'view' => 'Bekijk',
-    'view_all' => 'Bekijk Alle',
+    'view_all' => 'Bekijk alle',
     'create' => 'Aanmaken',
     'update' => 'Bijwerken',
     'edit' => 'Bewerk',
@@ -33,7 +33,7 @@ return [
     'copy' => 'Kopiëren',
     'reply' => 'Beantwoorden',
     'delete' => 'Verwijder',
-    'delete_confirm' => 'Confirm Deletion',
+    'delete_confirm' => 'Verwijdering bevestigen',
     'search' => 'Zoek',
     'search_clear' => 'Zoekopdracht wissen',
     'reset' => 'Resetten',
@@ -43,38 +43,47 @@ return [
 
     // Sort Options
     'sort_options' => 'Sorteeropties',
-    'sort_direction_toggle' => 'Sorteer richting',
+    'sort_direction_toggle' => 'Sorteerrichting',
     'sort_ascending' => 'Sorteer oplopend',
-    'sort_descending' => 'Sorteer teruglopend',
+    'sort_descending' => 'Sorteer aflopend',
     'sort_name' => 'Naam',
+    'sort_default' => 'Standaard',
     'sort_created_at' => 'Aanmaakdatum',
     'sort_updated_at' => 'Gewijzigd op',
 
     // Misc
     'deleted_user' => 'Verwijderde gebruiker',
-    'no_activity' => 'Geen activiteiten',
+    'no_activity' => 'Geen activiteit om weer te geven',
     'no_items' => 'Geen items beschikbaar',
     'back_to_top' => 'Terug naar boven',
-    'toggle_details' => 'Details Weergeven',
-    'toggle_thumbnails' => 'Thumbnails Weergeven',
+    'toggle_details' => 'Details weergeven',
+    'toggle_thumbnails' => 'Thumbnails weergeven',
     'details' => 'Details',
     'grid_view' => 'Grid weergave',
-    'list_view' => 'Lijst weergave',
+    'list_view' => 'Lijstweergave',
     'default' => 'Standaard',
     'breadcrumb' => 'Kruimelpad',
 
     // Header
+    'header_menu_expand' => 'Header menu uitvouwen',
     'profile_menu' => 'Profiel menu',
-    'view_profile' => 'Profiel Weergeven',
-    'edit_profile' => 'Profiel Bewerken',
-    'dark_mode' => 'Donkere Modus',
-    'light_mode' => 'Lichte Modus',
+    'view_profile' => 'Profiel weergeven',
+    'edit_profile' => 'Profiel bewerken',
+    'dark_mode' => 'Donkere modus',
+    'light_mode' => 'Lichte modus',
 
     // Layout tabs
     'tab_info' => 'Info',
+    'tab_info_label' => 'Tabblad: Toon secundaire informatie',
     'tab_content' => 'Inhoud',
+    'tab_content_label' => 'Tabblad: Toon primaire inhoud',
 
     // Email Content
-    'email_action_help' => 'Als je de knop ":actionText" niet werkt, kopieer en plak de onderstaande URL in je web browser:',
+    'email_action_help' => 'Als je de knop ":actionText" niet werkt, kopieër en plak de onderstaande URL in je web browser:',
     'email_rights' => 'Alle rechten voorbehouden',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacybeleid',
+    'terms_of_service' => 'Algemene voorwaarden',
 ];
index f40ac15b0f4c23342b6a245d7328b936ee4ce133..0cddef7c79cc8dc435dc94b177e80d2ad534a6dd 100644 (file)
@@ -5,21 +5,21 @@
 return [
 
     // Image Manager
-    'image_select' => 'Selecteer Afbeelding',
+    'image_select' => 'Selecteer afbeelding',
     'image_all' => 'Alles',
     'image_all_title' => 'Alle afbeeldingen weergeven',
     'image_book_title' => 'Afbeeldingen van dit boek weergeven',
     'image_page_title' => 'Afbeeldingen van deze pagina weergeven',
     'image_search_hint' => 'Zoek op afbeeldingsnaam',
     'image_uploaded' => 'Geüpload :uploadedDate',
-    'image_load_more' => 'Meer Laden',
+    'image_load_more' => 'Meer laden',
     'image_image_name' => 'Afbeeldingsnaam',
     'image_delete_used' => 'Deze afbeeldingen is op onderstaande pagina\'s in gebruik.',
     'image_delete_confirm_text' => 'Weet u zeker dat u deze afbeelding wilt verwijderen?',
-    'image_select_image' => 'Kies Afbeelding',
+    'image_select_image' => 'Kies afbeelding',
     'image_dropzone' => 'Sleep afbeeldingen hier of klik hier om te uploaden',
-    'images_deleted' => 'Verwijderde Afbeeldingen',
-    'image_preview' => 'Afbeelding Voorbeeld',
+    'images_deleted' => 'Verwijderde afbeeldingen',
+    'image_preview' => 'Afbeelding voorbeeld',
     'image_upload_success' => 'Afbeelding succesvol geüpload',
     'image_update_success' => 'Afbeeldingsdetails succesvol verwijderd',
     'image_delete_success' => 'Afbeelding succesvol verwijderd',
@@ -27,8 +27,8 @@ return [
 
     // Code Editor
     'code_editor' => 'Code invoegen',
-    'code_language' => 'Code taal',
+    'code_language' => 'Codetaal',
     'code_content' => 'Code',
-    'code_session_history' => 'Zittingsgeschiedenis',
+    'code_session_history' => 'Sessie geschiedenis',
     'code_save' => 'Sla code op',
 ];
index 0caab99f09afba113fd1804051a9d60bd399f162..aac4863cbddf33542b8845db7baac5212471d02d 100644 (file)
@@ -6,7 +6,7 @@
 return [
 
     // Shared
-    'recently_created' => 'Recent Aangemaakt',
+    'recently_created' => 'Recent aangemaakt',
     'recently_created_pages' => 'Recent Aangemaakte Pagina\'s',
     'recently_updated_pages' => 'Recent Bijgewerkte Pagina\'s',
     'recently_created_chapters' => 'Recent Aangemaakte Hoofdstukken',
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Aangemaakt: :timeLength door :user',
     'meta_updated' => ':timeLength Aangepast',
     'meta_updated_name' => 'Aangepast: :timeLength door :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Eigendom van :user',
     'entity_select' => 'Entiteit Selecteren',
     'images' => 'Afbeeldingen',
     'my_recent_drafts' => 'Mijn Concepten',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Als je dit aanzet, dan gelden rol-permissies niet meer voor deze pagina.',
     'permissions_enable' => 'Custom Permissies Aanzetten',
     'permissions_save' => 'Permissies Opslaan',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Eigenaar',
 
     // Search
     'search_results' => 'Zoekresultaten',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Permissies gezet',
     'search_created_by_me' => 'Door mij gemaakt',
     'search_updated_by_me' => 'Door mij geupdate',
+    'search_owned_by_me' => 'Eigendom van mij',
     'search_date_options' => 'Datum Opties',
     'search_updated_before' => 'Geupdate voor',
     'search_updated_after' => 'Geupdate na',
@@ -74,10 +75,10 @@ return [
     'x_shelves' => ':count Boekenplank|:count Boekenplanken',
     'shelves_long' => 'Boekenplanken',
     'shelves_empty' => 'Er zijn geen boekenplanken aangemaakt',
-    'shelves_create' => 'Nieuwe Boekenplank Aanmaken',
+    'shelves_create' => 'Nieuwe boekenplank maken',
     'shelves_popular' => 'Populaire Boekenplanken',
-    'shelves_new' => 'Nieuwe Boekenplanken',
-    'shelves_new_action' => 'Nieuwe Boekplank',
+    '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 aangemaakt boekenplanken worden hier weergeven.',
     'shelves_save' => 'Boekenplanken Opslaan',
@@ -107,11 +108,11 @@ return [
     'books_empty' => 'Er zijn geen boeken aangemaakt',
     'books_popular' => 'Populaire Boeken',
     'books_recent' => 'Recente Boeken',
-    'books_new' => 'Nieuwe Boeken',
-    'books_new_action' => 'Nieuw Boek',
+    '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_create' => 'Nieuw Boek Aanmaken',
+    'books_create' => 'Nieuw boek maken',
     'books_delete' => 'Boek Verwijderen',
     'books_delete_named' => 'Verwijder Boek :bookName',
     'books_delete_explain' => 'Deze actie verwijdert het boek \':bookName\', Alle pagina\'s en hoofdstukken worden verwijderd.',
@@ -123,7 +124,7 @@ return [
     'books_permissions' => 'Boek Permissies',
     'books_permissions_updated' => 'Boek Permissies Opgeslagen',
     'books_empty_contents' => 'Er zijn nog een hoofdstukken en pagina\'s voor dit boek gemaakt.',
-    'books_empty_create_page' => 'Pagina Toevoegen',
+    'books_empty_create_page' => 'Nieuwe pagina maken',
     'books_empty_sort_current_book' => 'Boek sorteren',
     'books_empty_add_chapter' => 'Hoofdstuk Toevoegen',
     'books_permissions_active' => 'Boek Permissies Actief',
@@ -137,18 +138,18 @@ return [
     'books_sort_chapters_first' => 'Hoofdstukken eerst',
     'books_sort_chapters_last' => 'Hoofdstukken Laatst',
     'books_sort_show_other' => 'Bekijk Andere Boeken',
-    'books_sort_save' => 'Nieuwe Order Opslaan',
+    'books_sort_save' => 'Nieuwe volgorde opslaan',
 
     // Chapters
     'chapter' => 'Hoofdstuk',
     'chapters' => 'Hoofdstukken',
     'x_chapters' => ':count Hoofdstuk|:count Hoofdstukken',
     'chapters_popular' => 'Populaire Hoofdstukken',
-    'chapters_new' => 'Nieuw Hoofdstuk',
-    'chapters_create' => 'Hoofdstuk Toevoegen',
+    'chapters_new' => 'Nieuw hoofdstuk',
+    'chapters_create' => 'Nieuw hoofdstuk maken',
     'chapters_delete' => 'Hoofdstuk Verwijderen',
     'chapters_delete_named' => 'Verwijder Hoofdstuk :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_explain' => 'Dit verwijdert het hoofdstuk met de naam \':chapterName\'. Alle pagina\'s die binnen dit hoofdstuk staan, worden ook verwijderd.',
     'chapters_delete_confirm' => 'Weet je zeker dat je dit boek wilt verwijderen?',
     'chapters_edit' => 'Hoofdstuk Aanpassen',
     'chapters_edit_named' => 'Hoofdstuk :chapterName Aanpassen',
@@ -167,7 +168,7 @@ return [
     'pages' => 'Pagina\'s',
     'x_pages' => ':count Pagina|:count Pagina\'s',
     'pages_popular' => 'Populaire Pagina\'s',
-    'pages_new' => 'Nieuwe Pagina',
+    'pages_new' => 'Nieuwe pagina',
     'pages_attachments' => 'Bijlages',
     'pages_navigation' => 'Pagina Navigatie',
     'pages_delete' => 'Pagina Verwijderen',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Pagina Revisies',
     'pages_revisions_named' => 'Pagina Revisies voor :pageName',
     'pages_revision_named' => 'Pagina Revisie voor :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Hersteld van #:id; :samenvatting',
     'pages_revisions_created_by' => 'Aangemaakt door',
     'pages_revisions_date' => 'Revisiedatum',
     'pages_revisions_number' => '#',
@@ -226,7 +227,7 @@ return [
     'pages_edit_content_link' => 'Bewerk inhoud',
     'pages_permissions_active' => 'Pagina Permissies Actief',
     'pages_initial_revision' => 'Eerste publicatie',
-    'pages_initial_name' => 'Nieuwe Pagina',
+    '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_edit_active' => [
@@ -268,7 +269,7 @@ return [
     'attachments_link_url' => 'Link naar bestand',
     'attachments_link_url_hint' => 'Url, site of bestand',
     'attach' => 'Koppelen',
-    'attachments_insert_link' => 'Add Attachment Link to Page',
+    'attachments_insert_link' => 'Bijlage link toevoegen aan pagina',
     'attachments_edit_file' => 'Bestand Bewerken',
     'attachments_edit_file_name' => 'Bestandsnaam',
     'attachments_edit_drop_upload' => 'Sleep een bestand of klik hier om te uploaden en te overschrijven',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Weet u zeker dat u deze revisie wilt herstellen? De huidige pagina-inhoud wordt vervangen.',
     'revision_delete_success' => 'Revisie verwijderd',
     'revision_cannot_delete_latest' => 'Kan de laatste revisie niet verwijderen.'
-];
\ No newline at end of file
+];
index 585346748a8825958efd06396f6f9af8048c257c..323115b5261c3f44270c1d64ec4e69d031ed3e43 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Pagina Niet Gevonden',
     'sorry_page_not_found' => 'Sorry, de pagina die je zocht is niet beschikbaar.',
     'sorry_page_not_found_permission_warning' => 'Als u verwacht dat deze pagina bestaat heeft u misschien geen rechten om het te bekijken.',
+    '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' => 'Terug naar home',
     'error_occurred' => 'Er Ging Iets Fout',
     'app_down' => ':appName is nu niet beschikbaar',
index 12f3150a0d2b0e2f06f84896f802ab29ace08f9e..3efc20f84d22e6895a012246a8fc0ccddacd849d 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Applicatie Homepagina',
     'app_homepage_desc' => 'Selecteer een weergave om weer te geven op de homepage in plaats van de standaard weergave. Paginarechten worden genegeerd voor geselecteerde pagina\'s.',
     'app_homepage_select' => 'Selecteer een pagina',
+    'app_footer_links' => 'Voettekst links',
+    'app_footer_links_desc' => 'Voeg links toe om te laten zien in de voettekst van de site. Deze worden onderaan de meeste pagina\'s weergegeven, met inbegrip van pagina\'s die geen inloggen vereisen. U kunt een label van "trans::<key>" gebruiken om systeemgedefinieerde vertalingen te gebruiken. Bijvoorbeeld: Het gebruik van "trans:common.privacy_policy" biedt de vertaalde tekst "Privacybeleid" en "trans:common.terms_of_service" voor de vertaalde tekst "Servicevoorwaarden".',
+    'app_footer_links_label' => 'Link label',
+    'app_footer_links_url' => 'Link URL',
+    'app_footer_links_add' => 'Voettekst link toevoegen',
     'app_disable_comments' => 'Reacties uitschakelen',
     'app_disable_comments_toggle' => 'Opmerkingen uitschakelen',
     'app_disable_comments_desc' => 'Schakel opmerkingen uit op alle pagina\'s in de applicatie. Bestaande opmerkingen worden niet getoond.',
@@ -68,7 +73,7 @@ return [
     'maint' => 'Onderhoud',
     'maint_image_cleanup' => 'Afbeeldingen opschonen',
     'maint_image_cleanup_desc' => "Scant pagina- en revisie inhoud om te controleren welke afbeeldingen en tekeningen momenteel worden gebruikt en welke afbeeldingen overbodig zijn. Zorg ervoor dat je een volledige database en afbeelding backup maakt voordat je dit uitvoert.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Ook afbeeldingen die alleen in oude pagina revisies bestaan verwijderen',
     'maint_image_cleanup_run' => 'Opschonen uitvoeren',
     'maint_image_cleanup_warning' => ':count potentieel ongebruikte afbeeldingen gevonden. Weet u zeker dat u deze afbeeldingen wilt verwijderen?',
     'maint_image_cleanup_success' => ':count potentieel ongebruikte afbeeldingen gevonden en verwijderd!',
@@ -80,41 +85,41 @@ return [
     'maint_send_test_email_mail_subject' => 'Test E-mail',
     'maint_send_test_email_mail_greeting' => 'E-mailbezorging lijkt te werken!',
     'maint_send_test_email_mail_text' => 'Gefeliciteerd! Nu je deze e-mailmelding hebt ontvangen, lijken je e-mailinstellingen correct te zijn geconfigureerd.',
-    '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_recycle_bin_desc' => 'Verwijderde planken, boeken, hoofdstukken en pagina\'s worden naar de prullenbak gestuurd om ze te herstellen of definitief te verwijderen. Oudere items in de prullenbak kunnen automatisch worden verwijderd, afhankelijk van de systeemconfiguratie.',
+    'maint_recycle_bin_open' => 'Prullenbak openen',
 
     // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin' => 'Prullenbak',
+    'recycle_bin_desc' => 'Hier kunt u items herstellen die zijn verwijderd of kiezen om ze permanent te verwijderen uit het systeem. Deze lijst is niet gefilterd, in tegenstelling tot vergelijkbare activiteitenlijsten in het systeem waar rechtenfilters worden toegepast.',
+    'recycle_bin_deleted_item' => 'Verwijderde Item',
+    'recycle_bin_deleted_by' => 'Verwijderd door',
+    'recycle_bin_deleted_at' => 'Verwijdert op',
+    'recycle_bin_permanently_delete' => 'Permanent verwijderen',
+    'recycle_bin_restore' => 'Herstellen',
+    '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 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_list' => 'Te vernietigen objecten',
+    '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.',
+    'recycle_bin_restore_deleted_parent' => 'De bovenliggende map van dit item is ook verwijderd. Deze zal worden verwijderd totdat het bovenliggende item ook is hersteld.',
+    'recycle_bin_destroy_notification' => 'Verwijderde totaal :count items uit de prullenbak.',
+    'recycle_bin_restore_notification' => 'Herstelde totaal :count items uit de prullenbak.',
 
     // 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_date' => 'Activity Date',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit_desc' => 'Dit auditlogboek toont een lijst met activiteiten die in het systeem zijn gedaan. Deze lijst is niet gefilterd, in tegenstelling tot vergelijkbare activiteitenlijsten in het systeem waar rechtenfilters worden toegepast.',
+    'audit_event_filter' => 'Gebeurtenis filter',
+    'audit_event_filter_no_filter' => 'Geen filter',
+    'audit_deleted_item' => 'Verwijderd Item',
+    'audit_deleted_item_name' => 'Naam: :name',
+    'audit_table_user' => 'Gebruiker',
+    'audit_table_event' => 'Gebeurtenis',
+    'audit_table_related' => 'Gerelateerd Item of Detail',
+    'audit_table_date' => 'Activiteit datum',
+    'audit_date_from' => 'Datum bereik vanaf',
+    'audit_date_to' => 'Datum bereik tot',
 
     // Role Settings
     'roles' => 'Rollen',
@@ -141,7 +146,7 @@ return [
     'role_access_api' => 'Ga naar systeem API',
     'role_manage_settings' => 'Beheer app instellingen',
     'role_asset' => 'Asset Permissies',
-    '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.',
+    'roles_system_warning' => 'Wees ervan bewust dat toegang tot een van de bovengenoemde drie machtigingen een gebruiker in staat kan stellen zijn eigen privileges of de privileges van anderen in het systeem te wijzigen. Wijs alleen rollen toe met deze machtigingen aan vertrouwde gebruikers.',
     'role_asset_desc' => 'Deze permissies bepalen de standaardtoegangsrechten. Permissies op boeken, hoofdstukken en pagina\'s overschrijven deze instelling.',
     'role_asset_admins' => 'Beheerders krijgen automatisch toegang tot alle inhoud, maar deze opties kunnen interface opties tonen of verbergen.',
     'role_all' => 'Alles',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'Gebruikersprofiel',
     'users_add_new' => 'Gebruiker toevoegen',
     'users_search' => 'Gebruiker zoeken',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Laatste activiteit',
     'users_details' => 'Gebruiker details',
     'users_details_desc' => 'Stel een weergavenaam en e-mailadres in voor deze gebruiker. Het e-mailadres zal worden gebruikt om in te loggen.',
     'users_details_desc_no_email' => 'Stel een weergavenaam in voor deze gebruiker zodat anderen deze kunnen herkennen.',
@@ -168,17 +173,17 @@ return [
     'users_send_invite_text' => 'U kunt ervoor kiezen om deze gebruiker een uitnodigingsmail te sturen waarmee hij zijn eigen wachtwoord kan instellen, anders kunt u zelf zijn wachtwoord instellen.',
     'users_send_invite_option' => 'Stuur gebruiker uitnodigings e-mail',
     'users_external_auth_id' => 'Externe authenticatie ID',
-    'users_external_auth_id_desc' => 'This is the ID used to match this user when communicating with your external authentication system.',
+    'users_external_auth_id_desc' => 'Dit is het ID dat gebruikt wordt om deze gebruiker te vergelijken met uw externe verificatiesysteem.',
     'users_password_warning' => 'Vul onderstaande formulier alleen in als je het wachtwoord wilt aanpassen:',
     'users_system_public' => 'De eigenschappen van deze gebruiker worden voor elke gastbezoeker gebruikt. Er kan niet mee ingelogd worden en wordt automatisch toegewezen.',
     'users_delete' => 'Verwijder gebruiker',
     'users_delete_named' => 'Verwijder gebruiker :userName',
     'users_delete_warning' => 'Dit zal de gebruiker \':userName\' volledig uit het systeem verwijderen.',
     'users_delete_confirm' => 'Weet je zeker dat je deze gebruiker wilt verwijderen?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'Draag eigendom over',
+    'users_migrate_ownership_desc' => 'Selecteer een gebruiker hier als u wilt dat een andere gebruiker de eigenaar wordt van alle items die momenteel eigendom zijn van deze gebruiker.',
+    'users_none_selected' => 'Geen gebruiker geselecteerd',
+    'users_delete_success' => 'Gebruiker succesvol verwijderd',
     'users_edit' => 'Bewerk Gebruiker',
     'users_edit_profile' => 'Bewerk Profiel',
     'users_edit_success' => 'Gebruiker succesvol bijgewerkt',
@@ -193,31 +198,31 @@ return [
     '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_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_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',
 
     // API Tokens
-    'user_api_token_create' => 'Create API Token',
+    'user_api_token_create' => 'API-token aanmaken',
     'user_api_token_name' => 'Naam',
-    'user_api_token_name_desc' => 'Give your token a readable name as a future reminder of its intended purpose.',
+    'user_api_token_name_desc' => 'Geef je token een leesbare naam als een toekomstige herinnering aan het beoogde doel.',
     'user_api_token_expiry' => 'Vervaldatum',
-    '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_create_success' => 'API token successfully created',
-    'user_api_token_update_success' => 'API token successfully updated',
+    '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_create_success' => 'API token succesvol aangemaakt',
+    'user_api_token_update_success' => 'API token succesvol bijgewerkt',
     '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_id_desc' => 'Dit is een niet bewerkbaar systeem gegenereerde id voor dit token dat moet worden verstrekt in API-verzoeken.',
+    'user_api_token_secret' => 'Geheime token sleutel',
+    'user_api_token_secret_desc' => 'Dit is een systeem gegenereerd geheim voor dit token dat moet worden verstrekt in API verzoeken. Dit wordt maar één keer weergegeven, dus kopieër deze waarde naar een veilige plaats.',
+    'user_api_token_created' => 'Token gemaakt :timeAgo',
+    'user_api_token_updated' => 'Token bijgewerkt :timeAgo',
     'user_api_token_delete' => 'Token Verwijderen',
-    '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_delete_success' => 'API token successfully deleted',
+    'user_api_token_delete_warning' => 'Dit zal de API-token met de naam \':tokenName\' volledig uit het systeem verwijderen.',
+    'user_api_token_delete_confirm' => 'Weet u zeker dat u deze API-token wilt verwijderen?',
+    'user_api_token_delete_success' => 'API-token succesvol verwijderd',
 
     //! If editing translations files directly please ignore this in all
     //! languages apart from en. Content will be auto-copied from en.
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Catalaans',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 2f8ffc9f0531959e4ff2098ad06f7a297890d9dd..f0e99ad912fd373ad75cbc88203c5d4b82d8a452 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute moet minstens :min karakters bevatten.',
         'array'   => ':attribute moet minstens :min items bevatten.',
     ],
-    'no_double_extension'  => ':attribute mag maar een enkele bestandsextensie hebben.',
     'not_in'               => ':attribute is ongeldig.',
     'not_regex'            => ':attribute formaat is ongeldig.',
     'numeric'              => ':attribute moet een getal zijn.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => ':attribute veld is verplicht wanneer :values niet ingesteld is.',
     'required_without_all' => ':attribute veld is verplicht wanneer geen van :values ingesteld zijn.',
     'same'                 => ':attribute en :other moeten overeenkomen.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => 'De opgegeven link is mogelijk niet veilig.',
     'size'                 => [
         'numeric' => ':attribute moet :size zijn.',
         'file'    => ':attribute moet :size kilobytes zijn.',
index c4e8ef0de831b0954d646c4007138a13bf40b450..53f8486daa50b1b3e40db72074cb471f447a1adc 100644 (file)
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'skomentował',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => 'zaktualizowane uprawnienia',
 ];
index c93d0b9e108f97638ffa8546cf70b66d90d9c8d0..35d6b6b8292fb7a91633d75b5545d346655e347f 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Sortuj rosnąco',
     'sort_descending' => 'Sortuj malejąco',
     'sort_name' => 'Nazwa',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Data utworzenia',
     'sort_updated_at' => 'Data aktualizacji',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Ścieżka nawigacji',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Menu profilu',
     'view_profile' => 'Zobacz profil',
     'edit_profile' => 'Edytuj profil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informacje',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Treść',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Jeśli masz problem z kliknięciem przycisku ":actionText", skopiuj i wklej poniższy adres URL w nowej karcie swojej przeglądarki:',
     'email_rights' => 'Wszelkie prawa zastrzeżone',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Polityka prywatności',
+    'terms_of_service' => 'Warunki usługi',
 ];
index 59960bd301e3b9b7564b607a23d15265175f44ac..4fab043217c0df57ea1a8602d5526e7cac9c9131 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Utworzono :timeLength przez :user',
     'meta_updated' => 'Zaktualizowano :timeLength',
     'meta_updated_name' => 'Zaktualizowano :timeLength przez :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Właściciel :user',
     'entity_select' => 'Wybór obiektu',
     'images' => 'Obrazki',
     'my_recent_drafts' => 'Moje ostatnie wersje robocze',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Jeśli włączone są indywidualne uprawnienia, to te uprawnienia będą miały priorytet względem pozostałych ustawionych uprawnień ról.',
     'permissions_enable' => 'Włącz własne uprawnienia',
     'permissions_save' => 'Zapisz uprawnienia',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Właściciel',
 
     // Search
     'search_results' => 'Wyniki wyszukiwania',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Zbiór uprawnień',
     'search_created_by_me' => 'Utworzone przeze mnie',
     'search_updated_by_me' => 'Zaktualizowane przeze mnie',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Opcje dat',
     'search_updated_before' => 'Zaktualizowane przed',
     'search_updated_after' => 'Zaktualizowane po',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Utwórz nowy rozdział',
     'chapters_delete' => 'Usuń rozdział',
     'chapters_delete_named' => 'Usuń rozdział :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_explain' => 'Spowoduje to usunięcie rozdziału o nazwie \':chapterName\'. Wszystkie strony, które istnieją w tym rozdziale, również zostaną usunięte.',
     'chapters_delete_confirm' => 'Czy na pewno chcesz usunąć ten rozdział?',
     'chapters_edit' => 'Edytuj rozdział',
     'chapters_edit_named' => 'Edytuj rozdział :chapterName',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Wersje strony',
     'pages_revisions_named' => 'Wersje strony :pageName',
     'pages_revision_named' => 'Wersja strony :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Przywrócono z #:id; :summary',
     'pages_revisions_created_by' => 'Utworzona przez',
     'pages_revisions_date' => 'Data wersji',
     'pages_revisions_number' => '#',
@@ -268,7 +269,7 @@ return [
     'attachments_link_url' => 'Link do pliku',
     'attachments_link_url_hint' => 'Strona lub plik',
     'attach' => 'Załącz',
-    'attachments_insert_link' => 'Add Attachment Link to Page',
+    'attachments_insert_link' => 'Dodaj link do załącznika do strony',
     'attachments_edit_file' => 'Edytuj plik',
     'attachments_edit_file_name' => 'Nazwa pliku',
     'attachments_edit_drop_upload' => 'Upuść pliki lub kliknij tutaj by przesłać pliki i nadpisać istniejące',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Czu ma pewno chcesz przywrócić tą wersję? Aktualna zawartość strony zostanie nadpisana.',
     'revision_delete_success' => 'Usunięto wersję',
     'revision_cannot_delete_latest' => 'Nie można usunąć najnowszej wersji.'
-];
\ No newline at end of file
+];
index 5f5ec9db25150366d9c90c0f164814cb67a3386f..236c787a7f767d4ab1b71ba5c3742417e4180f50 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Strona nie została znaleziona',
     'sorry_page_not_found' => 'Przepraszamy, ale strona której szukasz nie została znaleziona.',
     'sorry_page_not_found_permission_warning' => 'Jeśli spodziewałeś się, że ta strona istnieje, prawdopodobnie nie masz uprawnień do jej wyświetlenia.',
+    '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' => 'Powrót do strony głównej',
     'error_occurred' => 'Wystąpił błąd',
     'app_down' => ':appName jest aktualnie wyłączona',
index abba0ce60d89fc62962f943cecfe14757618321d..8e8a039482cd6064a1e3dbb80298f6fe6ad2b910 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Strona główna',
     'app_homepage_desc' => 'Wybierz widok, który będzie wyświetlany na stronie głównej zamiast w widoku domyślnego. Uprawnienia dostępowe są ignorowane dla wybranych stron.',
     'app_homepage_select' => 'Wybierz stronę',
+    'app_footer_links' => 'Linki w stopce',
+    'app_footer_links_desc' => 'Dodaj linki do pokazania w stopce witryny. Będą one wyświetlane na dole większości stron, włącznie z tymi, które nie wymagają logowania. Możesz użyć etykiety "trans::<key>" aby użyć tłumaczeń zdefiniowanych przez system. Na przykład: Używanie "trans::common.privacy_policy" zapewni przetłumaczony tekst "Polityka prywatności" i "trans::common.terms_of_service" zapewni przetłumaczony tekst "Warunki korzystania z usługi".',
+    'app_footer_links_label' => 'Etykieta linku',
+    'app_footer_links_url' => 'URL odnośnika',
+    'app_footer_links_add' => 'Dodaj link w stopce',
     'app_disable_comments' => 'Wyłącz komentarze',
     'app_disable_comments_toggle' => 'Wyłącz komentowanie',
     'app_disable_comments_desc' => 'Wyłącz komentarze na wszystkich stronach w aplikacji. Istniejące komentarze nie będą pokazywane.',
@@ -68,7 +73,7 @@ return [
     'maint' => 'Konserwacja',
     'maint_image_cleanup' => 'Czyszczenie obrazków',
     'maint_image_cleanup_desc' => "Skanuje zawartość strony i poprzednie wersje, aby sprawdzić, które obrazy i rysunki są aktualnie używane, a które obrazy są zbędne. Przed uruchomieniem tej opcji należy utworzyć pełną kopię zapasową bazy danych i obrazków.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Usuń również obrazy, które istnieją tylko w starych rewizjach strony',
     'maint_image_cleanup_run' => 'Uruchom czyszczenie',
     'maint_image_cleanup_warning' => 'Znaleziono :count potencjalnie niepotrzebnych obrazków. Czy na pewno chcesz je usunąć?',
     'maint_image_cleanup_success' => ':count potencjalnie nieużywane obrazki zostały znalezione i usunięte!',
@@ -80,41 +85,41 @@ return [
     'maint_send_test_email_mail_subject' => 'E-mail testowy',
     'maint_send_test_email_mail_greeting' => 'Wygląda na to, że wysyłka wiadomości e-mail działa!',
     'maint_send_test_email_mail_text' => 'Gratulacje! Otrzymałeś tego e-maila więc Twoje ustawienia poczty elektronicznej wydają się być prawidłowo skonfigurowane.',
-    '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_recycle_bin_desc' => 'Usunięte półki, książki, rozdziały i strony są wysyłane do kosza, aby mogły zostać przywrócone lub trwale usunięte. Starsze przedmioty w koszu mogą zostać automatycznie usunięte po pewnym czasie w zależności od konfiguracji systemu.',
+    'maint_recycle_bin_open' => 'Otwórz kosz',
 
     // Recycle Bin
-    'recycle_bin' => 'Recycle Bin',
+    'recycle_bin' => 'Kosz',
     '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_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_deleted_item' => 'Usunięta pozycja',
+    'recycle_bin_deleted_by' => 'Usunięty przez',
+    'recycle_bin_deleted_at' => 'Czas usunięcia',
+    'recycle_bin_permanently_delete' => 'Usuń trwale',
+    'recycle_bin_restore' => 'Przywróć',
+    'recycle_bin_contents_empty' => 'Kosz jest pusty',
+    'recycle_bin_empty' => 'Opróżnij kosz',
     '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_list' => 'Elementy do przywrócenia',
     '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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
     'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
 
     // Audit Log
-    'audit' => 'Audit Log',
+    'audit' => 'Dziennik audytu',
     '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_date' => 'Activity Date',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit_event_filter' => 'Filtry Wydarzeń',
+    'audit_event_filter_no_filter' => 'Brak filtra',
+    'audit_deleted_item' => 'Usunięta pozycja',
+    'audit_deleted_item_name' => 'Nazwa: :name',
+    'audit_table_user' => 'Użytkownik',
+    'audit_table_event' => 'Wydarzenie',
+    'audit_table_related' => 'Powiązany element lub szczegóły',
+    'audit_table_date' => 'Data Aktywności',
+    'audit_date_from' => 'Zakres dat od',
+    'audit_date_to' => 'Zakres dat do',
 
     // Role Settings
     'roles' => 'Role',
@@ -141,7 +146,7 @@ return [
     'role_access_api' => 'Dostęp do systemowego API',
     'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji',
     'role_asset' => 'Zarządzanie zasobami',
-    '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.',
+    'roles_system_warning' => 'Pamiętaj, że dostęp do trzech powyższych uprawnień może pozwolić użytkownikowi na zmianę własnych uprawnień lub uprawnień innych osób w systemie. Przypisz tylko role z tymi uprawnieniami do zaufanych użytkowników.',
     'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia książek, rozdziałów i stron nadpisują te ustawienia.',
     'role_asset_admins' => 'Administratorzy mają automatycznie dostęp do wszystkich treści, ale te opcję mogą być pokazywać lub ukrywać opcje interfejsu użytkownika.',
     'role_all' => 'Wszyscy',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'Profil użytkownika',
     'users_add_new' => 'Dodaj użytkownika',
     'users_search' => 'Wyszukaj użytkownika',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Ostatnia aktywność',
     'users_details' => 'Szczegóły użytkownika',
     'users_details_desc' => 'Ustaw wyświetlaną nazwę i adres e-mail dla tego użytkownika. Adres e-mail zostanie wykorzystany do zalogowania się do aplikacji.',
     'users_details_desc_no_email' => 'Ustaw wyświetlaną nazwę dla tego użytkownika, aby inni mogli go rozpoznać.',
@@ -175,10 +180,10 @@ return [
     'users_delete_named' => 'Usuń :userName',
     'users_delete_warning' => 'To usunie użytkownika \':userName\' z systemu.',
     'users_delete_confirm' => 'Czy na pewno chcesz usunąć tego użytkownika?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'Migracja Własności',
+    'users_migrate_ownership_desc' => 'Wybierz użytkownika tutaj, jeśli chcesz, aby inny użytkownik stał się właścicielem wszystkich elementów będących obecnie w posiadaniu tego użytkownika.',
+    'users_none_selected' => 'Nie wybrano użytkownika',
+    'users_delete_success' => 'Użytkownik pomyślnie usunięty',
     'users_edit' => 'Edytuj użytkownika',
     'users_edit_profile' => 'Edytuj profil',
     'users_edit_success' => 'Użytkownik zaktualizowany pomyślnie',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Kataloński',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 26ac348c1aa955d97af065e3bd538f7eb229df10..76ff49a6a4fcb96e13fdfe629785b499494277c6 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'Długość :attribute nie może być mniejsza niż :min znaków.',
         'array'   => 'Rozmiar :attribute musi posiadać co najmniej :min elementy.',
     ],
-    'no_double_extension'  => ':attribute może mieć tylko jedno rozszerzenie.',
     'not_in'               => 'Wartość :attribute jest nieprawidłowa.',
     'not_regex'            => 'Format :attribute jest nieprawidłowy.',
     'numeric'              => ':attribute musi być liczbą.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => 'Pole :attribute jest wymagane jeśli :values nie zostało wprowadzone.',
     'required_without_all' => 'Pole :attribute jest wymagane jeśli żadna z wartości :values nie została podana.',
     'same'                 => 'Pole :attribute i :other muszą być takie same.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => 'Podany link może nie być bezpieczny.',
     'size'                 => [
         'numeric' => ':attribute musi mieć długość :size.',
         'file'    => ':attribute musi mieć :size kilobajtów.',
index 4cac54b2a706efa35cb8873ebc247c20420e65b5..8a5d15e8f91c5a00b14133a216b6c372447b2827 100644 (file)
@@ -6,43 +6,44 @@
 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_create'                 => 'página criada',
+    'page_create_notification'    => 'Página criada com sucesso',
+    'page_update'                 => 'página atualizada',
+    'page_update_notification'    => 'Página atualizada com sucesso',
+    'page_delete'                 => 'página eliminada',
+    'page_delete_notification'    => 'Página eliminada com sucesso',
+    'page_restore'                => 'página restaurada',
+    'page_restore_notification'   => 'Página restaurada com sucesso',
+    'page_move'                   => 'página movida',
 
     // 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_create'              => 'capítulo criado',
+    'chapter_create_notification' => 'Capítulo criado com sucesso',
+    'chapter_update'              => 'capítulo atualizado',
+    'chapter_update_notification' => 'Capítulo atualizado com sucesso',
+    'chapter_delete'              => 'capítulo excluído',
+    'chapter_delete_notification' => 'Capítulo excluído com sucesso',
+    'chapter_move'                => 'capítulo movido',
 
     // Books
-    'book_create'                 => 'created book',
-    'book_create_notification'    => 'Book Successfully Created',
-    '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'                 => 'livro criado',
+    'book_create_notification'    => 'Livro criado com sucesso',
+    'book_update'                 => 'livro atualizado',
+    'book_update_notification'    => 'Livro atualizado com sucesso',
+    'book_delete'                 => 'livro eliminado',
+    'book_delete_notification'    => 'Livro eliminado com sucesso',
+    'book_sort'                   => 'livro ordenado',
+    'book_sort_notification'      => 'Livro reordenado com sucesso',
 
     // Bookshelves
-    'bookshelf_create'            => 'created Bookshelf',
-    'bookshelf_create_notification'    => 'Bookshelf Successfully Created',
-    'bookshelf_update'                 => 'updated bookshelf',
-    'bookshelf_update_notification'    => 'Bookshelf Successfully Updated',
-    'bookshelf_delete'                 => 'deleted bookshelf',
-    'bookshelf_delete_notification'    => 'Bookshelf Successfully Deleted',
+    'bookshelf_create'            => 'estante criada',
+    'bookshelf_create_notification'    => 'Estante criada com sucesso',
+    'bookshelf_update'                 => 'estante atualizada',
+    'bookshelf_update_notification'    => 'Estante atualizada com sucesso',
+    'bookshelf_delete'                 => 'excluiu a prateleira',
+    'bookshelf_delete_notification'    => 'Estante eliminada com sucesso',
 
     // Other
-    'commented_on'                => 'commented on',
+    'commented_on'                => 'comentado a',
+    'permissions_update'          => 'permissões atualizadas',
 ];
index d64fce93a62d90889b2297a9e4f6482ad9046475..7d520a5f7d3228a77484b688898b1260fde34d92 100644 (file)
@@ -6,72 +6,72 @@
  */
 return [
 
-    'failed' => 'These credentials do not match our records.',
-    'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+    'failed' => 'As credenciais fornecidas não correspondem aos nossos registos.',
+    'throttle' => 'Demasiadas tentativas de início de sessão. Por favor, tente novamente em :seconds segundos.',
 
     // 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' => 'Criar conta',
+    'log_in' => 'Iniciar sessão',
+    'log_in_with' => 'Iniciar sessão com :socialDriver',
+    'sign_up_with' => 'Criar conta com :socialDriver',
+    'logout' => 'Terminar sessão',
 
-    'name' => 'Name',
-    'username' => 'Username',
-    'email' => 'Email',
-    'password' => 'Password',
-    'password_confirm' => 'Confirm Password',
-    'password_hint' => 'Must be over 7 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' => 'Nome',
+    'username' => 'Nome de utilizador',
+    'email' => 'E-mail',
+    'password' => 'Palavra-passe',
+    'password_confirm' => 'Confirmar Palavra-passe',
+    'password_hint' => 'Deve ser maior que 7 caracteres',
+    'forgot_password' => 'Esqueceu-se da palavra-passe?',
+    'remember_me' => 'Lembrar-se de mim',
+    'ldap_email_hint' => 'Por favor insira um endereço de e-mail para esta conta.',
+    'create_account' => 'Criar Conta',
+    'already_have_account' => 'Já possui uma conta?',
+    'dont_have_account' => 'Não possui uma conta?',
+    'social_login' => 'Inicio de Sessão com Redes Sociais',
+    'social_registration' => 'Registo com Redes Sociais',
+    'social_registration_text' => 'Registe e inicie sessão com recurso a outro serviço.',
 
-    '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' => 'Obrigado por se registar!',
+    'register_confirm' => 'Por favor, verifique o seu e-mail e carregue no botão de confirmação para aceder :appName.',
+    'registrations_disabled' => 'Os registos estão temporariamente desativados',
+    'registration_email_domain_invalid' => 'O domínio de e-mail usado não tem acesso permitido a esta aplicação',
+    'register_success' => 'Obrigado por se registar! Você está agora registado e com a sessão iniciada.',
 
 
     // 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' => 'Redefinir Senha',
+    'reset_password_send_instructions' => 'Insira o seu endereço de e-mail abaixo, e uma mensagem com o link de redefinição de palavra-passe será lhe enviada.',
+    'reset_password_send_button' => 'Enviar o Link de Redefinição',
+    'reset_password_sent' => 'Um link de redefinição de palavra-passe será enviado para :email, se o endereço de e-mail for encontrado no sistema.',
+    'reset_password_success' => 'A sua palavra-passe foi redefinida com sucesso.',
+    'email_reset_subject' => 'Redefina a sua palavra-passe de :appName',
+    'email_reset_text' => 'Você recebeu este e-mail pois recebemos uma solicitação de redefinição de senha para a sua conta.',
+    'email_reset_not_requested' => 'Caso não tenha sido você a solicitar a redefinição de senha, ignore este e-mail.',
 
 
     // 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!',
-    'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.',
+    'email_confirm_subject' => 'Confirme o seu endereço de e-mail para :appName',
+    'email_confirm_greeting' => 'Obrigado por se registar em :appName!',
+    'email_confirm_text' => 'Por favor, confirme o seu endereço de e-mail ao carregar no botão abaixo:',
+    'email_confirm_action' => 'Confirmar E-mail',
+    'email_confirm_send_error' => 'A confirmação do endereço de e-mail é requerida, mas o sistema não pôde enviar a mensagem. Por favor, entre em contacto com o administrador para se certificar que o serviço de envio de e-mails está corretamente configurado.',
+    'email_confirm_success' => 'O seu endereço de e-mail foi confirmado!',
+    'email_confirm_resent' => 'E-mail de confirmação reenviado. Por favor, verifique a sua caixa de entrada.',
 
-    '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' => 'Endereço de E-mail Não Confirmado',
+    'email_not_confirmed_text' => 'O seu endereço de e-mail ainda não foi confirmado.',
+    'email_not_confirmed_click_link' => 'Por favor, carregue no link que se encontra no e-mail que lhe foi enviado após o seu registo.',
+    'email_not_confirmed_resend' => 'Caso não encontre o e-mail poderá reenviar a confirmação utilizando o formulário abaixo.',
+    'email_not_confirmed_resend_button' => 'Reenviar o E-mail de Confirmação',
 
     // 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' => 'Password set, you now have access to :appName!'
+    'user_invite_email_subject' => 'Você recebeu um convite para se juntar a :appName!',
+    'user_invite_email_greeting' => 'Uma conta foi criada para si em :appName.',
+    'user_invite_email_text' => 'Carregue no botão abaixo para definir uma palavra-passe de conta e obter acesso:',
+    'user_invite_email_action' => 'Defina a Palavra-passe da Conta',
+    'user_invite_page_welcome' => 'Bem-vindo(a) a :appName!',
+    'user_invite_page_text' => 'Para finalizar a sua conta e obter acesso, precisa de definir uma senha que será utilizada para efetuar login em :appName em visitas futuras.',
+    'user_invite_page_confirm_button' => 'Confirmar Palavra-Passe',
+    'user_invite_success' => 'Palavra-passe definida, tem agora acesso a :appName!'
 ];
\ No newline at end of file
index e87bd11a5e343173fadf78e62a042e1ca0579a4a..e42e34af8e1ffc30dbe9b3163f9bd3703de1c140 100644 (file)
@@ -5,76 +5,85 @@
 return [
 
     // Buttons
-    'cancel' => 'Cancel',
-    'confirm' => 'Confirm',
-    'back' => 'Back',
-    'save' => 'Save',
-    'continue' => 'Continue',
-    'select' => 'Select',
-    'toggle_all' => 'Toggle All',
-    'more' => 'More',
+    'cancel' => 'Cancelar',
+    'confirm' => 'Confirmar',
+    'back' => 'Voltar',
+    'save' => 'Guardar',
+    'continue' => 'Continuar',
+    'select' => 'Selecionar',
+    'toggle_all' => 'Alternar Todos',
+    'more' => 'Mais',
 
     // Form Labels
-    'name' => 'Name',
-    'description' => 'Description',
-    'role' => 'Role',
-    'cover_image' => 'Cover image',
-    'cover_image_description' => 'This image should be approx 440x250px.',
+    'name' => 'Nome',
+    'description' => 'Descrição',
+    'role' => 'Cargo',
+    'cover_image' => 'Imagem de capa',
+    'cover_image_description' => 'Esta imagem deve ser aproximadamente 440x250px.',
     
     // Actions
-    'actions' => 'Actions',
-    'view' => 'View',
-    'view_all' => 'View All',
-    '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',
-    'fullscreen' => 'Fullscreen',
+    'actions' => 'Ações',
+    'view' => 'Visualizar',
+    'view_all' => 'Visualizar Todos',
+    'create' => 'Criar',
+    'update' => 'Atualizar',
+    'edit' => 'Editar',
+    'sort' => 'Ordenar',
+    'move' => 'Mover',
+    'copy' => 'Copiar',
+    'reply' => 'Responder',
+    'delete' => 'Eliminar',
+    'delete_confirm' => 'Confirmar eliminação',
+    'search' => 'Pesquisar',
+    'search_clear' => 'Limpar Pesquisa',
+    'reset' => 'Redefinir',
+    'remove' => 'Remover',
+    'add' => 'Adicionar',
+    'fullscreen' => 'Ecrã completo',
 
     // Sort Options
-    'sort_options' => 'Sort Options',
-    'sort_direction_toggle' => 'Sort Direction Toggle',
-    'sort_ascending' => 'Sort Ascending',
-    'sort_descending' => 'Sort Descending',
-    'sort_name' => 'Name',
-    'sort_created_at' => 'Created Date',
-    'sort_updated_at' => 'Updated Date',
+    'sort_options' => 'Opções de Ordenação',
+    'sort_direction_toggle' => 'Alternar Direção de Ordenação',
+    'sort_ascending' => 'Ordenação Crescente',
+    'sort_descending' => 'Ordenação Decrescente',
+    'sort_name' => 'Nome',
+    'sort_default' => 'Padrão',
+    'sort_created_at' => 'Data de Criação',
+    'sort_updated_at' => 'Data de Atualização',
 
     // Misc
-    'deleted_user' => 'Deleted User',
-    'no_activity' => 'No activity to show',
-    'no_items' => 'No items available',
-    'back_to_top' => 'Back to top',
-    'toggle_details' => 'Toggle Details',
-    'toggle_thumbnails' => 'Toggle Thumbnails',
-    'details' => 'Details',
-    'grid_view' => 'Grid View',
-    'list_view' => 'List View',
-    'default' => 'Default',
-    'breadcrumb' => 'Breadcrumb',
+    'deleted_user' => 'Utilizador Eliminado',
+    'no_activity' => 'Nenhuma atividade a mostrar',
+    'no_items' => 'Nenhum item disponível',
+    'back_to_top' => 'Voltar ao topo',
+    'toggle_details' => 'Alternar Detalhes',
+    'toggle_thumbnails' => 'Alternar Miniaturas',
+    'details' => 'Detalhes',
+    'grid_view' => 'Visualização em Grade',
+    'list_view' => 'Visualização em Lista',
+    'default' => 'Padrão',
+    'breadcrumb' => 'Caminho',
 
     // Header
-    'profile_menu' => 'Profile Menu',
-    'view_profile' => 'View Profile',
-    'edit_profile' => 'Edit Profile',
-    'dark_mode' => 'Dark Mode',
-    'light_mode' => 'Light Mode',
+    'header_menu_expand' => 'Expandir Menu de Cabeçalho',
+    'profile_menu' => 'Menu de Perfil',
+    'view_profile' => 'Visualizar Perfil',
+    'edit_profile' => 'Editar Perfil',
+    'dark_mode' => 'Modo Escuro',
+    'light_mode' => 'Modo Claro',
 
     // Layout tabs
-    'tab_info' => 'Info',
-    'tab_content' => 'Content',
+    'tab_info' => 'Informações',
+    'tab_info_label' => 'Separador: Mostrar Informação Secundária',
+    'tab_content' => 'Conteúdo',
+    'tab_content_label' => 'Separador: Mostrar Conteúdo Primário',
 
     // 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' => 'Se estiver com problemas ao carregar no botão ":actionText", copie e cole o URL abaixo no seu navegador:',
+    'email_rights' => 'Todos os direitos reservados',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Política de Privacidade',
+    'terms_of_service' => 'Termos de Utilização',
 ];
index 48a0a32faa38c4821a9d71dda9a5fb4f97d35232..bdec2022696dfe1327b0fda480da12c425068ed1 100644 (file)
@@ -5,30 +5,30 @@
 return [
 
     // Image Manager
-    'image_select' => 'Image Select',
-    '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_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',
-    '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_upload_remove' => 'Remove',
+    'image_select' => 'Selecionar Imagem',
+    'image_all' => 'Todas',
+    'image_all_title' => 'Visualizar todas as imagens',
+    'image_book_title' => 'Visualizar imagens relacionadas a este livro',
+    'image_page_title' => 'Visualizar imagens relacionadas a esta página',
+    'image_search_hint' => 'Pesquisar imagem por nome',
+    'image_uploaded' => 'Adicionada em :uploadedDate',
+    'image_load_more' => 'Carregar Mais',
+    'image_image_name' => 'Nome da Imagem',
+    'image_delete_used' => 'Esta imagem é utilizada nas páginas abaixo.',
+    'image_delete_confirm_text' => 'Tem certeza de que deseja eliminar esta imagem?',
+    'image_select_image' => 'Selecionar Imagem',
+    'image_dropzone' => 'Arraste imagens ou carregue aqui para fazer upload',
+    'images_deleted' => 'Imagens Eliminadas',
+    'image_preview' => 'Pré-visualização de Imagem',
+    'image_upload_success' => 'Carregamento da imagem efetuado com sucesso',
+    'image_update_success' => 'Detalhes da imagem atualizados com sucesso',
+    'image_delete_success' => 'Imagem eliminada com sucesso',
+    'image_upload_remove' => 'Remover',
 
     // 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' => 'Editar Código',
+    'code_language' => 'Linguagem do Código',
+    'code_content' => 'Código',
+    'code_session_history' => 'Histórico de Sessão',
+    'code_save' => 'Guardar Código',
 ];
index f64867a56c31736a1730d58c51f3fe0c088364d1..82ac03aa500fab6741814315c79d6ee2088903be 100644 (file)
 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',
-    'entity_select' => 'Entity Select',
-    'images' => 'Images',
-    'my_recent_drafts' => 'My Recent Drafts',
-    'my_recently_viewed' => 'My Recently Viewed',
-    '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',
+    'recently_created' => 'Criado recentemente',
+    'recently_created_pages' => 'Páginas Criadas Recentemente',
+    'recently_updated_pages' => 'Páginas Atualizadas Recentemente',
+    'recently_created_chapters' => 'Capítulos Criados Recentemente',
+    'recently_created_books' => 'Livros Criados Recentemente',
+    'recently_created_shelves' => 'Estantes Criadas Recentemente',
+    'recently_update' => 'Atualizados Recentemente',
+    'recently_viewed' => 'Visualizados Recentemente',
+    'recent_activity' => 'Atividade Recente',
+    'create_now' => 'Criar um agora',
+    'revisions' => 'Revisões',
+    'meta_revision' => 'Revisão #:revisionCount',
+    'meta_created' => 'Criado :timeLength',
+    'meta_created_name' => 'Criado :timeLength por :user',
+    'meta_updated' => 'Atualizado :timeLength',
+    'meta_updated_name' => 'Atualizado :timeLength por :user',
+    'meta_owned_name' => 'Propriedade de :user',
+    'entity_select' => 'Seleção de Entidade',
+    'images' => 'Imagens',
+    'my_recent_drafts' => 'Os Meus Rascunhos Recentes',
+    'my_recently_viewed' => 'Visualizados Recentemente Por Mim',
+    'no_pages_viewed' => 'Você não viu nenhuma página',
+    'no_pages_recently_created' => 'Nenhuma página foi recentemente criada',
+    'no_pages_recently_updated' => 'Nenhuma página foi recentemente atualizada',
+    'export' => 'Exportar',
+    'export_html' => 'Arquivo Web contido',
+    'export_pdf' => 'Arquivo PDF',
+    'export_text' => 'Arquivo Texto',
 
     // Permissions and restrictions
-    'permissions' => 'Permissions',
-    'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.',
-    'permissions_enable' => 'Enable Custom Permissions',
-    'permissions_save' => 'Save Permissions',
+    'permissions' => 'Permissões',
+    'permissions_intro' => 'Uma vez ativadas, estas permissões terão prioridade sobre quaisquer outro conjunto de permissões.',
+    'permissions_enable' => 'Ativar Permissões Personalizadas',
+    'permissions_save' => 'Guardar Permissões',
+    'permissions_owner' => 'Proprietário',
 
     // 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_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' => 'Resultado(s) da Pesquisa',
+    'search_total_results_found' => ':count resultado encontrado|:count resultados encontrados',
+    'search_clear' => 'Limpar Pesquisa',
+    'search_no_pages' => 'Nenhuma página corresponde à pesquisa',
+    'search_for_term' => 'Pesquisar por :term',
+    'search_more' => 'Mais Resultados',
+    'search_advanced' => 'Pesquisa Avançada',
+    'search_terms' => 'Termos da Pesquisa',
+    'search_content_type' => 'Tipo de Conteúdo',
+    'search_exact_matches' => 'Correspondências Exatas',
+    'search_tags' => 'Persquisar Tags',
+    'search_options' => 'Opções',
+    'search_viewed_by_me' => 'Visualizado por mim',
+    'search_not_viewed_by_me' => 'Não visualizado por mim',
+    'search_permissions_set' => 'Permissão definida',
+    'search_created_by_me' => 'Criado por mim',
+    'search_updated_by_me' => 'Atualizado por mim',
+    'search_owned_by_me' => 'Propriedade minha',
+    'search_date_options' => 'Opções de Data',
+    'search_updated_before' => 'Atualizado antes de',
+    'search_updated_after' => 'Atualizado depois de',
+    'search_created_before' => 'Criado antes de',
+    'search_created_after' => 'Criado depois de',
+    'search_set_date' => 'Definir Data',
+    'search_update' => 'Atualizar pesquisa',
 
     // Shelves
-    'shelf' => 'Shelf',
-    'shelves' => 'Shelves',
-    'x_shelves' => ':count Shelf|:count Shelves',
-    'shelves_long' => 'Bookshelves',
-    '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 here 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 Bookshelf :name',
-    'shelves_edit' => 'Edit Bookshelf',
-    'shelves_delete' => 'Delete Bookshelf',
-    'shelves_delete_named' => 'Delete Bookshelf :name',
-    'shelves_delete_explain' => "This will delete the bookshelf with the name ':name'. Contained books will not be deleted.",
-    'shelves_delete_confirmation' => 'Are you sure you want to delete this bookshelf?',
-    'shelves_permissions' => 'Bookshelf Permissions',
-    'shelves_permissions_updated' => 'Bookshelf Permissions Updated',
-    'shelves_permissions_active' => 'Bookshelf Permissions Active',
-    '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 bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
-    'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
+    'shelf' => 'Estante',
+    'shelves' => 'Estantes',
+    'x_shelves' => ':count Estante|:count Estantes',
+    'shelves_long' => 'Estantes de Livros',
+    'shelves_empty' => 'Nenhuma estante foi criada',
+    'shelves_create' => 'Criar Nova Estante',
+    'shelves_popular' => 'Estantes Populares',
+    'shelves_new' => 'Estantes Novas',
+    'shelves_new_action' => 'Nova Estante',
+    'shelves_popular_empty' => 'As estantes mais populares serão mostradas aqui.',
+    'shelves_new_empty' => 'As mais recentes estantes criadas serão mostradas aqui.',
+    'shelves_save' => 'Guardar Estante',
+    'shelves_books' => 'Livros nesta estante',
+    'shelves_add_books' => 'Adicionar livros a esta estante',
+    'shelves_drag_books' => 'Arraste livros aqui para adicioná-los a esta estante',
+    'shelves_empty_contents' => 'Esta estante não tem livros atribuídos',
+    'shelves_edit_and_assign' => 'Editar estante para atribuir livros',
+    'shelves_edit_named' => 'Editar Estante de Livros :name',
+    'shelves_edit' => 'Editar Estante de Livros',
+    'shelves_delete' => 'Eliminar Estante de Livros',
+    'shelves_delete_named' => 'Excluir Prateleira de Livros :name',
+    'shelves_delete_explain' => "A ação vai eliminar a estante de nome ':name'. Os livros nela presentes não serão eliminados.",
+    'shelves_delete_confirmation' => 'Tem a certeza que quer eliminar esta estante de livros?',
+    'shelves_permissions' => 'Permissões da Estante',
+    'shelves_permissions_updated' => 'Permissões da Estante de Livros Atualizada',
+    'shelves_permissions_active' => 'Permissões da Estante de Livros Ativas',
+    '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.',
+    'shelves_copy_permission_success' => 'Permissões de estante copiadas para :count livros',
 
     // 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_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',
+    'book' => 'Livro',
+    'books' => 'Livros',
+    'x_books' => ':count Livro|:count Livros',
+    'books_empty' => 'Nenhum livro foi criado',
+    'books_popular' => 'Livros Populares',
+    'books_recent' => 'Livros Recentes',
+    'books_new' => 'Livros Novos',
+    'books_new_action' => 'Novo Livro',
+    'books_popular_empty' => 'Os livros mais populares serão mostrados aqui.',
+    'books_new_empty' => 'Os livros mais recentemente criados serão mostrados aqui.',
+    'books_create' => 'Criar Livro Novo',
+    'books_delete' => 'Eliminar Livro',
+    'books_delete_named' => 'Eliminar Livro :bookName',
+    'books_delete_explain' => 'A ação vai eliminar o livro com de nome \':bookName\'. Todas as páginas e capítulos serão também removidos.',
+    'books_delete_confirmation' => 'Tem a certeza que quer eliminar este livro?',
+    'books_edit' => 'Editar Livro',
+    'books_edit_named' => 'Editar Livro :bookName',
+    'books_form_book_name' => 'Nome do Livro',
+    'books_save' => 'Guardar Livro',
+    '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.',
+    'books_empty_create_page' => 'Criar uma nova página',
+    'books_empty_sort_current_book' => 'Ordenar o livro atual',
+    'books_empty_add_chapter' => 'Adicionar um capítulo',
+    'books_permissions_active' => 'Permissões do Livro Ativas',
+    'books_search_this' => 'Pesquisar neste livro',
+    'books_navigation' => 'Navegação do Livro',
+    'books_sort' => 'Ordenar Conteúdos do Livro',
+    'books_sort_named' => 'Ordenar Livro :bookName',
+    'books_sort_name' => 'Ordenar por Nome',
+    'books_sort_created' => 'Ordenar por Data de Criação',
+    'books_sort_updated' => 'Ordenar por Data de Atualização',
+    'books_sort_chapters_first' => 'Capítulos Primeiro',
+    'books_sort_chapters_last' => 'Capítulos por Último',
+    'books_sort_show_other' => 'Mostrar Outros Livros',
+    'books_sort_save' => 'Guardar Nova Ordenação',
 
     // 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 will be removed and added directly to the parent book.',
-    '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',
-    'chapter_move_success' => 'Chapter moved to :bookName',
-    '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' => 'Capítulo',
+    'chapters' => 'Capítulos',
+    'x_chapters' => ':count Capítulo|:count Capítulos',
+    'chapters_popular' => 'Capítulos Populares',
+    'chapters_new' => 'Novo Capítulo',
+    'chapters_create' => 'Criar Novo Capítulo',
+    'chapters_delete' => 'Eliminar Capítulo',
+    'chapters_delete_named' => 'Eliminar Capítulo :chapterName',
+    'chapters_delete_explain' => 'Isto irá eliminar o capítulo com o nome \':chapterName\'. Todas as páginas existentes dentro do mesmo serão também eliminadas.',
+    'chapters_delete_confirm' => 'Tem certeza que deseja eliminar o capítulo?',
+    'chapters_edit' => 'Editar Capítulo',
+    'chapters_edit_named' => 'Editar Capítulo :chapterName',
+    'chapters_save' => 'Guardar Capítulo',
+    'chapters_move' => 'Mover Capítulo',
+    'chapters_move_named' => 'Mover Capítulo :chapterName',
+    'chapter_move_success' => 'Capítulo movido para :bookName',
+    'chapters_permissions' => 'Permissões do Capítulo',
+    'chapters_empty' => 'Nenhuma página existente neste capítulo.',
+    'chapters_permissions_active' => 'Permissões de Capítulo Ativas',
+    'chapters_permissions_success' => 'Permissões de Capítulo Atualizadas',
+    'chapters_search_this' => 'Pesquisar neste Capítulo',
 
     // 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_discard_draft' => 'Discard Draft',
-    '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_save' => 'Save Page',
-    'pages_title' => 'Page Title',
-    'pages_name' => 'Page Name',
+    'page' => 'Página',
+    'pages' => 'Páginas',
+    'x_pages' => ':count Página|:count Páginas',
+    'pages_popular' => 'Páginas Populares',
+    'pages_new' => 'Nova Página',
+    'pages_attachments' => 'Anexos',
+    'pages_navigation' => 'Navegação da Página',
+    'pages_delete' => 'Eliminar Página',
+    'pages_delete_named' => 'Eliminar Página :pageName',
+    'pages_delete_draft_named' => 'Eliminar Rascunho de Página de nome :pageName',
+    '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_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',
+    'pages_edit_draft_options' => 'Opções de Rascunho',
+    'pages_edit_save_draft' => 'Guardar Rascunho',
+    'pages_edit_draft' => 'Editar Rascunho de Página',
+    'pages_editing_draft' => 'A Editar Rascunho',
+    'pages_editing_page' => 'A Editar Página',
+    'pages_edit_draft_save_at' => 'Rascunho guardado em ',
+    'pages_edit_delete_draft' => 'Eliminar Rascunho',
+    'pages_edit_discard_draft' => 'Descartar Rascunho',
+    'pages_edit_set_changelog' => 'Relatar Alterações',
+    'pages_edit_enter_changelog_desc' => 'Digite uma breve descrição das alterações efetuadas por si',
+    'pages_edit_enter_changelog' => 'Inserir Alterações',
+    'pages_save' => 'Guardar Página',
+    'pages_title' => 'Título da Página',
+    'pages_name' => 'Nome da Página',
     '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_not_in_chapter' => 'Page is not in a chapter',
-    'pages_move' => 'Move Page',
-    'pages_move_success' => 'Page moved to ":parentName"',
-    '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_named' => 'Page Revisions for :pageName',
-    'pages_revision_named' => 'Page Revision for :pageName',
-    'pages_revisions_created_by' => 'Created By',
-    'pages_revisions_date' => 'Revision Date',
+    'pages_md_preview' => 'Pré-Visualização',
+    'pages_md_insert_image' => 'Inserir Imagem',
+    'pages_md_insert_link' => 'Inserir Link para Entidade',
+    'pages_md_insert_drawing' => 'Inserir Desenho',
+    'pages_not_in_chapter' => 'A página não está dentro de um capítulo',
+    'pages_move' => 'Mover Página',
+    'pages_move_success' => 'Pagina movida para ":parentName"',
+    'pages_copy' => 'Copiar Página',
+    'pages_copy_desination' => 'Destino da Cópia',
+    'pages_copy_success' => 'Página copiada com sucesso',
+    'pages_permissions' => 'Permissões da Página',
+    'pages_permissions_success' => 'Permissões da Página atualizadas',
+    'pages_revision' => 'Revisão',
+    'pages_revisions' => 'Revisões da Página',
+    'pages_revisions_named' => 'Revisões de Página para :pageName',
+    'pages_revision_named' => 'Revisão de Página para :pageName',
+    'pages_revision_restored_from' => 'Recuperado de #:id; :summary',
+    'pages_revisions_created_by' => 'Criado por',
+    'pages_revisions_date' => 'Data da Revisão',
     'pages_revisions_number' => '#',
-    'pages_revisions_numbered' => 'Revision #:id',
-    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
-    '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' => 'Edit Content',
-    'pages_permissions_active' => 'Page Permissions Active',
-    'pages_initial_revision' => 'Initial publish',
-    '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_revisions_numbered' => 'Revisão #:id',
+    'pages_revisions_numbered_changes' => 'Alterações da Revisão #:id',
+    'pages_revisions_changelog' => 'Relatório de Alterações',
+    'pages_revisions_changes' => 'Alterações',
+    'pages_revisions_current' => 'Versão Atual',
+    'pages_revisions_preview' => 'Pré-Visualização',
+    'pages_revisions_restore' => 'Restaurar',
+    'pages_revisions_none' => 'Essa página não tem revisões',
+    'pages_copy_link' => 'Copiar Link',
+    'pages_edit_content_link' => 'Editar Conteúdo',
+    'pages_permissions_active' => 'Permissões de Página Ativas',
+    'pages_initial_revision' => 'Publicação Inicial',
+    'pages_initial_name' => 'Nova Página',
+    'pages_editing_draft_notification' => 'Você está atualmente a editar um rascunho que foi guardado pela última vez a :timeDiff.',
+    'pages_draft_edited_notification' => 'Esta página entretanto já foi atualizada. É recomendado que você descarte este rascunho.',
     '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 usuários iniciaram a edição dessa página',
+        'start_b' => ':userName iniciou a edição desta página',
+        'time_a' => 'desde que a página foi atualizada pela última vez',
+        'time_b' => 'nos últimos :minCount minutos',
+        'message' => ':start :time. Tenha cuidado para não sobrescrever atualizações de outras pessoas!',
     ],
-    'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
-    'pages_specific' => 'Specific Page',
-    'pages_is_template' => 'Page Template',
+    'pages_draft_discarded' => 'Rascunho descartado. O editor foi atualizado com o conteúdo atual da página',
+    'pages_specific' => 'Página Específica',
+    'pages_is_template' => 'Modelo de Página',
 
     // Editor Sidebar
-    'page_tags' => 'Page Tags',
-    'chapter_tags' => 'Chapter Tags',
-    'book_tags' => 'Book Tags',
-    'shelf_tags' => 'Shelf Tags',
-    'tag' => 'Tag',
-    'tags' =>  'Tags',
-    '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',
-    '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_items' => 'Attached Items',
-    'attachments_upload' => 'Upload File',
-    'attachments_link' => 'Attach Link',
-    'attachments_set_link' => 'Set Link',
-    'attachments_delete' => 'Are you sure you want to delete this attachment?',
-    'attachments_dropzone' => 'Drop files or click here to attach a file',
-    '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',
+    'page_tags' => 'Etiquetas de Página',
+    'chapter_tags' => 'Etiquetas do Capítulo',
+    'book_tags' => 'Etiquetas do Livro',
+    'shelf_tags' => 'Etiquetas da Prateleira',
+    'tag' => 'Etiqueta',
+    'tags' =>  'Etiquetas',
+    'tag_name' =>  'Nome da Etiqueta',
+    'tag_value' => 'Valor da Etiqueta (Opcional)',
+    'tags_explain' => "Adicione algumas etiquetas para melhor categorizar o seu conteúdo. \n Você poderá atribuir valores às etiquetas para uma organização mais complexa.",
+    'tags_add' => 'Adicionar outra etiqueta',
+    'tags_remove' => 'Remover esta etiqueta',
+    'attachments' => 'Anexos',
+    'attachments_explain' => 'Carregue alguns arquivos ou anexe links para serem exibidos na sua página. Eles estarão visíveis na barra lateral à direita.',
+    'attachments_explain_instant_save' => 'As mudanças são guardadas instantaneamente.',
+    'attachments_items' => 'Itens Anexados',
+    'attachments_upload' => 'Carregamento de Arquivos',
+    'attachments_link' => 'Anexar Link',
+    'attachments_set_link' => 'Definir Link',
+    'attachments_delete' => 'Tem certeza de que deseja eliminar este anexo?',
+    'attachments_dropzone' => 'Arraste arquivos para aqui ou clique para os anexar',
+    'attachments_no_files' => 'Nenhum arquivo foi enviado',
+    'attachments_explain_link' => 'Pode anexar um link se preferir não fazer o carregamento do arquivo. O link poderá ser para uma outra página ou para um arquivo na nuvem.',
+    'attachments_link_name' => 'Nome do Link',
+    'attachment_link' => 'Link do Anexo',
+    'attachments_link_url' => 'Link para o Arquivo',
+    'attachments_link_url_hint' => 'Url do sítio ou arquivo',
+    'attach' => 'Anexar',
+    'attachments_insert_link' => 'Adicionar Link de Anexo à Página',
+    'attachments_edit_file' => 'Editar Arquivo',
+    'attachments_edit_file_name' => 'Nome do Arquivo',
+    'attachments_edit_drop_upload' => 'Arraste arquivos para aqui ou carregue para anexar arquivos e sobrescreve-los',
+    'attachments_order_updated' => 'Ordem dos anexos atualizada',
+    'attachments_updated_success' => 'Detalhes dos anexos atualizados',
+    'attachments_deleted' => 'Anexo eliminado',
+    'attachments_file_uploaded' => 'Carregamento de arquivo efetuado com sucesso',
+    'attachments_file_updated' => 'Arquivo atualizado com sucesso',
+    'attachments_link_attached' => 'Link anexado com sucesso à página',
+    'templates' => 'Modelos',
+    'templates_set_as_template' => 'A página é um modelo',
+    'templates_explain_set_as_template' => 'Pode definir esta página como um modelo para que o seu conteúdo possa ser utilizado para criar outras páginas. Outros usuários poderão utilizar esta página como modelo se tiverem permissão para visualiza-la.',
+    'templates_replace_content' => 'Substituir conteúdo da página',
+    'templates_append_content' => 'Adicionar ao fim do conteúdo da página',
+    'templates_prepend_content' => 'Adicionar ao início do conteúdo da página',
 
     // 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' => 'Utilizador por :time',
+    'profile_created_content' => 'Conteúdo Criado',
+    'profile_not_created_pages' => ':userName não criou páginas',
+    'profile_not_created_chapters' => ':userName não criou capítulos',
+    'profile_not_created_books' => ':userName não criou livros',
+    'profile_not_created_shelves' => ':userName não criou estantes',
 
     // 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_saving' => 'Saving comment...',
-    'comment_deleting' => 'Deleting comment...',
-    'comment_new' => 'New Comment',
-    'comment_created' => 'commented :createDiff',
-    'comment_updated' => 'Updated :updateDiff by :username',
-    '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' => 'Comentário',
+    'comments' => 'Comentários',
+    'comment_add' => 'Adicionar Comentário',
+    'comment_placeholder' => 'Digite aqui os seus comentários',
+    'comment_count' => '{0} Nenhum comentário|{1} 1 Comentário|[2,*] :count Comentários',
+    'comment_save' => 'Guardar comentário',
+    'comment_saving' => 'Guardar comentário...',
+    'comment_deleting' => 'Remover comentário...',
+    'comment_new' => 'Comentário Novo',
+    'comment_created' => 'comentado :createDiff',
+    'comment_updated' => 'A editar :updateDiff por :username',
+    'comment_deleted_success' => 'Comentário removido',
+    'comment_created_success' => 'Comentário adicionado',
+    'comment_updated_success' => 'Comentário editado',
+    'comment_delete_confirm' => 'Tem a certeza de que deseja eliminar este comentário?',
+    'comment_in_reply_to' => 'Em resposta à :commentId',
 
     // 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_delete_success' => 'Revision deleted',
-    'revision_cannot_delete_latest' => 'Cannot delete the latest revision.'
-];
\ No newline at end of file
+    'revision_delete_confirm' => 'Tem a certeza de que deseja eliminar esta revisão?',
+    'revision_restore_confirm' => 'Tem a certeza que deseja restaurar esta revisão? O conteúdo atual da página será substituído.',
+    'revision_delete_success' => 'Revisão excluída',
+    'revision_cannot_delete_latest' => 'Não é possível eliminar a revisão mais recente.'
+];
index 79024e482ed69efa633116592f9b7c83a0bcc93a..011b5b3a25e3bfbb30a1a4b7d6d1968d146af672 100644 (file)
 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' => 'Você não tem permissão para aceder à página requisitada.',
+    'permissionJson' => 'Você não tem permissão para realizar a ação requerida.',
 
     // 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',
-    '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' => 'Um utilizador com o endereço de e-mail :email já existe mas com credenciais diferentes.',
+    'email_already_confirmed' => 'E-mail já foi confirmado. Tente iniciar sessão.',
+    'email_confirmation_invalid' => 'Este token de confirmação não é válido ou já foi utilizado. Por favor, tente registar-se novamente.',
+    'email_confirmation_expired' => 'O token de confirmação já expirou. Um novo e-mail foi enviado.',
+    'email_confirmation_awaiting' => 'O endereço de e-mail da conta em uso precisa ser confirmado',
+    'ldap_fail_anonymous' => 'O acesso LDAP falhou ao tentar usar o anonymous bind',
+    'ldap_fail_authed' => 'O acesso LDAP falhou ao tentar os detalhes do dn e senha fornecidos',
+    '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',
+    'social_no_action_defined' => 'Nenhuma ação definida',
+    'social_login_bad_response' => "Erro recebido durante o inicio de sessão :socialAccount: \n:error",
+    'social_account_in_use' => 'Esta conta :socialAccount já está em uso. Por favor, tente entrar utilizando a opção :socialAccount.',
+    'social_account_email_in_use' => 'O e-mail :email já está em uso. Se já possui uma conta poderá ligar a sua conta :socialAccount a partir das configurações do seu perfil.',
+    'social_account_existing' => 'Esta conta :socialAccount já está vinculada a este perfil.',
+    'social_account_already_used_existing' => 'Esta conta :socialAccount já está a ser utilizada por outro utilizador.',
+    'social_account_not_used' => 'Esta conta :socialAccount não está vinculada a nenhum utilizador. Por favor vincule a conta nas suas configurações de perfil. ',
+    'social_account_register_instructions' => 'Se não possui uma conta, poderá registar-se utilizando a opção :socialAccount.',
+    'social_driver_not_found' => 'Social driver não encontrado',
+    'social_driver_not_configured' => 'Os seus parâmetros sociais de :socialAccount não estão corretamente configurados.',
+    'invite_token_expired' => 'Este link de convite expirou. Alternativamente, pode tentar redefinir a senha da sua conta.',
 
     // 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.',
-    'uploaded'  => 'The server does not allow uploads of this size. Please try a smaller file size.',
-    'image_upload_error' => 'An error occurred uploading the image',
-    'image_upload_type_error' => 'The image type being uploaded is invalid',
-    'file_upload_timeout' => 'The file upload has timed out.',
+    'path_not_writable' => 'O caminho do arquivo :filePath não pôde ser carregado. Certifique-se de que tem permissões de escrita no servidor.',
+    '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.',
+    'uploaded'  => 'O servidor não permite o carregamento de arquivos com esse tamanho. Por favor, tente fazer o carregamento de arquivos mais pequenos.',
+    'image_upload_error' => 'Ocorreu um erro no carregamento da imagem',
+    'image_upload_type_error' => 'O tipo de imagem enviada é inválida',
+    'file_upload_timeout' => 'O carregamento do arquivo expirou.',
 
     // Attachments
-    'attachment_not_found' => 'Attachment not found',
+    'attachment_not_found' => 'Anexo não encontrado',
 
     // Pages
-    'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
-    'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage',
+    'page_draft_autosave_fail' => 'Falha ao tentar guardar o rascunho. Certifique-se que a conexão de Internet está funcional antes de tentar guardar esta página',
+    'page_custom_home_deletion' => 'Não é possível eliminar uma página que está definida como página inicial',
 
     // Entities
-    'entity_not_found' => 'Entity not found',
-    'bookshelf_not_found' => 'Bookshelf 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' => 'Entidade não encontrada',
+    'bookshelf_not_found' => 'Estante de Livros não encontrada',
+    'book_not_found' => 'Livro não encontrado',
+    'page_not_found' => 'Página não encontrada',
+    'chapter_not_found' => 'Capítulo não encontrado',
+    'selected_book_not_found' => 'O livro selecionado não foi encontrado',
+    'selected_book_chapter_not_found' => 'O Livro ou Capítulo selecionado não foi encontrado',
+    'guests_cannot_save_drafts' => 'Convidados não podem guardar rascunhos',
 
     // 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' => 'Não pode excluir o único administrador',
+    'users_cannot_delete_guest' => 'Não pode excluir o usuário convidado',
 
     // 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' => 'Este cargo não pode ser editado',
+    'role_system_cannot_be_deleted' => 'Este cargo é um cargo do sistema e não pode ser excluído',
+    'role_registration_default_cannot_delete' => 'Este cargo não poderá se excluído enquanto estiver definido como o cargo padrão',
+    'role_cannot_remove_only_admin' => 'Este utilizador é o único vinculado ao cargo de administrador. Atribua o cargo de administrador a outro antes de tentar removê-lo aqui.',
 
     // 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' => 'Ocorreu um erro ao recolher os comentários.',
+    'cannot_add_comment_to_draft' => 'Não pode adicionar comentários a um rascunho.',
+    'comment_add' => 'Ocorreu um erro ao adicionar/atualizar o comentário.',
+    'comment_delete' => 'Ocorreu um erro ao eliminar o comentário.',
+    'empty_comment' => 'Não é possível adicionar um comentário vazio.',
 
     // 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.',
-    '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' => 'Página Não Encontrada',
+    'sorry_page_not_found' => 'Desculpe, a página que procura não foi encontrada.',
+    'sorry_page_not_found_permission_warning' => 'Se esperava que esta página existisse, talvez não tenha permissão para visualizá-la.',
+    'image_not_found' => 'Imagem não encontrada',
+    'image_not_found_subtitle' => 'Desculpe, o arquivo de imagem que estava à procura não foi encontrado.',
+    'image_not_found_details' => 'Se estava à espera que a mesma existisse é possível que tenha sido eliminada.',
+    'return_home' => 'Regressar à página inicial',
+    'error_occurred' => 'Ocorreu um Erro',
+    'app_down' => ':appName está fora do ar de momento',
+    'back_soon' => 'Voltaremos em breve.',
 
     // 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' => 'Nenhum token de autorização encontrado na requisição',
+    'api_bad_authorization_format' => 'Um token de autorização foi encontrado na requisição, mas o formato parece incorreto',
+    'api_user_token_not_found' => 'Nenhum token de API correspondente foi encontrado para o token de autorização fornecido',
+    'api_incorrect_token_secret' => 'O segredo fornecido para o token de API usado está incorreto',
+    'api_user_no_api_permission' => 'O proprietário do token de API utilizado não tem permissão para fazer requisições de API',
+    'api_user_token_expired' => 'O token de autenticação expirou',
 
     // Settings & Maintenance
-    'maintenance_test_email_failure' => 'Error thrown when sending a test email:',
+    'maintenance_test_email_failure' => 'Erro lançado ao enviar um e-mail de teste:',
 
 ];
index 85bd12fc319557dcc852fdacc07e882583d66be3..1870e34184138478210e19a2ae44f2417aa0a2ba 100644 (file)
@@ -6,7 +6,7 @@
  */
 return [
 
-    'previous' => '&laquo; Previous',
-    'next'     => 'Next &raquo;',
+    'previous' => '&laquo; Anterior',
+    'next'     => 'Seguinte &raquo;',
 
 ];
index b408f3c2fdaf1e80e9cdafa36ae9507db9fbda48..e468b9f683895dee641b0f6b83781fce89d3bc3c 100644 (file)
@@ -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' => 'As palavras-passe devem ter no mínimo oito caracteres e serem iguais à confirmação.',
+    'user' => "Não pudemos encontrar um utilizador com o endereço de e-mail fornecido.",
+    'token' => 'O token de redefinição de senha é inválido para este endereço de e-mail.',
+    'sent' => 'Enviamos o link de redefinição de palavra-passe para o seu e-mail!',
+    'reset' => 'A sua palavra-passe foi redefinida com sucesso!',
 
 ];
index 2bd314cf0f28561f9a2f296b373483df42480f89..3222d218dbd9b0e6754db139659c218547974843 100644 (file)
 return [
 
     // Common Messages
-    'settings' => 'Settings',
-    'settings_save' => 'Save Settings',
-    'settings_save_success' => 'Settings saved',
+    'settings' => 'Configurações',
+    'settings_save' => 'Guardar Configurações',
+    'settings_save_success' => 'Configurações guardadas',
 
     // 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_editor' => 'Page Editor',
-    'app_editor_desc' => 'Select which editor will be used by all users to edit pages.',
-    'app_custom_html' => 'Custom HTML Head Content',
-    'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> 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 image should be 43px in height. <br>Large images will be scaled down.',
-    'app_primary_color' => 'Application Primary Color',
-    'app_primary_color_desc' => 'Sets the primary color for the application including the banner, buttons, and links.',
-    '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_disable_comments' => 'Disable Comments',
-    'app_disable_comments_toggle' => 'Disable comments',
-    'app_disable_comments_desc' => 'Disables comments across all pages in the application. <br> Existing comments are not shown.',
+    'app_customization' => 'Personalização',
+    'app_features_security' => 'Recursos & Segurança',
+    'app_name' => 'Nome da Aplicação',
+    'app_name_desc' => 'Este nome será mostrado no cabeçalho e em e-mails.',
+    'app_name_header' => 'Mostrar o nome no cabeçalho',
+    'app_public_access' => 'Acesso Público',
+    'app_public_access_desc' => 'Ativar esta opção irá permitir que os visitantes que não estão autenticados, acedam ao conteúdo da sua instância do BookStack.',
+    'app_public_access_desc_guest' => 'O acesso de visitantes públicos pode ser controlado através do utilizador "Convidado".',
+    'app_public_access_toggle' => 'Permitir acesso público',
+    'app_public_viewing' => 'Permitir visualização pública?',
+    'app_secure_images' => 'Carregamento de Imagens mais Seguro',
+    'app_secure_images_toggle' => 'Ativar o carregamento de imagem mais seguro',
+    'app_secure_images_desc' => 'Por razões de performance, todas as imagens são públicas. Esta opção adiciona uma string aleatória na frente das Urls de imagens. Certifique-se de que os diretórios não possam ser indexados para prevenir acesso indesejado.',
+    'app_editor' => 'Editor de Página',
+    'app_editor_desc' => 'Selecione qual editor será utilizado pelos utilizadores ao editar páginas.',
+    'app_custom_html' => 'Conteúdo personalizado para para o Head do HTML',
+    'app_custom_html_desc' => 'Quaisquer conteúdos aqui adicionados serão inseridos no final da secção <head> de cada página. Esta é uma maneira útil de sobrescrever estilos e adicionar códigos de análise de site.',
+    'app_custom_html_disabled_notice' => 'O conteúdo personalizado do <head> HTML está desativado nesta página de configurações, para garantir que quaisquer alterações que acabem maliciosas possam ser revertidas.',
+    'app_logo' => 'Logo da Aplicação',
+    'app_logo_desc' => 'A imagem deve ter 43px de altura. <br>Imagens maiores serão reduzidas.',
+    'app_primary_color' => 'Cor Primária da Aplicação',
+    'app_primary_color_desc' => 'Define a cor primária para a aplicação, incluindo o banner, botões e links.',
+    'app_homepage' => 'Página Inicial',
+    'app_homepage_desc' => 'Selecione uma opção para ser exibida como página inicial em vez da padrão. Permissões de página serão ignoradas para as páginas selecionadas.',
+    'app_homepage_select' => 'Selecione uma página',
+    'app_footer_links' => 'Links do Rodapé',
+    'app_footer_links_desc' => 'Adicionar links para mostrar dentro do rodapé do site. Estes serão exibidos no rodapé da maioria das páginas, incluindo as que não requerem autenticação. Pode utilizar uma etiqueta de "trans::<key>" para utilizar traduções definidas pelo sistema. Por exemplo: Utilizando "trans::common.privacy_policy" fornecerá o texto traduzido "Política de Privacidade" e "trans::common.terms_of_service" fornecerá o texto traduzido "Termos de Serviço".',
+    'app_footer_links_label' => 'Etiqueta do Link',
+    'app_footer_links_url' => 'URL do link',
+    'app_footer_links_add' => 'Adicionar Link de Rodapé',
+    'app_disable_comments' => 'Desativar Comentários',
+    'app_disable_comments_toggle' => 'Desativar comentários',
+    'app_disable_comments_desc' => 'Desativar comentários em todas as páginas no aplicativo.<br> Comentários existentes não serão exibidos.',
 
     // Color settings
-    'content_colors' => 'Content Colors',
-    'content_colors_desc' => 'Sets 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',
+    'content_colors' => 'Cores do Conteúdo',
+    'content_colors_desc' => 'Define as cores para todos os elementos da hierarquia de organização de páginas. Escolher cores com brilho similar ao das cores padrão é aconselhável para a legibilidade.',
+    'bookshelf_color' => 'Cor da Prateleira',
+    'book_color' => 'Cor do Livro',
+    'chapter_color' => 'Cor do Capítulo',
+    'page_color' => 'Cor da Página',
+    'page_draft_color' => 'Cor do Rascunho',
 
     // 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. <br> Note that users will be able to change their email addresses after successful registration.',
-    'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
+    'reg_settings' => 'Cadastro',
+    'reg_enable' => 'Habilitar Cadastro',
+    'reg_enable_toggle' => 'Habilitar cadastro',
+    'reg_enable_desc' => 'Quando o cadastro é habilitado, visitantes poderão cadastrar-se como usuários do aplicativo. Realizado o cadastro, recebem um único cargo padrão.',
+    'reg_default_role' => 'Cargo padrão para usuários após o cadastro',
+    'reg_enable_external_warning' => 'A opção acima é ignorada enquanto a autenticação externa LDAP ou SAML estiver ativa. Contas de usuários para membros não existentes serão criadas automaticamente se a autenticação pelo sistema externo em uso for bem sucedida.',
+    'reg_email_confirmation' => 'Confirmação de E-mail',
+    'reg_email_confirmation_toggle' => 'Requerer confirmação de e-mail',
+    'reg_confirm_email_desc' => 'Em caso da restrição de domínios estar em uso, a confirmação de e-mail será requerida e esta opção será ignorada.',
+    'reg_confirm_restrict_domain' => 'Restrição de Domínios',
+    'reg_confirm_restrict_domain_desc' => 'Entre com uma lista separada por vírgulas de domínios de e-mails aos quais você deseja restringir o registo. Um e-mail de confirmação será enviado para o utilizador validar o seu respetivo endereço de e-mail antes de ser permitida a interação com a aplicação. <br> Note que os utilizadores serão capazes de alterar os seus endereços de e-mail após o sucesso na confirmação do registo.',
+    'reg_confirm_restrict_domain_placeholder' => 'Nenhuma restrição definida',
 
     // 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_image_cleanup_ignore_revisions' => 'Ignore images in 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' => 'Manutenção',
+    'maint_image_cleanup' => 'Limpeza de Imagens',
+    'maint_image_cleanup_desc' => "Examina páginas e reviste os seus conteúdos para verificar quais imagens e desenhos estão atualmente em uso e quais são redundantes. Certifique-se de criar uma cópia de segurança completa da base de dados e imagens antes de executar esta ação.",
+    'maint_delete_images_only_in_revisions' => 'Eliminar também imagens que existam apenas em revisões de página antigas',
+    'maint_image_cleanup_run' => 'Executar Limpeza',
+    'maint_image_cleanup_warning' => ':count imagens potencialmente não utilizadas foram encontradas. Tem certeza de que deseja eliminar estas imagens?',
+    'maint_image_cleanup_success' => ':count imagens potencialmente não utilizadas foram encontradas e eliminadas!',
+    'maint_image_cleanup_nothing_found' => 'Nenhuma imagem por utilizar foi encontrada, nada foi eliminado!',
+    'maint_send_test_email' => 'Enviar um E-mail de Teste',
+    'maint_send_test_email_desc' => 'Esta opção envia um e-mail de teste para o endereço especificado no seu perfil.',
+    'maint_send_test_email_run' => 'Enviar e-mail de teste',
+    'maint_send_test_email_success' => 'E-mail enviado para :address',
+    'maint_send_test_email_mail_subject' => 'E-mail de Teste',
+    'maint_send_test_email_mail_greeting' => 'O envio de e-mails parece funcionar!',
+    'maint_send_test_email_mail_text' => 'Parabéns! Já que recebeu esta notificação, as suas opções de e-mail parecem estar configuradas corretamente.',
+    'maint_recycle_bin_desc' => 'Estantes, livros, capítulos e páginas eliminados são mandados para a reciclagem podendo assim ser restaurados ou excluídos permanentemente. Itens mais antigos da podem vir a ser automaticamente removidos da reciclagem após um tempo, dependendo da configuração do sistema.',
+    'maint_recycle_bin_open' => 'Abrir Reciclagem',
+
+    // Recycle Bin
+    'recycle_bin' => 'Reciclagem',
+    'recycle_bin_desc' => 'Aqui pode restaurar itens que foram eliminados ou eliminá-los permanentemente do sistema. Esta lista não é filtrada diferentemente de listas de atividades parecidas no sistema onde filtros de permissão são aplicados.',
+    'recycle_bin_deleted_item' => 'Item eliminado',
+    'recycle_bin_deleted_by' => 'Eliminado por',
+    'recycle_bin_deleted_at' => 'Data de Eliminação',
+    'recycle_bin_permanently_delete' => 'Eliminar permanentemente',
+    'recycle_bin_restore' => 'Restaurar',
+    'recycle_bin_contents_empty' => 'A reciclagem está atualmente vazia',
+    'recycle_bin_empty' => 'Esvaziar Reciclagem',
+    'recycle_bin_empty_confirm' => 'Isto irá destruir permanentemente todos os itens na reciclagem inclusive o conteúdo de cada item. Tem certeza de que a deseja esvaziar?',
+    'recycle_bin_destroy_confirm' => 'Esta ação irá excluir permanentemente do sistema este item junto com todos os elementos filhos listados abaixo. Não poderá restaurar este conteúdo. Tem certeza de que deseja excluir permanentemente este item?',
+    'recycle_bin_destroy_list' => 'Itens a serem Destruídos',
+    'recycle_bin_restore_list' => 'Itens a serem Restaurados',
+    'recycle_bin_restore_confirm' => 'Esta ação irá restaurar o item excluído, inclusive quaisquer elementos filhos, para o seu local original. Se a localização original tiver, entretanto, sido eliminada e estiver agora na reciclagem, o item pai também precisará de ser restaurado.',
+    'recycle_bin_restore_deleted_parent' => 'O parente deste item foi também eliminado. Estes permanecerão eliminados até que o parente seja também restaurado.',
+    'recycle_bin_destroy_notification' => 'Eliminados no total :count itens da lixeira.',
+    'recycle_bin_restore_notification' => 'Restaurados no total :count itens da reciclagem.',
 
     // 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_item' => 'Related Item',
-    'audit_table_date' => 'Activity Date',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit' => 'Registo de auditoria',
+    'audit_desc' => 'Este registo de auditoria exibe uma lista de atividades rastreadas no sistema. Esta lista não é filtrada ao contrário de listas de atividades semelhantes no sistema onde os filtros de permissão são aplicados.',
+    'audit_event_filter' => 'Filtro de Evento',
+    'audit_event_filter_no_filter' => 'Sem filtro',
+    'audit_deleted_item' => 'Item excluído',
+    'audit_deleted_item_name' => 'Nome: :name',
+    'audit_table_user' => 'Utilizador',
+    'audit_table_event' => 'Evento',
+    'audit_table_related' => 'Item ou Detalhe Relacionado',
+    'audit_table_date' => 'Data da Atividade',
+    'audit_date_from' => 'Intervalo De',
+    'audit_date_to' => 'Intervalo Até',
 
     // Role Settings
-    'roles' => 'Roles',
-    'role_user_roles' => 'User Roles',
-    'role_create' => 'Create New Role',
-    'role_create_success' => 'Role successfully created',
-    '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_delete_success' => 'Role successfully deleted',
-    'role_edit' => 'Edit Role',
-    'role_details' => 'Role Details',
-    'role_name' => 'Role Name',
-    'role_desc' => 'Short Description of Role',
-    '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_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_all' => 'All',
-    'role_own' => 'Own',
-    'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
-    'role_save' => 'Save Role',
-    'role_update_success' => 'Role successfully updated',
-    'role_users' => 'Users in this role',
-    'role_users_none' => 'No users are currently assigned to this role',
+    'roles' => 'Cargos',
+    'role_user_roles' => 'Cargos de Utilizador',
+    'role_create' => 'Criar novo Cargo',
+    'role_create_success' => 'Cargo criado com sucesso',
+    'role_delete' => 'Excluir Cargo',
+    'role_delete_confirm' => 'A ação vai eliminar o cargo de nome \':roleName\'.',
+    'role_delete_users_assigned' => 'Esse cargo tem :userCount utilizadores vinculados nele. Se quiser migrar utilizadores deste cargo para outro, selecione um novo cargo.',
+    'role_delete_no_migration' => "Não migrar utilizadores",
+    'role_delete_sure' => 'Tem certeza que deseja excluir este cargo?',
+    'role_delete_success' => 'Cargo excluído com sucesso',
+    'role_edit' => 'Editar Cargo',
+    'role_details' => 'Detalhes do Cargo',
+    'role_name' => 'Nome do Cargo',
+    'role_desc' => 'Breve Descrição do Cargo',
+    'role_external_auth_id' => 'IDs de Autenticação Externa',
+    'role_system' => 'Permissões do Sistema',
+    'role_manage_users' => 'Gerir utilizadores',
+    'role_manage_roles' => 'Gerir cargos e permissões de cargos',
+    'role_manage_entity_permissions' => 'Gerir todos os livros, capítulos e permissões de páginas',
+    'role_manage_own_entity_permissions' => 'Gerir permissões de seu próprio livro, capítulo e paginas',
+    'role_manage_page_templates' => 'Gerir modelos de página',
+    'role_access_api' => 'Aceder à API do sistema',
+    'role_manage_settings' => 'Gerir as configurações da aplicação',
+    'role_asset' => 'Permissões de Ativos',
+    'roles_system_warning' => 'Esteja ciente de que o acesso a qualquer uma das três permissões acima pode permitir que um utilizador altere os seus próprios privilégios ou privilégios de outros no sistema. Apenas atribua cargos com essas permissões a utilizadores de confiança.',
+    'role_asset_desc' => 'Estas permissões controlam o acesso padrão para os ativos dentro do sistema. Permissões em Livros, Capítulos e Páginas serão sobrescritas por estas permissões.',
+    'role_asset_admins' => 'Os administradores recebem automaticamente acesso a todo o conteúdo, mas estas opções podem mostrar ou ocultar as opções da Interface de Usuário.',
+    'role_all' => 'Todos',
+    'role_own' => 'Próprio',
+    'role_controlled_by_asset' => 'Controlado pelo ativo para o qual eles são enviados',
+    'role_save' => 'Guardar Cargo',
+    'role_update_success' => 'Cargo atualizado com sucesso',
+    'role_users' => 'Utilizadores com este cargo',
+    'role_users_none' => 'Nenhum utilizador está atualmente vinculado a este cargo',
 
     // Users
-    'users' => 'Users',
-    'user_profile' => 'User Profile',
-    'users_add_new' => 'Add New User',
-    'users_search' => 'Search Users',
-    '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 6 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' => 'This is the ID used to match this user when communicating with your external authentication system.',
-    'users_password_warning' => 'Only fill the below if you would like to change your password.',
-    '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_delete_success' => 'Users successfully removed',
-    'users_edit' => 'Edit User',
-    'users_edit_profile' => 'Edit Profile',
-    'users_edit_success' => 'User successfully updated',
-    '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_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_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_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' => 'Utilizadores',
+    'user_profile' => 'Perfil do Utilizador',
+    'users_add_new' => 'Adicionar Novo Utilizador',
+    'users_search' => 'Pesquisar Utilizadores',
+    'users_latest_activity' => 'Última atividade',
+    'users_details' => 'Detalhes do Utilizador',
+    'users_details_desc' => 'Defina um nome de exibição e um endereço de e-mail para este utilizador. O endereço de e-mail será utilizado na autenticação da aplicação.',
+    'users_details_desc_no_email' => 'Defina um nome de exibição para este utilizador para que outros possam reconhecê-lo.',
+    'users_role' => 'Cargos do Utilizador',
+    'users_role_desc' => 'Selecione os cargos aos quais este utilizador será vinculado. Se um utilizador for vinculado a múltiplos cargos, as suas permissões serão empilhadas e ele receberá todas as habilidades dos cargos atribuídos.',
+    'users_password' => 'Palavra-passe do Utilizador',
+    'users_password_desc' => 'Defina uma palavra-passe utilizada para efetuar a autenticação na aplicação. Esta deve ter pelo menos 6 caracteres.',
+    'users_send_invite_text' => 'Pode escolher enviar a este utilizador um convite por e-mail que o possibilitará definir a sua própria palavra-passe, ou defina você mesmo uma.',
+    'users_send_invite_option' => 'Enviar convite por e-mail',
+    'users_external_auth_id' => 'ID de Autenticação Externa',
+    'users_external_auth_id_desc' => 'Este ID é utilizado para relacionar um utilizador ao comunicar com um sistema de autenticação externo.',
+    'users_password_warning' => 'Apenas preencha os dados abaixo caso queira modificar a sua palavra-passe.',
+    'users_system_public' => 'Este utilizador representa quaisquer convidados que visitam a aplicação. Não pode ser utilizado para efetuar autenticação mas é automaticamente atribuído.',
+    'users_delete' => 'Eliminar Utilizador',
+    'users_delete_named' => 'Eliminar :userName',
+    'users_delete_warning' => 'A ação vai eliminar completamente o utilizador de nome \':userName\' do sistema.',
+    'users_delete_confirm' => 'Tem certeza que eliminar este utilizador?',
+    'users_migrate_ownership' => 'Migrar Posse',
+    'users_migrate_ownership_desc' => 'Selecione um utilizador aqui se desejar que outro se torne o proprietário de todos os itens atualmente pertencentes a este.',
+    'users_none_selected' => 'Nenhum utilizador selecionado',
+    'users_delete_success' => 'Utilizador removido com sucesso',
+    'users_edit' => 'Editar Utilizador',
+    'users_edit_profile' => 'Editar Perfil',
+    'users_edit_success' => 'Utilizador atualizado com sucesso',
+    'users_avatar' => 'Avatar do Utilizador',
+    'users_avatar_desc' => 'Defina uma imagem para representar este utilizador. Deve ser um quadrado com aproximadamente 256px de altura e largura.',
+    'users_preferred_language' => 'Linguagem de Preferência',
+    'users_preferred_language_desc' => 'Esta opção irá alterar o idioma utilizado para a interface de utilizador da aplicação. Isto não afetará nenhum conteúdo criado por utilizadores.',
+    'users_social_accounts' => 'Contas Sociais',
+    '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_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',
+    'users_api_tokens_none' => 'Nenhum token de API foi criado para este utilizador',
+    'users_api_tokens_create' => 'Criar Token',
+    'users_api_tokens_expires' => 'Expira',
+    'users_api_tokens_docs' => 'Documentação da API',
 
     // 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_create_success' => 'API token successfully created',
-    'user_api_token_update_success' => 'API token successfully updated',
-    '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_delete_success' => 'API token successfully deleted',
+    'user_api_token_create' => 'Criar Token de API',
+    'user_api_token_name' => 'Nome',
+    'user_api_token_name_desc' => 'Dê ao seu token um nome legível como um futuro lembrete de seu propósito.',
+    'user_api_token_expiry' => 'Data de Expiração',
+    'user_api_token_expiry_desc' => 'Defina uma data em que este token expira. Depois desta data, as requisições feitas usando este token deixarão de funcionar. Deixar este campo em branco definirá um prazo de 100 anos futuros.',
+    'user_api_token_create_secret_message' => 'Imediatamente após a criação deste token, um "ID de token" e "Segredo de token" serão gerados e exibidos. O segredo só será mostrado uma única vez, portanto, certifique-se de copiar o valor para algum lugar seguro antes de prosseguir.',
+    'user_api_token_create_success' => 'Token de API criado com sucesso',
+    'user_api_token_update_success' => 'Token de API atualizado com sucesso',
+    'user_api_token' => 'Token de API',
+    'user_api_token_id' => 'ID do Token',
+    'user_api_token_id_desc' => 'Este é um identificador de sistema não editável, gerado para este token, que precisará ser fornecido em solicitações de API.',
+    'user_api_token_secret' => 'Segredo do Token',
+    'user_api_token_secret_desc' => 'Este é um segredo de sistema gerado para este token que precisará ser fornecido em requisições de API. Isto só será mostrado nesta única vez, portanto, copie este valor para um lugar seguro.',
+    'user_api_token_created' => 'Token criado a :timeAgo',
+    'user_api_token_updated' => 'Token atualizado a :timeAgo',
+    'user_api_token_delete' => 'Eliminar Token',
+    'user_api_token_delete_warning' => 'Isto irá excluir completamente este token de API com o nome \':tokenName\' do sistema.',
+    'user_api_token_delete_confirm' => 'Tem certeza que deseja eliminar este token de API?',
+    'user_api_token_delete_success' => 'Token de API excluído com sucesso',
 
     //! If editing translations files directly please ignore this in all
     //! languages apart from en. Content will be auto-copied from en.
@@ -200,6 +230,9 @@ return [
     'language_select' => [
         'en' => 'English',
         'ar' => 'العربية',
+        'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Catalão',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -209,11 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
+        'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 76b57a2a3b58ddb8ef41e0562c5187359cc6e542..b36ed0dab7b83a508da381c7b652cade9cad1664 100644 (file)
 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.',
-    'before'               => 'The :attribute must be a date before :date.',
+    'accepted'             => 'O campo :attribute deve ser aceite.',
+    'active_url'           => 'O campo :attribute não é um URL válido.',
+    'after'                => 'O campo :attribute deve ser uma data posterior à data :date.',
+    'alpha'                => 'O campo :attribute deve conter apenas letras.',
+    'alpha_dash'           => 'O campo :attribute deve conter apenas letras, números, traços e sublinhado.',
+    'alpha_num'            => 'O campo :attribute deve conter apenas letras e números.',
+    'array'                => 'O campo :attribute deve ser uma lista(array).',
+    'before'               => 'O campo :attribute deve ser uma data anterior à data :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' => 'O campo :attribute deve estar entre :min e :max.',
+        'file'    => 'O campo :attribute deve ter entre :min e :max kilobytes.',
+        'string'  => 'O campo :attribute deve ter entre :min e :max caracteres.',
+        'array'   => 'O campo :attribute deve ter entre :min e :max itens.',
     ],
-    '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',
-    'filled'               => 'The :attribute field is required.',
+    'boolean'              => 'O campo :attribute deve ser verdadeiro ou falso.',
+    'confirmed'            => 'O campo :attribute não é igual à sua confirmação.',
+    'date'                 => 'O campo :attribute não está num formato de data válido.',
+    'date_format'          => 'O campo :attribute não tem a formatação :format.',
+    'different'            => 'O campo :attribute e o campo :other devem ser diferentes.',
+    'digits'               => 'O campo :attribute deve ter :digits dígitos.',
+    'digits_between'       => 'O campo :attribute deve ter entre :min e :max dígitos.',
+    'email'                => 'O campo :attribute deve ser um endereço de e-mail válido.',
+    'ends_with' => 'O campo :attribute deve terminar com um dos seguintes: :values',
+    'filled'               => 'O campo :attribute é requerido.',
     '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' => 'O campo :attribute deve ser maior que :value.',
+        'file'    => 'O campo :attribute deve ser maior que :value kilobytes.',
+        'string'  => 'O campo :attribute deve ser maior que :value caracteres.',
+        'array'   => 'O campo :attribute deve ter mais que :value itens.',
     ],
     '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' => 'O campo :attribute deve ser maior ou igual a :value.',
+        'file'    => 'O campo :attribute deve ser maior ou igual a :value kilobytes.',
+        'string'  => 'O campo :attribute deve ser maior ou igual a :value caracteres.',
+        'array'   => 'O campo :attribute deve ter :value itens ou mais.',
     ],
-    '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'               => 'O campo :attribute selecionado não é válido.',
+    'image'                => 'O campo :attribute deve ser uma imagem.',
+    'image_extension'      => 'O campo :attribute deve ter uma extensão de imagem válida e suportada.',
+    'in'                   => 'O campo :attribute selecionado não é válido.',
+    'integer'              => 'O campo :attribute deve ser um número inteiro.',
+    'ip'                   => 'O campo :attribute deve ser um endereço IP válido.',
+    'ipv4'                 => 'O campo :attribute deve ser um endereço IPv4 válido.',
+    'ipv6'                 => 'O campo :attribute deve ser um endereço IPv6 válido.',
+    'json'                 => 'O campo :attribute deve ser uma string JSON válida.',
     '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' => 'O campo :attribute deve ser menor que :value.',
+        'file'    => 'O campo :attribute deve ser menor que :value kilobytes.',
+        'string'  => 'O campo :attribute deve ser menor que :value caracteres.',
+        'array'   => 'O campo :attribute deve conter menos que :value itens.',
     ],
     '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' => 'O campo :attribute deve ser menor ou igual a :value.',
+        'file'    => 'O campo :attribute deve ser menor ou igual a :value kilobytes.',
+        'string'  => 'O campo :attribute deve ser menor ou igual a :value caracteres.',
+        'array'   => 'O campo :attribute não deve conter mais que :value itens.',
     ],
     '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' => 'O valor para o campo :attribute não deve ser maior que :max.',
+        'file'    => 'O valor para o campo :attribute não deve ter tamanho maior que :max kilobytes.',
+        'string'  => 'O valor para o campo :attribute não deve ter mais que :max caracteres.',
+        'array'   => 'O valor para o campo :attribute não deve ter mais que :max itens.',
     ],
-    'mimes'                => 'The :attribute must be a file of type: :values.',
+    'mimes'                => 'O campo :attribute deve ser do tipo type: :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' => 'O campo :attribute não deve ser menor que :min.',
+        'file'    => 'O campo :attribute não deve ter tamanho menor que :min kilobytes.',
+        'string'  => 'O campo :attribute não deve ter menos que :min caracteres.',
+        'array'   => 'O campo :attribute não deve ter menos que :min itens.',
     ],
-    'no_double_extension'  => 'The :attribute must only have a single file extension.',
-    '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.',
+    'not_in'               => 'O campo selecionado :attribute é inválido.',
+    'not_regex'            => 'O formato do campo :attribute é inválido.',
+    'numeric'              => 'O campo :attribute deve ser um número.',
+    'regex'                => 'O formato do campo :attribute é inválido.',
+    'required'             => 'O campo :attribute é requerido.',
+    'required_if'          => 'O campo :attribute é requerido quando o campo :other tem valor :value.',
+    'required_with'        => 'O campo :attribute é requerido quando os valores :values estiverem presentes.',
+    'required_with_all'    => 'O campo :attribute é requerido quando os valores :values estiverem presentes.',
+    'required_without'     => 'O campo :attribute é requerido quando os valores :values não estiverem presentes.',
+    'required_without_all' => 'O campo :attribute é requerido quando nenhum dos valores :values estiverem presentes.',
+    'same'                 => 'O campo :attribute e o campo :other devem ser iguais.',
+    'safe_url'             => 'O link fornecido poderá não ser seguro.',
     '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' => 'O tamanho do campo :attribute deve ser :size.',
+        'file'    => 'O tamanho do arquivo :attribute deve ser de :size kilobytes.',
+        'string'  => 'O tamanho do campo :attribute deve ser de :size caracteres.',
+        'array'   => 'O campo :attribute deve conter :size itens.',
     ],
-    'string'               => 'The :attribute must be a string.',
-    'timezone'             => 'The :attribute must be a valid zone.',
-    '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'               => 'O campo :attribute deve ser uma string.',
+    'timezone'             => 'O campo :attribute deve conter uma timezone válida.',
+    'unique'               => 'Já existe um campo/dado de nome :attribute.',
+    'url'                  => 'O formato da URL :attribute é inválido.',
+    'uploaded'             => 'O arquivo não pôde ser carregado. O servidor pode não aceitar arquivos deste tamanho.',
 
     // Custom validation lines
     'custom' => [
         'password-confirm' => [
-            'required_with' => 'Password confirmation required',
+            'required_with' => 'Confirmação de senha requerida',
         ],
     ],
 
index 2fa3d65717f82856b0ddfe62f33b6cc487c54649..33781417f755840ae0819234d2f065e2c03374e1 100644 (file)
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'comentou em',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => 'atualizou permissões',
 ];
index 8b0dffe6e844cfbe55480cd7b7aaf57540df47d9..d2d544901179d61d0e846f3845a2627f22c905d2 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Ordenação Crescente',
     'sort_descending' => 'Ordenação Decrescente',
     'sort_name' => 'Nome',
+    'sort_default' => 'Padrão',
     'sort_created_at' => 'Data de Criação',
     'sort_updated_at' => 'Data de Atualização',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Caminho',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Menu de Perfil',
     'view_profile' => 'Visualizar Perfil',
     'edit_profile' => 'Editar Perfil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informações',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Conteúdo',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Se você estiver tendo problemas ao clicar o botão ":actionText", copie e cole a URL abaixo no seu navegador:',
     'email_rights' => 'Todos os direitos reservados',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Políticas de Privacidade',
+    'terms_of_service' => 'Termos de Serviço',
 ];
index 129e1f260876dabb5a63088bc9f2e26ec523ad59..e67dfc8c37b7ba5819cb4ed5a5cc715203429953 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Criado :timeLength por :user',
     'meta_updated' => 'Atualizado :timeLength',
     'meta_updated_name' => 'Atualizado :timeLength por :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'De :user',
     'entity_select' => 'Seleção de Entidade',
     'images' => 'Imagens',
     'my_recent_drafts' => 'Meus Rascunhos Recentes',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Uma vez habilitadas, estas permissões terão prioridade sobre outro conjunto de permissões.',
     'permissions_enable' => 'Habilitar Permissões Customizadas',
     'permissions_save' => 'Salvar Permissões',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Proprietário',
 
     // Search
     'search_results' => 'Resultado(s) da Pesquisa',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Permissão definida',
     'search_created_by_me' => 'Criado por mim',
     'search_updated_by_me' => 'Atualizado por mim',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Opções de Data',
     'search_updated_before' => 'Atualizado antes de',
     'search_updated_after' => 'Atualizado depois de',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Criar Novo Capítulo',
     'chapters_delete' => 'Excluir Capítulo',
     'chapters_delete_named' => 'Excluir Capítulo :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_explain' => 'Isto irá excluir o capítulo com o nome \':chapterName\'. Todas as páginas que existem neste capítulo também serão excluídas.',
     'chapters_delete_confirm' => 'Tem certeza que deseja excluir o capítulo?',
     'chapters_edit' => 'Editar Capítulo',
     'chapters_edit_named' => 'Editar Capítulo :chapterName',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Revisões da Página',
     'pages_revisions_named' => 'Revisões de Página para :pageName',
     'pages_revision_named' => 'Revisão de Página para :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Restaurado de #:id; :summary',
     'pages_revisions_created_by' => 'Criada por',
     'pages_revisions_date' => 'Data da Revisão',
     'pages_revisions_number' => '#',
@@ -268,7 +269,7 @@ return [
     'attachments_link_url' => 'Link para o Arquivo',
     'attachments_link_url_hint' => 'URL do site ou arquivo',
     'attach' => 'Anexar',
-    'attachments_insert_link' => 'Add Attachment Link to Page',
+    'attachments_insert_link' => 'Adicionar Link de Anexo à Página',
     'attachments_edit_file' => 'Editar Arquivo',
     'attachments_edit_file_name' => 'Nome do Arquivo',
     'attachments_edit_drop_upload' => 'Arraste arquivos para cá ou clique para anexar arquivos e sobrescreve-los',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Tem certeza que deseja restaurar esta revisão? O conteúdo atual da página será substituído.',
     'revision_delete_success' => 'Revisão excluída',
     'revision_cannot_delete_latest' => 'Não é possível excluir a revisão mais recente.'
-];
\ No newline at end of file
+];
index c65d7f4969e14573eec31203e3f45a2dc955a0c8..d0e5c443904a186dbe1199c59d68e45696a3e4ad 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Página Não Encontrada',
     'sorry_page_not_found' => 'Desculpe, a página que você está procurando não pôde ser encontrada.',
     'sorry_page_not_found_permission_warning' => 'Se você esperava que esta página existisse, talvez você não tenha permissão para visualizá-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' => 'Retornar à página inicial',
     'error_occurred' => 'Ocorreu um Erro',
     'app_down' => ':appName está fora do ar no momento',
index a970e023117ba617662a4407447cbaa0bc699faf..6e801e27d4d1e86bc6525ae2398bc4750b8141c3 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Página Inicial',
     'app_homepage_desc' => 'Selecione uma opção para ser exibida como página inicial em vez da padrão. Permissões de página serão ignoradas para as páginas selecionadas.',
     'app_homepage_select' => 'Selecione uma página',
+    'app_footer_links' => 'Links do Rodapé',
+    '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::<key>" 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' => 'Etiqueta do Link',
+    'app_footer_links_url' => 'URL do Link',
+    'app_footer_links_add' => 'Adicionar Link de Rodapé',
     'app_disable_comments' => 'Desativar Comentários',
     'app_disable_comments_toggle' => 'Desativar comentários',
     'app_disable_comments_desc' => 'Desativar comentários em todas as páginas no aplicativo.<br> Comentários existentes não serão exibidos.',
@@ -68,7 +73,7 @@ return [
     'maint' => 'Manutenção',
     'maint_image_cleanup' => 'Limpeza de Imagens',
     'maint_image_cleanup_desc' => "Examina páginas e revisa seus conteúdos para verificar quais imagens e desenhos estão atualmente em uso e quais são redundantes. Certifique-se de criar um backup completo do banco de dados e imagens antes de executar esta ação.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Também excluir imagens que existem apenas em revisões de página antigas',
     'maint_image_cleanup_run' => 'Executar Limpeza',
     'maint_image_cleanup_warning' => ':count imagens potencialmente não utilizadas foram encontradas. Tem certeza de que deseja excluir estas imagens?',
     'maint_image_cleanup_success' => ':count imagens potencialmente não utilizadas foram encontradas e excluídas!',
@@ -95,8 +100,8 @@ return [
     'recycle_bin_empty' => 'Esvaziar Lixeira',
     'recycle_bin_empty_confirm' => 'Isso irá destruir permanentemente todos os itens na lixeira inclusive o conteúdo de cada item. Tem certeza de que quer esvaziar a lixeira?',
     'recycle_bin_destroy_confirm' => 'Esta ação irá excluir permanentemente do sistema este item junto com todos os elementos filhos listados abaixo. Você não poderá restaurar esse conteúdo. Tem certeza de que deseja excluir permanentemente este item?',
-    'recycle_bin_destroy_list' => 'Items to be Destroyed',
-    'recycle_bin_restore_list' => 'Items to be Restored',
+    'recycle_bin_destroy_list' => 'Itens a serem Destruídos',
+    'recycle_bin_restore_list' => 'Itens a serem restaurados',
     'recycle_bin_restore_confirm' => 'Esta ação irá restaurar o item excluído, inclusive quaisquer elementos filhos, para seu local original. Se a localização original tiver, entretanto, sido eliminada e estiver agora na lixeira, o item pai também precisará ser restaurado.',
     '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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
@@ -105,7 +110,7 @@ return [
     // Audit Log
     'audit' => 'Registro de auditoria',
     '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' => 'Filtro de Eventos',
     'audit_event_filter_no_filter' => 'Sem filtro',
     'audit_deleted_item' => 'Item excluído',
     'audit_deleted_item_name' => 'Nome: :name',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'Perfil do Usuário',
     'users_add_new' => 'Adicionar Novo Usuário',
     'users_search' => 'Pesquisar Usuários',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Última Atividade',
     'users_details' => 'Detalhes do Usuário',
     'users_details_desc' => 'Defina um nome de exibição e um endereço de e-mail para este usuário. O endereço de e-mail será usado para fazer login na aplicação.',
     'users_details_desc_no_email' => 'Defina um nome de exibição para este usuário para que outros usuários possam reconhecê-lo',
@@ -177,8 +182,8 @@ return [
     'users_delete_confirm' => 'Tem certeza que deseja excluir esse usuário?',
     '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_delete_success' => 'User successfully removed',
+    'users_none_selected' => 'Nenhum usuário selecionado',
+    'users_delete_success' => 'Usuário removido com sucesso',
     'users_edit' => 'Editar Usuário',
     'users_edit_profile' => 'Editar Perfil',
     'users_edit_success' => 'Usuário atualizado com sucesso',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 9777eacac5c5ff756f94a776437fee19ed9a48d2..ea3779c78874efda0f86b0c0ae69b2895eec1bd1 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'O campo :attribute não deve ter menos que :min caracteres.',
         'array'   => 'O campo :attribute não deve ter menos que :min itens.',
     ],
-    'no_double_extension'  => 'O campo :attribute deve ter apenas uma extensão de arquivo.',
     'not_in'               => 'O campo selecionado :attribute é inválido.',
     'not_regex'            => 'O formato do campo :attribute é inválido.',
     'numeric'              => 'O campo :attribute deve ser um número.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => 'O campo :attribute é requerido quando os valores :values não estiverem presentes.',
     'required_without_all' => 'O campo :attribute é requerido quando nenhum dos valores :values estiverem presentes.',
     'same'                 => 'O campo :attribute e o campo :other devem ser iguais.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => 'O link fornecido pode não ser seguro.',
     'size'                 => [
         'numeric' => 'O tamanho do campo :attribute deve ser :size.',
         'file'    => 'O tamanho do arquivo :attribute deve ser de :size kilobytes.',
index 12eac6912df6f4ead8089965516d8c90ad58527b..a0feccf9198a34040b6639289ff844cddcd67503 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'По возрастанию',
     'sort_descending' => 'По убыванию',
     'sort_name' => 'По имени',
+    'sort_default' => 'По умолчанию',
     'sort_created_at' => 'По дате создания',
     'sort_updated_at' => 'По дате обновления',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Навигация',
 
     // Header
+    'header_menu_expand' => 'Развернуть меню заголовка',
     'profile_menu' => 'Меню профиля',
     'view_profile' => 'Посмотреть профиль',
     'edit_profile' => 'Редактировать профиль',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Информация',
+    'tab_info_label' => 'Вкладка: Показать вторичную информацию',
     'tab_content' => 'Содержание',
+    'tab_content_label' => 'Вкладка: Показать основной контент',
 
     // Email Content
     'email_action_help' => 'Если у вас возникли проблемы с нажатием кнопки \':actionText\', то скопируйте и вставьте указанный URL-адрес в свой браузер:',
     'email_rights' => 'Все права защищены',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Политика конфиденциальности',
+    'terms_of_service' => 'Условия использования',
 ];
index e78ffde6563acbd8cce09ef059a1616656f4a920..4e1cca6ee0c0c172b22833bc52d7d5b58c7d326a 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Набор разрешений',
     'search_created_by_me' => 'Создано мной',
     'search_updated_by_me' => 'Обновлено мной',
+    'search_owned_by_me' => 'Созданные мной',
     'search_date_options' => 'Параметры даты',
     'search_updated_before' => 'Обновлено до',
     'search_updated_after' => 'Обновлено после',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Вы уверены, что хотите восстановить эту версию? Текущее содержимое страницы будет заменено.',
     'revision_delete_success' => 'Версия удалена',
     'revision_cannot_delete_latest' => 'Нельзя удалить последнюю версию.'
-];
\ No newline at end of file
+];
index e8f537ecdaa9e4b22b5b3fd467959cba88d09f66..96c792e1dd828cd1b95c8536bc051a85c23f851a 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Страница не найдена',
     'sorry_page_not_found' => 'Извините, страница, которую вы искали, не найдена.',
     'sorry_page_not_found_permission_warning' => 'Если вы ожидали что страница существует, возможно у вас нет прав для её просмотра.',
+    'image_not_found' => 'Изображение не найдено',
+    'image_not_found_subtitle' => 'К сожалению, файл изображения, который вы искали, не найден.',
+    'image_not_found_details' => 'Возможно данное изображение было удалено.',
     'return_home' => 'вернуться на главную страницу',
     'error_occurred' => 'Произошла ошибка',
     'app_down' => ':appName в данный момент не доступно',
index 472973ca2a2c1b08c45a9da9579be29ef8084880..57e23b3c93a78a0e199c7adef4d2ef104bed5d26 100755 (executable)
@@ -29,7 +29,7 @@ return [
     'app_editor_desc' => 'Выберите, какой редактор будет использоваться всеми пользователями для редактирования страниц.',
     'app_custom_html' => 'Пользовательский контент заголовка HTML',
     'app_custom_html_desc' => 'Любой контент, добавленный здесь, будет вставлен в нижнюю часть раздела <head> каждой страницы. Это удобно для переопределения стилей или добавления кода аналитики.',
-    'app_custom_html_disabled_notice' => 'Пользовательский контент заголовка HTML отключен на этой странице, чтобы гарантировать отмену любых критических изменений',
+    'app_custom_html_disabled_notice' => 'Пользовательский контент заголовка HTML отключен на этой странице, чтобы гарантировать отмену любых критических изменений.',
     'app_logo' => 'Логотип приложения',
     'app_logo_desc' => 'Это изображение должно быть 43px в высоту. <br>Большое изображение будет уменьшено.',
     'app_primary_color' => 'Основной цвет приложения',
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Стартовая страница приложения',
     'app_homepage_desc' => 'Выберите страницу, которая будет отображаться на главной странице вместо стандартной. Права на страницы игнорируются для выбранных страниц.',
     'app_homepage_select' => 'Выберите страницу',
+    'app_footer_links' => 'Ссылки в нижней части страницы',
+    'app_footer_links_desc' => 'Добавьте ссылки для отображения в нижнем колонтитуле сайта. Они будут отображаться в нижней части большинства страниц, включая те, которые не требуют входа. Вы можете использовать метку "trans::<key>" для использования системных переводов. Например: Использование "trans::common.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' => 'Отключение комментариев на всех страницах. Существующие комментарии будут скрыты.',
@@ -118,7 +123,7 @@ return [
 
     // Role Settings
     'roles' => 'Роли',
-    'role_user_roles' => 'Роли пользователя',
+    'role_user_roles' => 'Роли пользователей',
     'role_create' => 'Добавить роль',
     'role_create_success' => 'Роль успешно добавлена',
     'role_delete' => 'Удалить роль',
@@ -141,7 +146,7 @@ return [
     'role_access_api' => 'Доступ к системному API',
     'role_manage_settings' => 'Управление настройками приложения',
     'role_asset' => 'Права доступа к материалам',
-    'roles_system_warning' => 'Ð\98мейÑ\82е Ð² Ð²Ð¸Ð´Ñ\83, Ñ\87Ñ\82о Ð´Ð¾Ñ\81Ñ\82Ñ\83п Ðº Ð»Ñ\8eбомÑ\83 Ð¸Ð· Ñ\83казаннÑ\8bÑ\85 Ð²Ñ\8bÑ\88е Ñ\82Ñ\80еÑ\85 Ñ\80азÑ\80еÑ\88ений Ð¼Ð¾Ð¶ÐµÑ\82 Ð¿Ð¾Ð·Ð²Ð¾Ð»Ð¸Ñ\82Ñ\8c Ð¿Ð¾Ð»Ñ\8cзоваÑ\82елÑ\8e Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ\82Ñ\8c Ñ\81вои Ñ\81обÑ\81Ñ\82веннÑ\8bе Ð¿Ñ\80ивилегии Ð¸Ð»Ð¸ Ð¿Ñ\80ивилегии Ð´Ñ\80Ñ\83гиÑ\85 Ð¿Ð¾Ð»Ñ\8cзоваÑ\82елей Ñ\81иÑ\81Ñ\82емÑ\8b. Ð\9dазнаÑ\87иÑ\82Ñ\8c Ñ\80оли Ñ\81 Ñ\8dÑ\82ими Ð¿Ñ\80авами только доверенным пользователям.',
+    'roles_system_warning' => 'Ð\98мейÑ\82е Ð² Ð²Ð¸Ð´Ñ\83, Ñ\87Ñ\82о Ð´Ð¾Ñ\81Ñ\82Ñ\83п Ðº Ð»Ñ\8eбомÑ\83 Ð¸Ð· Ñ\83казаннÑ\8bÑ\85 Ð²Ñ\8bÑ\88е Ñ\82Ñ\80еÑ\85 Ñ\80азÑ\80еÑ\88ений Ð¼Ð¾Ð¶ÐµÑ\82 Ð¿Ð¾Ð·Ð²Ð¾Ð»Ð¸Ñ\82Ñ\8c Ð¿Ð¾Ð»Ñ\8cзоваÑ\82елÑ\8e Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ\82Ñ\8c Ñ\81вои Ñ\81обÑ\81Ñ\82веннÑ\8bе Ð¿Ñ\80ивилегии Ð¸Ð»Ð¸ Ð¿Ñ\80ивилегии Ð´Ñ\80Ñ\83гиÑ\85 Ð¿Ð¾Ð»Ñ\8cзоваÑ\82елей Ñ\81иÑ\81Ñ\82емÑ\8b. Ð\9dазнаÑ\87аÑ\82Ñ\8c Ñ\80оли Ñ\81 Ñ\8dÑ\82ими Ð¿Ñ\80авами Ð¼Ð¾Ð¶Ð½Ð¾ только доверенным пользователям.',
     'role_asset_desc' => 'Эти разрешения контролируют доступ по умолчанию к параметрам внутри системы. Разрешения на книги, главы и страницы перезапишут эти разрешения.',
     'role_asset_admins' => 'Администраторы автоматически получают доступ ко всему контенту, но эти опции могут отображать или скрывать параметры пользовательского интерфейса.',
     'role_all' => 'Все',
@@ -203,7 +208,7 @@ return [
     'user_api_token_name' => 'Имя',
     'user_api_token_name_desc' => 'Присвойте вашему токену читаемое имя, в качестве напоминания о его назначении в будущем.',
     'user_api_token_expiry' => 'Истекает',
-    'user_api_token_expiry_desc' => 'Установите дату истечения срока действия этого токена. После этой даты запросы, сделанные с использованием этого токена, больше не будут работать. Если оставить это поле пустым, срок действия истечет через 100 лет.',
+    'user_api_token_expiry_desc' => 'Установите дату истечения срока действия этого токена. После наступления даты запросы, сделанные с использованием данного токена, больше не будут работать. Если оставить это поле пустым, срок действия истечет через 100 лет.',
     'user_api_token_create_secret_message' => 'Сразу после создания этого токена будут сгенерированы и отображены идентификатор токена и секретный ключ. Секретный ключ будет показан только один раз, поэтому перед продолжением обязательно скопируйте значение в безопасное и надежное место.',
     'user_api_token_create_success' => 'API токен успешно создан',
     'user_api_token_update_success' => 'API токен успешно обновлен',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index d16ead5d843e737384416fd8152f9519cf83c337..8c583f7e7820eb72d748ac62ec1d94c6dfb05198 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute должен быть минимум :min символов.',
         'array'   => ':attribute должен содержать хотя бы :min элементов.',
     ],
-    'no_double_extension'  => ':attribute должен иметь только одно расширение файла.',
     'not_in'               => 'Выбранный :attribute некорректен.',
     'not_regex'            => 'Формат :attribute некорректен.',
     'numeric'              => ':attribute должен быть числом.',
index 555c2c6f2c65c71427763c8f52f17f2b4554dd74..0d8eb2b6d84648d0a2887c3b49e9d570399f9f27 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Zoradiť vzostupne',
     'sort_descending' => 'Zoradiť zostupne',
     'sort_name' => 'Meno',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Dátum vytvorenia',
     'sort_updated_at' => 'Aktualizované dňa',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Breadcrumb',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Menu profilu',
     'view_profile' => 'Zobraziť profil',
     'edit_profile' => 'Upraviť profil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informácie',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Obsah',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Ak máte problém klinkúť na tlačidlo ":actionText", skopírujte a vložte URL uvedenú nižšie do Vášho prehliadača:',
     'email_rights' => 'Všetky práva vyhradené',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index 4b026c506c5a0a0f8c84e649feea1f2ea5968118..058d7f4c915a9bdeb73598f5fd64d450f1c6218f 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Oprávnenia',
     'search_created_by_me' => 'Vytvorené mnou',
     'search_updated_by_me' => 'Aktualizované mnou',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Možnosti dátumu',
     'search_updated_before' => 'Aktualizované pred',
     'search_updated_after' => 'Aktualizované po',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Naozaj chcete obnoviť túto revíziu? Aktuálny obsah stránky sa nahradí.',
     'revision_delete_success' => 'Revízia bola vymazaná',
     'revision_cannot_delete_latest' => 'Nie je možné vymazať poslednú revíziu.'
-];
\ No newline at end of file
+];
index c045d473d05e99e86971523018513dc50de667d8..e523110edea3a369f546aa8bba7ebf9961b76010 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Stránka nenájdená',
     'sorry_page_not_found' => 'Prepáčte, stránka ktorú hľadáte nebola nájdená.',
     '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' => 'Vrátiť sa domov',
     'error_occurred' => 'Nastala chyba',
     'app_down' => ':appName je momentálne nedostupná',
index 17e658a3fbe6413e82236840da0e2f5d6d024bab..60a1a5add6c06164ed83c89f1c900ffbbc33e7d1 100644 (file)
@@ -37,6 +37,11 @@ return [
     '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::<key>" 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' => 'Zakázať komentáre',
     'app_disable_comments_toggle' => 'Disable comments',
     'app_disable_comments_desc' => 'Zakázať komentáre na všetkých stránkach aplikácie. Existujúce komentáre sa nezobrazujú.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index c127b0623cc24158c006b7219ccf5f97546774c8..545313415afbf963738584626e2827ade96a8d09 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute musí mať aspoň :min znakov.',
         'array'   => ':attribute musí mať aspoň :min položiek.',
     ],
-    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'Vybraný :attribute je neplatný.',
     'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => ':attribute musí byť číslo.',
index 65a42288c4c478520b4d8fb3bdb12e441e143518..cadd2ba8821af4bebc2defd78aca719e733b3ad3 100644 (file)
@@ -9,7 +9,7 @@ return [
     'confirm' => 'Potrdi',
     'back' => 'Nazaj',
     'save' => 'Shrani',
-    'continue' => 'Nadaljuj',
+    'continue' => 'Naprej',
     'select' => 'Izberi',
     'toggle_all' => 'Vklopi vse',
     'more' => 'Več',
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Razvrsti naraščajoče',
     'sort_descending' => 'Razvrsti padajoče',
     'sort_name' => 'Ime',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Datum nastanka',
     'sort_updated_at' => 'Datum posodobitve',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Pot',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Meni profila',
     'view_profile' => 'Ogled profila',
     'edit_profile' => 'Uredi profil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informacije',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Vsebina',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'V kolikor imate težave s klikom na gumb ":actionText", kopirajte in prilepite spodnjo povezavo v vaš brskalnik:',
     'email_rights' => 'Vse pravice pridržane',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Pravilnik o zasebnosti',
+    'terms_of_service' => 'Pogoji uporabe',
 ];
index 9e400bbec584e63ad927937591dc0732455d40f4..c5ade055f72cc342ed6f8469b4b5d29bac80de70 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => 'Ustvaril :timeLength uporabnik :user',
     'meta_updated' => 'Posodobljeno :timeLength',
     'meta_updated_name' => 'Posodobil :timeLength uporabnik :user',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'V lasti :user',
     'entity_select' => 'Izbira entitete',
     'images' => 'Slike',
     'my_recent_drafts' => 'Moji nedavni osnutki',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'V trenutku, ko bodo omogočena, bodo imela ta dovoljenja prednost pred dovoljenji za določanje vlog.',
     'permissions_enable' => 'Omogoči dovoljenja po meri',
     'permissions_save' => 'Shrani dovoljenja',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Lastnik',
 
     // Search
     'search_results' => 'Rezultati iskanja',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Nastavljena dovoljenja',
     'search_created_by_me' => 'Ustvaril sem jaz',
     'search_updated_by_me' => 'Posodobil sem jaz',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Možnosti datuma',
     'search_updated_before' => 'Posodobljeno pred',
     'search_updated_after' => 'Posodobljeno po',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Ustvari novo poglavje',
     'chapters_delete' => 'Izbriši poglavje',
     'chapters_delete_named' => 'Izbriši poglavje :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_explain' => 'Poglavje z imenom ":chapterName" bo izbrisano. Vse strani znotraj poglavja bodo prav tako izbrisane.',
     'chapters_delete_confirm' => 'Ste prepričani, da želite izbrisati to poglavje?',
     'chapters_edit' => 'Uredi poglavje',
     'chapters_edit_named' => 'Uredi poglavje :chapterName',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Pregled strani',
     'pages_revisions_named' => 'Pregledi strani za :pageName',
     'pages_revision_named' => 'Pregled strani za :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Obnovljeno iz #:id; :summary',
     'pages_revisions_created_by' => 'Ustvaril',
     'pages_revisions_date' => 'Datum revizije',
     'pages_revisions_number' => '#',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Ali ste prepričani da želite obnoviti to revizijo? Vsebina trenutne strani bo zamenjana.',
     'revision_delete_success' => 'Revizija izbrisana',
     'revision_cannot_delete_latest' => 'Ne morem izbrisati zadnje revizije.'
-];
\ No newline at end of file
+];
index e291222c3f6be5f22cdbcbb61908c492d4619327..b0e253be0e8c28f68a25ec45cb00199ddba61090 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Strani ni mogoče najti',
     'sorry_page_not_found' => 'Oprostite, strani ki jo iščete, ni mogoče najti.',
     'sorry_page_not_found_permission_warning' => 'Če pričakujete, da ta stran obstaja, mogoče nimate pravic ogleda zanjo.',
+    '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' => 'Vrni se domov',
     'error_occurred' => 'Prišlo je do napake',
     'app_down' => ':appName trenutno ni dosegljiva',
index feae93bd516a4406bb9cca266e610624e54857bb..8373c8cebef6fce4d0f480c93a92e6dfbb7530e9 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Domača stran aplikacije',
     'app_homepage_desc' => 'Izberi pogled, da se pokaže na domači strani, namesto osnovnega pogleda. Dovoljenja strani so prezrta za izbrane strani.',
     'app_homepage_select' => 'Izberi stran',
+    'app_footer_links' => 'Povezave v nogi',
+    'app_footer_links_desc' => 'Dodaj URL povezave, ki bodo na voljo v nogi spletne strani. Povezave bodo vidne na dnu večine strani, vključno s tistimi, ki ne zahtevajo prijave. Na voljo imate oznako "trans::<key>" za uporabo sistemskih prevodov. Na primer: uporaba oznake "trans::common.privacy_policy" bo poskrbela za prevod besedila "Privacy Policy" in oznaka "trans::common.terms_of_service" bo poskrbela za prevod besedila "Terms of Service".',
+    'app_footer_links_label' => 'Oznaka povezave',
+    'app_footer_links_url' => 'Naslov URL povezave',
+    'app_footer_links_add' => 'Dodaj povezavo v nogo',
     'app_disable_comments' => 'Onemogoči komentarje',
     'app_disable_comments_toggle' => 'Onemogoči komentarje',
     'app_disable_comments_desc' => 'Onemogoči komentarje na vseh straneh v aplikaciji. <br> Obstoječi komentarji se ne prikazujejo.',
@@ -68,7 +73,7 @@ return [
     'maint' => 'Vzdrževanje',
     'maint_image_cleanup' => 'Odstrani /počisti slike',
     'maint_image_cleanup_desc' => "Pregleda vsebino strani in revizij ter ugotovi, katere slike in risbe so v uporabi in katere so odvečne. Preden to poženeš, naredi popolno varnostno kopijo podatkovne zbirke in slik.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Izbriši tudi slike, ki obstajajo le v starih različicah strani',
     'maint_image_cleanup_run' => 'Zaženi čiščenje',
     'maint_image_cleanup_warning' => 'Najdenih je bilo :count verjetno neuporabljenih slik. Ali si prepričan, da želiš odstraniti izbrane slike?',
     'maint_image_cleanup_success' => ':count verjetno neuporavljenih slik je bilo najdenih in izbrisanih!',
@@ -175,10 +180,10 @@ return [
     'users_delete_named' => 'Brisanje uporabnika :userName',
     'users_delete_warning' => 'Iz sistema se bo popolnoma  izbrisal uporabnik z imenom \':userName\'',
     'users_delete_confirm' => 'Ste prepričani, da želite izbrisati tega uporabnika?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'Prenesi lastništvo',
+    'users_migrate_ownership_desc' => 'Izberite uporabnika, če želite nanj prenesti lastništvo vseh vnosov.',
+    'users_none_selected' => 'Ni izbranega uporabnika',
+    'users_delete_success' => 'Uporabnik uspešno odstranjen',
     'users_edit' => 'Uredi uporabnika',
     'users_edit_profile' => 'Uredi profil',
     'users_edit_success' => 'Uporabnik uspešno posodobljen',
@@ -227,6 +232,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'danščina',
         'de' => 'Deutsch (Sie)',
@@ -236,12 +243,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index bc7242bac35d4e1ab13861f568a14e4ffb216ac1..9b1a5ff463877dc18d5a0b554baee3427c2630b0 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute mora biti najmanj :min znakov.',
         'array'   => ':attribute mora imeti vsaj :min elementov.',
     ],
-    'no_double_extension'  => ':attribute mora imeti samo eno razširitveno datoteko',
     'not_in'               => 'Izbrani atribut je neveljaven.',
     'not_regex'            => ':attribute oblika ni veljavna.',
     'numeric'              => 'Atribut mora biti število.',
index 7ad2712ccf40bbd00a18fb79fd1e764e64a8d7f1..f820fbdb3a0393fe5bcfbbcb4587d07a924a84ef 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Sortera stigande',
     'sort_descending' => 'Sortera fallande',
     'sort_name' => 'Namn',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Skapad',
     'sort_updated_at' => 'Uppdaterad',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Brödsmula',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profilmeny',
     'view_profile' => 'Visa profil',
     'edit_profile' => 'Redigera profil',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Information',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Innehåll',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Om du har problem, klicka på knappen ":actionText", och kopiera och klistra in den här adressen i din webbläsare:',
     'email_rights' => 'Alla rättigheter är reserverade',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index d31bef6f00dc593bed331c3326270cbc140b58ef..a0df3b608745fcdc4c5a13b26b41f8e2d3a0eef6 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Har anpassade rättigheter',
     'search_created_by_me' => 'Skapade av mig',
     'search_updated_by_me' => 'Uppdaterade av mig',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Datumalternativ',
     'search_updated_before' => 'Uppdaterade före',
     'search_updated_after' => 'Uppdaterade efter',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Är du säker på att du vill använda denna revision? Det nuvarande innehållet kommer att ersättas.',
     'revision_delete_success' => 'Revisionen raderad',
     'revision_cannot_delete_latest' => 'Det går inte att ta bort den senaste versionen.'
-];
\ No newline at end of file
+];
index dbc3f37f237021350066bfe1fc36a29e4be4566e..125fcbfd0e3039a338e7e6a44b719e277d0b12a4 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Sidan hittades inte',
     'sorry_page_not_found' => 'Tyvärr gick det inte att hitta sidan du söker.',
     'sorry_page_not_found_permission_warning' => 'Om du förväntade dig att denna sida skulle existera, kanske du inte har behörighet att se den.',
+    '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' => 'Återvänd till startsidan',
     'error_occurred' => 'Ett fel inträffade',
     'app_down' => ':appName är nere just nu',
index 8c6caaf4a9e6d1096e6423f30fa89382fb5533fc..07b1b5d4a19f02b4321631aa240258e4201c9986 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Startsida',
     'app_homepage_desc' => 'Välj en sida att använda som startsida istället för standardvyn. Den valda sidans rättigheter kommer att ignoreras.',
     'app_homepage_select' => 'Välj en sida',
+    '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::<key>" 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' => 'Inaktivera kommentarer',
     'app_disable_comments_toggle' => 'Inaktivera kommentarer',
     'app_disable_comments_desc' => 'Inaktivera kommentarer på alla sidor i applikationen. Befintliga kommentarer visas inte.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Danska',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 3190318559d1642c960bc6b2c612a27b874fb757..da39796bc388e6ad11b8ea6a273e3ada6c2c7f75 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute måste vara minst :min tecken.',
         'array'   => ':attribute måste ha minst :min poster.',
     ],
-    'no_double_extension'  => ':attribute får bara ha ett filtillägg.',
     'not_in'               => 'Vald :attribute är inte giltig',
     'not_regex'            => 'Formatet på :attribute är ogiltigt.',
     'numeric'              => ':attribute måste vara ett nummer.',
index 76b57a2a3b58ddb8ef41e0562c5187359cc6e542..f4af9bc6f010ff1eed03b1cbc77e95d1490e53b4 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'The :attribute must be at least :min characters.',
         'array'   => 'The :attribute must have at least :min items.',
     ],
-    'no_double_extension'  => 'The :attribute must only have a single file extension.',
     'not_in'               => 'The selected :attribute is invalid.',
     'not_regex'            => 'The :attribute format is invalid.',
     'numeric'              => 'The :attribute must be a number.',
index a252ac7703d6e9b65f8eb0f010e8e3fa75ebb972..ff253493a33947cf51f61b28d14a67782793f6db 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Artan Sıralama',
     'sort_descending' => 'Azalan Sıralama',
     'sort_name' => 'İsim',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Oluşturulma Tarihi',
     'sort_updated_at' => 'Güncelleme Tarihi',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Gezinti Menüsü',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Profil Menüsü',
     'view_profile' => 'Profili Görüntüle',
     'edit_profile' => 'Profili Düzenle',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Bilgi',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'İçerik',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => '":actionText" butonuna tıklamada sorun yaşıyorsanız, aşağıda bulunan bağlantıyı kopyalayıp tarayıcınıza yapıştırın:',
     'email_rights' => 'Tüm hakları saklıdır',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index 2f447e7953a51a7a6cb3bfb257c57904eac855cb..64a0ee35268ed2df9ca355ea59eccbf800515c02 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'İzinler ayarlanmış',
     'search_created_by_me' => 'Oluşturduklarım',
     'search_updated_by_me' => 'Güncellediklerim',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Tarih Seçenekleri',
     'search_updated_before' => 'Önce güncellendi',
     'search_updated_after' => 'Sonra güncellendi',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Bu revizyonu yeniden yüklemek istediğinize emin misiniz? Sayfanın şu anki içeriği değiştirilecektir.',
     'revision_delete_success' => 'Revizyon silindi',
     'revision_cannot_delete_latest' => 'Son revizyonu silemezsiniz.'
-];
\ No newline at end of file
+];
index d3a24622c2c8eb73bba91c3c5a4a97155aa27154..5048b079cf6045597d15d90b59347fa06d1fcce2 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Sayfa Bulunamadı',
     'sorry_page_not_found' => 'Üzgünüz, aradığınız sayfa bulunamıyor.',
     'sorry_page_not_found_permission_warning' => 'Bu sayfanın var olduğunu düşünüyorsanız, görüntüleme iznine sahip olmayabilirsiniz.',
+    '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' => 'Ana sayfaya dön',
     'error_occurred' => 'Bir Hata Oluştu',
     'app_down' => ':appName şu anda erişilemez durumda',
index fd861c17011c23573a1de8f2e60444f6dc640bc9..8ba1192c75094dc0deda77923ee19b8d2a687d8b 100755 (executable)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Ana Sayfa',
     'app_homepage_desc' => 'Varsayılan görünüm yerine ana sayfada görünmesi için bir görünüm seçin. Sayfa izinleri, burada seçeceğiniz sayfalar için yok sayılacaktır.',
     'app_homepage_select' => 'Bir sayfa seçin',
+    '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::<key>" 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' => 'Yorumları Devre Dışı Bırak',
     'app_disable_comments_toggle' => 'Yorumları devre dışı bırak',
     'app_disable_comments_desc' => 'Bütün sayfalar için yorumları devre dışı bırakır. <br> Mevcut yorumlar gösterilmeyecektir.',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Danca',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'İbranice',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 45b7189d7dca89a2d3cc945f8be4d87f9b1052c8..48bbef92b20b316a7ed926979ddc62290d9f5f80 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute, en az :min karakter içermelidir.',
         'array'   => ':attribute, en az :min öge içermelidir.',
     ],
-    'no_double_extension'  => ':attribute, sadece tek bir dosya tipinde olmalıdır.',
     'not_in'               => 'Seçili :attribute geçersiz.',
     'not_regex'            => ':attribute formatı geçersiz.',
     'numeric'              => ':attribute, bir sayı olmalıdır.',
index 51cf9db19058b4f8a980b23884bb1fee9d3caa44..f16f6fe3edc0fd3b37941cc77d79a0aeaa5e795c 100644 (file)
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'прокоментував',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => 'оновив дозволи',
 ];
index 79fe3c24bf86c16bdfcf521d2e65bed2c435bd68..1920f125a79bf64cec7fbf4107133975daaf4e13 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'За зростанням',
     'sort_descending' => 'За спаданням',
     'sort_name' => 'Ім\'я',
+    'sort_default' => 'За замовчуванням',
     'sort_created_at' => 'Дата створення',
     'sort_updated_at' => 'Дата оновлення',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Навігація',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Меню профілю',
     'view_profile' => 'Переглянути профіль',
     'edit_profile' => 'Редагувати профіль',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Інфо',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Вміст',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Якщо у вас виникають проблеми при натисканні кнопки ":actionText", скопіюйте та вставте URL у свій веб-браузер:',
     'email_rights' => 'Всі права захищені',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Політика приватності',
+    'terms_of_service' => 'Умови використання',
 ];
index 3ca3915f40a376049d319deb75ca495ca4d2eed4..86f044102ac39f884f6877ea4c2c044148b2f4af 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => ':user створив :timeLength',
     'meta_updated' => 'Оновлено :timeLength',
     'meta_updated_name' => ':user оновив :timeLength',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => 'Власник :user',
     'entity_select' => 'Вибір об\'єкта',
     'images' => 'Зображення',
     'my_recent_drafts' => 'Мої останні чернетки',
@@ -40,7 +40,7 @@ return [
     'permissions_intro' => 'Після ввімкнення ці дозволи будуть мати вищий пріоритет ніж інші дозволи ролей.',
     'permissions_enable' => 'Увімкнути спеціальні дозволи',
     'permissions_save' => 'Зберегти дозволи',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => 'Власник',
 
     // Search
     'search_results' => 'Результати пошуку',
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Налаштування дозволів',
     'search_created_by_me' => 'Створено мною',
     'search_updated_by_me' => 'Оновлено мною',
+    'search_owned_by_me' => 'Належать мені',
     'search_date_options' => 'Параметри дати',
     'search_updated_before' => 'Оновлено до',
     'search_updated_after' => 'Оновлено після',
@@ -148,7 +149,7 @@ return [
     'chapters_create' => 'Створити новий розділ',
     'chapters_delete' => 'Видалити розділ',
     'chapters_delete_named' => 'Видалити розділ :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_explain' => 'Це видалить розділ під назвою \':chapterName\'. Усі сторінки, що існують у цьому розділі, також будуть видалені.',
     'chapters_delete_confirm' => 'Ви впевнені, що хочете видалити цей розділ?',
     'chapters_edit' => 'Редагувати розділ',
     'chapters_edit_named' => 'Редагувати розділ :chapterName',
@@ -210,7 +211,7 @@ return [
     'pages_revisions' => 'Версія сторінки',
     'pages_revisions_named' => 'Версії сторінки для :pageName',
     'pages_revision_named' => 'Версія сторінки для :pageName',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => 'Відновлено з #:id; :summary',
     'pages_revisions_created_by' => 'Створена',
     'pages_revisions_date' => 'Дата версії',
     'pages_revisions_number' => '#',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Дійсно відновити цю версію? Вміст поточної сторінки буде замінено.',
     'revision_delete_success' => 'Версія видалена',
     'revision_cannot_delete_latest' => 'Неможливо видалити останню версію.'
-];
\ No newline at end of file
+];
index eb20bae74bf57e921caf9cbce25e2448d228e049..c7d2545f995565c6935b0b8325629401422180b5 100644 (file)
@@ -83,6 +83,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.',
     'return_home' => 'Повернутися на головну',
     'error_occurred' => 'Виникла помилка',
     'app_down' => ':appName зараз недоступний',
index b94ae4e8c03d64af50902105d8c06c2984d2bad7..12c2561325e5aa1af7016c88916d6f2328b0b0c8 100644 (file)
@@ -15,7 +15,7 @@ return [
     'app_customization' => 'Налаштування',
     'app_features_security' => 'Особливості та безпека',
     'app_name' => 'Назва програми',
-    'app_name_desc' => 'ЦÑ\8f Ð½Ð°Ð·Ð²Ð° Ð²Ñ\96добÑ\80ажаÑ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\83 Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÑ\83 Ñ\82а Ñ\83 Ð²сіх листах.',
+    'app_name_desc' => 'ЦÑ\8f Ð½Ð°Ð·Ð²Ð° Ð¿Ð¾ÐºÐ°Ð·Ñ\83Ñ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\83 Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÑ\83 Ñ\82а Ð² Ñ\83сіх листах.',
     'app_name_header' => 'Показати назву програми в заголовку',
     'app_public_access' => 'Публічнй доступ',
     'app_public_access_desc' => 'Увімкнення цієї опції дозволить відвідувачам, які не увійшли в систему, отримати доступ до вмісту у вашому екземплярі BookStack.',
@@ -35,8 +35,13 @@ return [
     'app_primary_color' => 'Основний колір програми',
     'app_primary_color_desc' => 'Колір потрібно вказати у hex-форматі. <br>Залиште порожнім, щоб використати стандартний колір.',
     'app_homepage' => 'Домашня сторінка програми',
-    'app_homepage_desc' => 'Ð\92ибеÑ\80Ñ\96Ñ\82Ñ\8c Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\83, Ñ\8fка Ð²Ñ\96добÑ\80ажаÑ\82имеÑ\82Ñ\8cÑ\81Ñ\8f Ð½Ð° Ð´Ð¾Ð¼Ð°Ñ\88нÑ\96й Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\96 Ð·Ð°Ð¼Ñ\96Ñ\81Ñ\82Ñ\8c Ð¿ÐµÑ\80еглÑ\8fдÑ\83 Ð·Ð° Ñ\83мовÑ\87анням. Права на сторінку не враховуються для вибраних сторінок.',
+    'app_homepage_desc' => 'Ð\92ибеÑ\80Ñ\96Ñ\82Ñ\8c Ñ\81Ñ\82оÑ\80Ñ\96нкÑ\83, Ñ\8fка Ð¿Ð¾ÐºÐ°Ð·Ñ\83ваÑ\82имеÑ\82Ñ\8cÑ\81Ñ\8f Ð½Ð° Ð´Ð¾Ð¼Ð°Ñ\88нÑ\96й Ñ\81Ñ\82оÑ\80Ñ\96нÑ\86Ñ\96 Ð·Ð°Ð¼Ñ\96Ñ\81Ñ\82Ñ\8c Ð¿ÐµÑ\80еглÑ\8fдÑ\83 Ð·Ð° Ð·Ð°Ð¼Ð¾Ð²Ñ\87Ñ\83ванням. Права на сторінку не враховуються для вибраних сторінок.',
     'app_homepage_select' => 'Вибрати сторінку',
+    'app_footer_links' => 'Посилання нижньої частини сайту',
+    'app_footer_links_desc' => 'Додайте посилання до нижньої частини сайту. Вони будуть відображатися в нижній частині більшості сторінок, включаючи ті, що не потребують входу. Для використання системних перекладів ви можете скористатися мітками "trans::<key>". Наприклад: додавання "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' => 'Вимкнути коментарі на всіх сторінках програми. Існуючі коментарі не відображаються.',
@@ -52,7 +57,7 @@ return [
 
     // Registration Settings
     'reg_settings' => 'Реєстрація',
-    'reg_enable' => 'Дозволити реєстрацію',
+    'reg_enable' => 'Дозвіл на реєстрацію',
     'reg_enable_toggle' => 'Дозволити реєстрацію',
     'reg_enable_desc' => 'При включенні реєстрації відвідувач зможе зареєструватися як користувач програми. Після реєстрації їм надається єдина роль користувача за замовчуванням.',
     'reg_default_role' => 'Роль користувача за умовчанням після реєстрації',
@@ -60,7 +65,7 @@ return [
     'reg_email_confirmation' => 'Підтвердження електронною поштою',
     'reg_email_confirmation_toggle' => 'Необхідне підтвердження електронною поштою',
     'reg_confirm_email_desc' => 'Якщо використовується обмеження домену, то підтвердження електронною поштою буде потрібно, а нижче значення буде проігноровано.',
-    'reg_confirm_restrict_domain' => 'Ð\9eбмежиÑ\82и по домену',
+    'reg_confirm_restrict_domain' => 'Ð\9eбмеженнÑ\8f по домену',
     'reg_confirm_restrict_domain_desc' => 'Введіть список розділених комами доменів електронної пошти, до яких ви хочете обмежити реєстрацію. Користувачам буде надіслано електронне повідомлення для підтвердження своєї адреси, перш ніж дозволяти взаємодіяти з додатком. <br> Зауважте, що користувачі зможуть змінювати свої електронні адреси після успішної реєстрації.',
     'reg_confirm_restrict_domain_placeholder' => 'Не встановлено обмежень',
 
@@ -68,7 +73,7 @@ return [
     'maint' => 'Обслуговування',
     'maint_image_cleanup' => 'Очищення зображень',
     'maint_image_cleanup_desc' => "Сканує вміст сторінки та версій, щоб перевірити, які зображення та малюнки в даний час використовуються, а також які зображення зайві. Переконайтеся, що ви створили повну резервну копію бази даних та зображення, перш ніж запускати це.",
-    'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions',
+    'maint_delete_images_only_in_revisions' => 'Також видалити зображення, що існують лише в старих версіях сторінки',
     'maint_image_cleanup_run' => 'Запустити очищення',
     'maint_image_cleanup_warning' => ':count потенційно невикористаних зображень було знайдено. Ви впевнені, що хочете видалити ці зображення?',
     'maint_image_cleanup_success' => ':count потенційно невикористані зображення знайдено і видалено!',
@@ -80,27 +85,27 @@ return [
     'maint_send_test_email_mail_subject' => 'Перевірка електронної пошти',
     'maint_send_test_email_mail_greeting' => 'Доставляння електронної пошти працює!',
     'maint_send_test_email_mail_text' => 'Вітаємо! Оскільки ви отримали цього листа, поштова скринька налаштована правильно.',
-    '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_recycle_bin_desc' => 'Видалені полиці, книги, розділи та сторінки попадають кошик, щоб їх можна було відновити або видалити остаточно. Старіші елементи з кошика можна автоматично видаляти через деякий час, залежно від налаштувань системи.',
+    'maint_recycle_bin_open' => 'Відкрити кошик',
 
     // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin' => 'Кошик',
+    'recycle_bin_desc' => 'Тут ви можете відновити видалені елементи, або назавжди видалити їх із системи. Цей список нефільтрований, на відміну від подібних списків активності в системі, де застосовуються фільтри дозволів.',
+    'recycle_bin_deleted_item' => 'Виадлений елемент',
+    'recycle_bin_deleted_by' => 'Ким видалено',
+    'recycle_bin_deleted_at' => 'Час видалення',
+    'recycle_bin_permanently_delete' => 'Видалити остаточно',
+    'recycle_bin_restore' => 'Відновити',
+    'recycle_bin_contents_empty' => 'Зараз кошик порожній',
+    'recycle_bin_empty' => 'Очистити кошик',
+    'recycle_bin_empty_confirm' => 'Це назавжди знищить усі елементи в кошику, включаючи вміст кожного елементу. Ви впевнені, що хочете очистити кошик?',
+    'recycle_bin_destroy_confirm' => 'Ця дія назавжди видалить цей об\'єкт із системи, а також усі дочірні об\'єкти вказані нижче, і ви не зможете відновити його. Ви впевнені, що хочете назавжди видалити цей об\'єкт?',
+    'recycle_bin_destroy_list' => 'Елементи для знищення',
+    'recycle_bin_restore_list' => 'Елементи для відновлення',
+    'recycle_bin_restore_confirm' => 'Ця дія відновить видалений елемент у початкове місце, включаючи всі дочірні елементи. Якщо вихідне розташування відтоді було видалено, і знаходиться у кошику, батьківський елемент також потрібно буде відновити.',
+    'recycle_bin_restore_deleted_parent' => 'Батьківський елемент цього об\'єкта також був видалений. Вони залишатимуться видаленими, доки батьківський елемент також не буде відновлений.',
+    'recycle_bin_destroy_notification' => 'Видалено :count елементів із кошика.',
+    'recycle_bin_restore_notification' => 'Відновлено :count елементів із кошика.',
 
     // Audit Log
     'audit' => 'Журнал аудиту',
@@ -111,7 +116,7 @@ return [
     'audit_deleted_item_name' => 'Назва: :name',
     'audit_table_user' => 'Користувач',
     'audit_table_event' => 'Подія',
-    'audit_table_related' => 'Related Item or Detail',
+    'audit_table_related' => 'Пов’язаний елемент',
     'audit_table_date' => 'Дата активності',
     'audit_date_from' => 'Діапазон дат від',
     'audit_date_to' => 'Діапазон дат до',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'Профіль користувача',
     'users_add_new' => 'Додати нового користувача',
     'users_search' => 'Пошук користувачів',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Остання активність',
     'users_details' => 'Відомості про користувача',
     'users_details_desc' => 'Встановіть ім\'я та електронну адресу для цього користувача. Адреса електронної пошти буде використана для входу до програми.',
     'users_details_desc_no_email' => 'Встановіть ім\'я для цього користувача, щоб інші могли його розпізнати.',
@@ -175,10 +180,10 @@ return [
     'users_delete_named' => 'Видалити користувача :userName',
     'users_delete_warning' => 'Це повне видалення цього користувача з ім\'ям \':userName\' з системи.',
     'users_delete_confirm' => 'Ви впевнені, що хочете видалити цього користувача?',
-    '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_delete_success' => 'User successfully removed',
+    'users_migrate_ownership' => 'Право власності при міграції',
+    'users_migrate_ownership_desc' => 'Виберіть тут користувача, якщо ви хочете, щоб інший користувач став власником усіх елементів, які зараз належать цьому користувачеві.',
+    'users_none_selected' => 'Не вибрано жодного користувача',
+    'users_delete_success' => 'Користувача успішно видалено',
     'users_edit' => 'Редагувати користувача',
     'users_edit_profile' => 'Редагувати профіль',
     'users_edit_success' => 'Користувача успішно оновлено',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Dansk',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 51b9a09990ff024bf83d2dac2edab6ccce92f05d..77df1ed4ea627eea4b088b5aaad4b8e67d0af38a 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => 'Текст в полі :attribute повинен містити не менше :min символів.',
         'array'   => 'Поле :attribute повинне містити не менше :min елементів.',
     ],
-    'no_double_extension'  => 'Поле :attribute повинне містити тільки одне розширення файлу.',
     'not_in'               => 'Вибране для :attribute значення не коректне.',
     'not_regex'            => 'Формат поля :attribute не вірний.',
     'numeric'              => 'Поле :attribute повинно містити число.',
@@ -90,7 +89,7 @@ return [
     'required_without'     => 'Поле :attribute є обов\'язковим для заповнення, коли :values не вказано.',
     'required_without_all' => 'Поле :attribute є обов\'язковим для заповнення, коли :values не вказано.',
     'same'                 => 'Поля :attribute та :other мають збігатися.',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => 'Надане посилання може бути небезпечним.',
     'size'                 => [
         'numeric' => 'Поле :attribute має бути довжини :size.',
         'file'    => 'Файл в полі :attribute має бути розміром :size кілобайт.',
index 42b34f38379f3dc98dcee3c9c64c1d9b57e317b0..85a850e274bcc4bb1e9d5c868ccacac7658bc2ac 100644 (file)
@@ -45,5 +45,5 @@ return [
 
     // Other
     'commented_on'                => 'đã bình luận về',
-    'permissions_update'          => 'updated permissions',
+    'permissions_update'          => 'các quyền đã được cập nhật',
 ];
index d1dc8a0489fa3cd425d44f183ba4ef6587c2e11d..b48c65676ad604bdd967f5c2eeb7e21868c70ce1 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => 'Sắp xếp tăng dần',
     'sort_descending' => 'Sắp xếp giảm dần',
     'sort_name' => 'Tên',
+    'sort_default' => 'Default',
     'sort_created_at' => 'Ngày Tạo',
     'sort_updated_at' => 'Ngày cập nhật',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => 'Đường dẫn liên kết',
 
     // Header
+    'header_menu_expand' => 'Expand Header Menu',
     'profile_menu' => 'Menu Hồ sơ',
     'view_profile' => 'Xem Hồ sơ',
     'edit_profile' => 'Sửa Hồ sơ',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => 'Thông tin',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => 'Nội dung',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
     'email_action_help' => 'Nếu bạn đang có vấn đề trong việc bấm nút ":actionText", sao chép và dán địa chỉ URL dưới đây vào trình duyệt web:',
     'email_rights' => 'Bản quyền đã được bảo hộ',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => 'Privacy Policy',
+    'terms_of_service' => 'Terms of Service',
 ];
index 158bdad18efc44e1a136603a0421805786a79238..e72f696488f49171af2ac5e9709ab94d9f094bbf 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => 'Phân quyền',
     'search_created_by_me' => 'Được tạo bởi tôi',
     'search_updated_by_me' => 'Được cập nhật bởi tôi',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => 'Tùy chọn ngày',
     'search_updated_before' => 'Đã được cập nhật trước đó',
     'search_updated_after' => 'Đã được cập nhật sau',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => 'Bạn có chắc bạn muốn khôi phục phiên bản này? Nội dung trang hiện tại sẽ được thay thế.',
     'revision_delete_success' => 'Phiên bản đã được xóa',
     'revision_cannot_delete_latest' => 'Không thể xóa phiên bản mới nhất.'
-];
\ No newline at end of file
+];
index 6410fb97092748f0b91064b4b91d49d5d270e714..0c4a6fa8f5dbd2fc396e8916ff74101251a531f2 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => 'Không Tìm Thấy Trang',
     'sorry_page_not_found' => 'Xin lỗi, Không tìm thấy trang bạn đang tìm kiếm.',
     'sorry_page_not_found_permission_warning' => 'Nếu trang bạn tìm kiếm tồn tại, có thể bạn đang không có quyền truy cập.',
+    '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' => 'Quay lại trang chủ',
     'error_occurred' => 'Đã xảy ra lỗi',
     'app_down' => ':appName hiện đang ngoại tuyến',
index 67fcb1285bbf47f49716f771aa17e113a055d216..6f282a568c4b28ffbb865b3862ab99b3c17af9dd 100644 (file)
@@ -37,6 +37,11 @@ return [
     'app_homepage' => 'Trang chủ Ứng dụng',
     'app_homepage_desc' => 'Chọn hiển thị để hiện tại trang chủ thay cho hiển thị mặc định. Quyền cho trang được bỏ qua cho các trang được chọn.',
     'app_homepage_select' => 'Chọn một trang',
+    '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::<key>" 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' => 'Tắt bình luận',
     'app_disable_comments_toggle' => 'Tắt bình luận',
     'app_disable_comments_desc' => 'Tắt các bình luận trên tất cả các trang của ứng dụng. <br> Các bình luận đã tồn tại sẽ không được hiển thị.',
@@ -86,12 +91,12 @@ return [
     // Recycle Bin
     'recycle_bin' => 'Thùng Rác',
     '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_item' => 'Mục Đã Xóa',
     'recycle_bin_deleted_by' => 'Xóa Bởi',
     'recycle_bin_deleted_at' => 'Thời điểm Xóa',
     'recycle_bin_permanently_delete' => 'Xóa Vĩnh viễn',
     'recycle_bin_restore' => 'Khôi phục',
-    'recycle_bin_contents_empty' => 'The recycle bin is currently empty',
+    'recycle_bin_contents_empty' => 'Thùng rác hiện đang trống',
     'recycle_bin_empty' => 'Dọn dẹp Thùng Rác',
     '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?',
@@ -106,15 +111,15 @@ return [
     '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_event_filter_no_filter' => 'Không Lọc',
+    'audit_deleted_item' => 'Mục Đã Xóa',
+    'audit_deleted_item_name' => 'Tên: :name',
     'audit_table_user' => 'Người dùng',
-    'audit_table_event' => 'Event',
+    'audit_table_event' => 'Sự kiện',
     'audit_table_related' => 'Related Item or Detail',
-    'audit_table_date' => 'Activity Date',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit_table_date' => 'Ngày hoạt động',
+    'audit_date_from' => 'Ngày từ khoảng',
+    'audit_date_to' => 'Ngày đến khoảng',
 
     // Role Settings
     'roles' => 'Quyền',
@@ -157,7 +162,7 @@ return [
     'user_profile' => 'Hồ sơ người dùng',
     'users_add_new' => 'Thêm người dùng mới',
     'users_search' => 'Tìm kiếm người dùng',
-    'users_latest_activity' => 'Latest Activity',
+    'users_latest_activity' => 'Hoạt động mới nhất',
     'users_details' => 'Chi tiết người dùng',
     'users_details_desc' => 'Hiển thị tên và địa chỉ email cho người dùng này. Địa chỉ email sẽ được sử dụng để đăng nhập vào ứng dụng.',
     'users_details_desc_no_email' => 'Đặt tên cho người dùng này để giúp người dùng khác nhận ra họ.',
@@ -177,8 +182,8 @@ return [
     'users_delete_confirm' => 'Bạn có chắc muốn xóa người dùng này không?',
     '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_delete_success' => 'User successfully removed',
+    'users_none_selected' => 'Chưa chọn người dùng',
+    'users_delete_success' => 'Người dùng đã được xóa thành công',
     'users_edit' => 'Sửa người dùng',
     'users_edit_profile' => 'Sửa Hồ sơ',
     'users_edit_success' => 'Người dùng được cập nhật thành công',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => 'Català',
         'cs' => 'Česky',
         'da' => 'Đan Mạch',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index c0020717506345e699dc887ea2d99d32e522a945..bcfb178fb5e2edb9d4bbde9c69e4cbb878534089 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute phải có tối thiểu :min ký tự.',
         'array'   => ':attribute phải có tối thiểu :min mục.',
     ],
-    'no_double_extension'  => ':attribute chỉ được có một định dạng mở rộng duy nhất.',
     'not_in'               => ':attribute đã chọn không hợp lệ.',
     'not_regex'            => 'Định dạng của :attribute không hợp lệ.',
     'numeric'              => ':attribute phải là một số.',
index a7f8017cb68e81bf4b6b2a05c0c85414a1580e6f..dcceb3c6a58ed75876e9a003d1a75256d3653916 100644 (file)
@@ -16,7 +16,7 @@ return [
     'sign_up_with' => '注册:socialDriver',
     'logout' => '注销',
 
-    'name' => 'å§\93å\90\8d',
+    'name' => 'å\90\8dç§°',
     'username' => '用户名',
     'email' => 'Email地址',
     'password' => '密码',
index e96edaf1e990881438461ac00ec051a612e898eb..72f0bd44ac9434cd0e644bae26c49511687abe8c 100644 (file)
@@ -47,6 +47,7 @@ return [
     'sort_ascending' => '升序',
     'sort_descending' => '降序',
     'sort_name' => '名称',
+    'sort_default' => '默认',
     'sort_created_at' => '创建时间',
     'sort_updated_at' => '更新时间',
 
@@ -64,6 +65,7 @@ return [
     'breadcrumb' => '面包屑导航',
 
     // Header
+    'header_menu_expand' => '展开标头菜单',
     'profile_menu' => '个人资料',
     'view_profile' => '查看资料',
     'edit_profile' => '编辑资料',
@@ -72,9 +74,16 @@ return [
 
     // Layout tabs
     'tab_info' => '信息',
+    'tab_info_label' => '标签:显示次要信息',
     'tab_content' => '内容',
+    'tab_content_label' => '标签:显示主要内容',
 
     // Email Content
     'email_action_help' => '如果您无法点击“:actionText”按钮,请将下面的网址复制到您的浏览器中打开:',
     'email_rights' => '版权所有',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => '隐私政策',
+    'terms_of_service' => '服务条款',
 ];
index 986476119fd863bfdf17e847b8310ba78593b460..09564a70a09a36862fd8673d30856ddea0d06540 100644 (file)
@@ -60,6 +60,7 @@ return [
     'search_permissions_set' => '权限设置',
     'search_created_by_me' => '我创建的',
     'search_updated_by_me' => '我更新的',
+    'search_owned_by_me' => '我拥有的',
     'search_date_options' => '日期选项',
     'search_updated_before' => '在此之前更新',
     'search_updated_after' => '在此之后更新',
@@ -316,4 +317,4 @@ return [
     'revision_restore_confirm' => '您确定要恢复到此修订版吗?恢复后原有内容将会被替换。',
     'revision_delete_success' => '修订删除',
     'revision_cannot_delete_latest' => '无法删除最新版本。'
-];
\ No newline at end of file
+];
index b41e21ac3e83b9858edece28d99428dfa581f2b8..7ad049ed40bdbfc9decfb985f30b25d6922646e1 100644 (file)
@@ -83,6 +83,9 @@ return [
     '404_page_not_found' => '无法找到页面',
     'sorry_page_not_found' => '对不起,无法找到您想访问的页面。',
     'sorry_page_not_found_permission_warning' => '您可能没有查看权限。',
+    'image_not_found' => '未找到图片',
+    'image_not_found_subtitle' => '对不起,无法找到您想访问的图片。',
+    'image_not_found_details' => '原本放在这里的图片已被删除。',
     'return_home' => '返回主页',
     'error_occurred' => '出现错误',
     'app_down' => ':appName现在正在关闭',
index 5d58ace2c6c9a33d26313934fbda4a45f4fd4b08..ed222d7f492a9ac2d7ca82573f34f39a1484f3c8 100755 (executable)
@@ -16,7 +16,7 @@ return [
     'app_features_security' => '功能与安全',
     'app_name' => '站点名称',
     'app_name_desc' => '此名称将在网页头部和Email中显示。',
-    'app_name_header' => '在网页头部显示应用名?',
+    'app_name_header' => '在网页头部显示站点名称?',
     'app_public_access' => '访问权限',
     'app_public_access_desc' => '启用此选项将允许未登录的用户访问站点内容。',
     'app_public_access_desc_guest' => '可以通过“访客”用户来控制公共访问者的访问。',
@@ -37,6 +37,11 @@ return [
     'app_homepage' => '站点主页',
     'app_homepage_desc' => '选择要在主页上显示的页面来替换默认的视图,选定页面的访问权限将被忽略。',
     'app_homepage_select' => '选择一个页面',
+    'app_footer_links' => '页脚链接',
+    'app_footer_links_desc' => '添加在网站页脚中显示的链接。这些链接将显示在大多数页面的底部,也包括不需要登录的页面。您可以使用标签"trans::<key>"来使用系统定义的翻译。例如:使用"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' => '在站点的所有页面上禁用评论,现有评论也不会显示出来。',
@@ -68,7 +73,7 @@ return [
     'maint' => '维护',
     'maint_image_cleanup' => '清理图像',
     'maint_image_cleanup_desc' => "扫描页面和修订内容以检查哪些图像是正在使用的以及哪些图像是多余的。确保在运行前创建完整的数据库和映像备份。",
-    'maint_delete_images_only_in_revisions' => '同时删除只存在于旧页面版本中的图片',
+    'maint_delete_images_only_in_revisions' => '同时删除只存在于旧的页面修订中的图片',
     'maint_image_cleanup_run' => '运行清理',
     'maint_image_cleanup_warning' => '发现了 :count 张可能未使用的图像。您确定要删除这些图像吗?',
     'maint_image_cleanup_success' => '找到并删除了 :count 张可能未使用的图像!',
@@ -157,7 +162,7 @@ return [
     'user_profile' => '用户资料',
     'users_add_new' => '添加用户',
     'users_search' => '搜索用户',
-    'users_latest_activity' => '最活动',
+    'users_latest_activity' => '最活动',
     'users_details' => '用户详细资料',
     'users_details_desc' => '设置该用户的显示名称和电子邮件地址。 该电子邮件地址将用于登录本站。',
     'users_details_desc_no_email' => '设置此用户的昵称,以便其他人识别。',
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => '保加利亚语',
+        'bs' => 'Bosanski',
+        'ca' => '加泰罗尼亚语',
         'cs' => 'Česky',
         'da' => '丹麦',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => 'עברית',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => '挪威语 (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 8bb8a207a00099893bf52e37d37ab3ffc4da83b9..72b0d594e75dd852b8002bcdb91b2c8034db5c3d 100644 (file)
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute 至少为:min个字符。',
         'array'   => ':attribute 至少有:min项。',
     ],
-    'no_double_extension'  => ':attribute 必须具有一个扩展名。',
     'not_in'               => '选中的 :attribute 无效。',
     'not_regex'            => ':attribute 格式错误。',
     'numeric'              => ':attribute 必须是一个数。',
index dedcdc0cf0cd469a6ea977b2d7f53c400e3527cc..0c86665b03df03f90b79e0fc65f20f95fc2770c7 100644 (file)
@@ -14,7 +14,7 @@ return [
     'page_delete_notification'    => '頁面已刪除成功',
     'page_restore'                => '已還原頁面',
     'page_restore_notification'   => '頁面已還原成功',
-    'page_move'                   => '移動了頁面',
+    'page_move'                   => '已移動頁面',
 
     // Chapters
     'chapter_create'              => '已建立章節',
@@ -28,7 +28,7 @@ return [
     // Books
     'book_create'                 => '已建立書本',
     'book_create_notification'    => '書本已建立成功',
-    'book_update'                 => '更新了圖書',
+    'book_update'                 => '已更新書本',
     'book_update_notification'    => '書本已更新成功',
     'book_delete'                 => '已刪除書本',
     'book_delete_notification'    => '書本已刪除成功',
index 2ad648589af9cd61cb95c88e3a9ff76d0a138e73..e4f3c79782b6da909c5a0b5e039cfa901c319ead 100644 (file)
@@ -12,8 +12,8 @@ return [
     // Login & Register
     'sign_up' => '註冊',
     'log_in' => '登入',
-    'log_in_with' => '以:socialDriver登入',
-    'sign_up_with' => '註冊:socialDriver',
+    'log_in_with' => '以 :socialDriver 登入',
+    'sign_up_with' => '以 :socialDriver 註冊',
     'logout' => '登出',
 
     'name' => '名稱',
@@ -22,56 +22,56 @@ return [
     'password' => '密碼',
     'password_confirm' => '確認密碼',
     'password_hint' => '必須超過 7 個字元',
-    'forgot_password' => '忘記密碼?',
+    'forgot_password' => '忘記密碼',
     'remember_me' => '記住我',
     'ldap_email_hint' => '請輸入此帳號使用的電子郵件。',
-    'create_account' => '建立帳',
-    'already_have_account' => '已經擁有帳戶?',
-    'dont_have_account' => '沒有帳?',
+    'create_account' => '建立帳',
+    'already_have_account' => '已有帳號?',
+    'dont_have_account' => '沒有帳?',
     'social_login' => '社群網站登入',
-    'social_registration' => '社群網站帳戶註冊',
+    'social_registration' => '使用社群網站帳號註冊',
     'social_registration_text' => '使用其他服務註冊及登入。',
 
     'register_thanks' => '感謝您的註冊!',
     'register_confirm' => '請檢查您的電子郵件,並按下確認按鈕以使用 :appName 。',
     'registrations_disabled' => '目前已停用註冊',
     'registration_email_domain_invalid' => '這個電子郵件網域沒有權限使用',
-    'register_success' => '感謝您註冊:appName,您現在已經登入。',
+    'register_success' => '感謝您註冊!您已註冊完成並可登入。',
 
 
     // Password Reset
-    'reset_password' => '重密碼',
-    'reset_password_send_instructions' => '在下方輸入您的電子郵件,您將收到一封帶有密碼重連結的郵件。',
-    'reset_password_send_button' => '發送重連結',
-    'reset_password_sent' => '重置密碼的連結會發送至電子郵件地址:email(如果系統記錄中存在此電子郵件地址)',
-    'reset_password_success' => '您的密碼已成功重。',
-    'email_reset_subject' => '重置您的:appName密碼',
-    'email_reset_text' => '您收到此電子郵件是因為我們收到了您的帳號的密碼重請求。',
-    'email_reset_not_requested' => '如果您沒有要求重密碼,則不需要採取進一步的操作。',
+    'reset_password' => '重密碼',
+    'reset_password_send_instructions' => '在下方輸入您的電子郵件,您將收到一封帶有密碼重連結的郵件。',
+    'reset_password_send_button' => '發送重連結',
+    'reset_password_sent' => '重設密碼的連結會發送至電子郵件 :email(如果此電子郵件在我們的系統中存在)',
+    'reset_password_success' => '您的密碼已成功重。',
+    'email_reset_subject' => '重設您的 :appName 密碼',
+    'email_reset_text' => '您收到此電子郵件是因為我們收到了您的帳號的密碼重請求。',
+    'email_reset_not_requested' => '如果您沒有要求重密碼,則不需要採取進一步的操作。',
 
 
     // Email Confirmation
-    'email_confirm_subject' => '確認您在:appName的電子郵件',
-    'email_confirm_greeting' => '感謝您加入:appName!',
-    'email_confirm_text' => '請點選下面的按鈕確認您的Email位址:',
-    'email_confirm_action' => '確認Email',
-    'email_confirm_send_error' => '需要Email驗證,但系統無法發送電子郵件,請聯繫網站管理員。',
-    'email_confirm_success' => '您的Email位址已成功驗證!',
-    'email_confirm_resent' => '驗證郵件已重新發送,請檢查收件箱。',
+    'email_confirm_subject' => '確認您在 :appName 的電子郵件',
+    'email_confirm_greeting' => '感謝您加入 :appName!',
+    'email_confirm_text' => '請點選下面的按鈕來確認您的電子郵件地址:',
+    'email_confirm_action' => '確認電子郵件',
+    'email_confirm_send_error' => '需要電子郵件驗證,但系統無法傳送電子郵件。請與管理員聯絡以確保電子郵件正確設定。',
+    'email_confirm_success' => '您的電子郵件已成功驗證!',
+    'email_confirm_resent' => '確認電子郵件已重新傳送。請檢查您的收件匣。',
 
-    'email_not_confirmed' => 'Email位址未驗證',
+    'email_not_confirmed' => '電子郵件地址未確認',
     'email_not_confirmed_text' => '您的電子郵件位址尚未確認。',
     'email_not_confirmed_click_link' => '請檢查註冊時收到的電子郵件,然後點選確認連結。',
-    'email_not_confirmed_resend' => '如果找不到電子郵件,請透過下面的表單重新發送確認Email。',
-    'email_not_confirmed_resend_button' => '重新發送確認Email',
+    'email_not_confirmed_resend' => '如果找不到電子郵件,請透過下面的表單重新發送確認電子郵件。',
+    'email_not_confirmed_resend_button' => '重新傳送確認電子郵件',
 
     // User Invite
-    'user_invite_email_subject' => '您受邀請加入:appName!',
-    'user_invite_email_greeting' => '我們為您在:appName上創建了一個新賬戶。',
-    'user_invite_email_text' => '請點擊下面的按鈕設置賬戶密碼并獲取訪問權限:',
-    'user_invite_email_action' => 'è«\8b設置賬æ\88密碼',
-    'user_invite_page_welcome' => '歡迎使用:appName',
-    'user_invite_page_text' => '要完成設置您的賬戶並獲取訪問權限,您需要設置一個密碼。該密碼將在以後訪問時用於登陸:appName',
-    'user_invite_page_confirm_button' => '請確定密碼',
-    'user_invite_success' => '密碼已設置,您現在可以進入:appName了啦!'
+    'user_invite_email_subject' => '您被邀請加入 :appName!',
+    'user_invite_email_greeting' => '我們為您在 :appName 上建立了一個新帳號。',
+    'user_invite_email_text' => '請點擊下方按鈕來設定帳號密碼並取得存取權:',
+    'user_invite_email_action' => '設å®\9a帳è\99\9f密碼',
+    'user_invite_page_welcome' => '歡迎使用 :appName!',
+    'user_invite_page_text' => '要完成設定您的帳號並取得存取權,您必須設定密碼,此密碼將用於登入 :appName。',
+    'user_invite_page_confirm_button' => '確認密碼',
+    'user_invite_success' => '密碼已設定,您現在可以存取 :appName 了!'
 ];
\ No newline at end of file
index 1d26ded298f6ca1ed15435438d76b843ce066225..ac2a49dccb75cef4f0fe76174efa021190ce6f30 100644 (file)
@@ -10,21 +10,21 @@ return [
     'back' => '返回',
     'save' => '儲存',
     'continue' => '繼續',
-    'select' => '選',
-    'toggle_all' => '換全部',
+    'select' => '選',
+    'toggle_all' => '換全部',
     'more' => '更多',
 
     // Form Labels
     'name' => '名稱',
-    'description' => 'æ\91\98è¦\81',
+    'description' => 'æ\8f\8fè¿°',
     'role' => '角色',
     'cover_image' => '封面圖片',
-    'cover_image_description' => 'æ\89\80使ç\94¨å\9c\96ç\89\87大å°\8få¿\85é \88æ\98¯440x250px。',
+    'cover_image_description' => 'æ­¤å\9c\96ç\89\87大å°\8fæ\87\89ç´\84ç\82º 440x250px。',
     
     // Actions
     'actions' => '動作',
     'view' => '檢視',
-    'view_all' => '視全部',
+    'view_all' => '視全部',
     'create' => '建立',
     'update' => '更新',
     'edit' => '編輯',
@@ -36,45 +36,54 @@ return [
     'delete_confirm' => '確認刪除',
     'search' => '搜尋',
     'search_clear' => '清除搜尋',
-    'reset' => '重',
-    'remove' => '除',
+    'reset' => '重',
+    'remove' => '除',
     'add' => '新增',
-    'fullscreen' => '全屏顯示',
+    'fullscreen' => '全螢幕',
 
     // Sort Options
-    'sort_options' => '選項分類',
+    'sort_options' => '排序選項',
     'sort_direction_toggle' => '順序方向切換',
-    'sort_ascending' => '序',
-    'sort_descending' => 'é\99\8d序',
+    'sort_ascending' => '遞增排序',
+    'sort_descending' => 'é\81\9eæ¸\9bæ\8e\92序',
     'sort_name' => '名稱',
-    'sort_created_at' => '創建日期',
+    'sort_default' => 'Default',
+    'sort_created_at' => '建立日期',
     'sort_updated_at' => '更新日期',
 
     // Misc
-    'deleted_user' => '刪除使用者',
-    'no_activity' => '無活動',
-    'no_items' => '無項目',
+    'deleted_user' => 'å·²å\88ªé\99¤ä½¿ç\94¨è\80\85',
+    'no_activity' => '無活動可顯示',
+    'no_items' => '無可用項目',
     'back_to_top' => '回到頂端',
-    'toggle_details' => '顯示/隱藏詳細資訊',
-    'toggle_thumbnails' => '顯示/隱藏縮圖',
+    'toggle_details' => '顯示隱藏詳細資訊',
+    'toggle_thumbnails' => '顯示隱藏縮圖',
     'details' => '詳細資訊',
-    'grid_view' => '縮å\9c\96檢視',
-    'list_view' => '清單撿視',
+    'grid_view' => '網格檢視',
+    'list_view' => '列表檢視',
     'default' => '預設',
-    'breadcrumb' => '導覽路徑',
+    'breadcrumb' => '頁面路徑',
 
     // Header
-    'profile_menu' => '個人資料菜單',
-    'view_profile' => '檢視資料',
-    'edit_profile' => '編輯資料',
+    'header_menu_expand' => 'Expand Header Menu',
+    'profile_menu' => '個人資料選單',
+    'view_profile' => '檢視個人資料',
+    'edit_profile' => '編輯個人資料',
     'dark_mode' => '深色模式',
-    'light_mode' => 'æ\98\8e亮模式',
+    'light_mode' => 'æ·ºè\89²模式',
 
     // Layout tabs
-    'tab_info' => '訊息',
+    'tab_info' => '資訊',
+    'tab_info_label' => 'Tab: Show Secondary Information',
     'tab_content' => '內容',
+    'tab_content_label' => 'Tab: Show Primary Content',
 
     // Email Content
-    'email_action_help' => '如果您無法點選“:actionText”按鈕,請將下面的網址複製到您的瀏覽器中打開:',
+    'email_action_help' => '如果您無法點擊 ":actionText" 按鈕,請將下方的網址複製並貼上到您的網路瀏覽器中:',
     'email_rights' => '版權所有',
+
+    // Footer Link Options
+    // Not directly used but available for convenience to users.
+    'privacy_policy' => '隱私權政策',
+    'terms_of_service' => '服務條款',
 ];
index b200fa7948506c3d56e5e653642b2c38a690f8ca..62f515f5ef8e1b6f274e9beea77da030289bc658 100644 (file)
@@ -5,7 +5,7 @@
 return [
 
     // Image Manager
-    'image_select' => '選圖片',
+    'image_select' => '選圖片',
     'image_all' => '全部',
     'image_all_title' => '檢視所有圖片',
     'image_book_title' => '檢視上傳到此書本的圖片',
@@ -14,10 +14,10 @@ return [
     'image_uploaded' => '上傳於 :uploadedDate',
     'image_load_more' => '載入更多',
     'image_image_name' => '圖片名稱',
-    'image_delete_used' => 'æ\89\80使ç\94¨å\9c\96ç\89\87ç\9b®å\89\8d用於以下頁面。',
+    'image_delete_used' => 'æ­¤å\9c\96ç\89\87用於以下頁面。',
     'image_delete_confirm_text' => '您確認想要刪除這個圖片?',
-    'image_select_image' => '選圖片',
-    'image_dropzone' => '拖曳圖片或點選這裡上傳',
+    'image_select_image' => '選圖片',
+    'image_dropzone' => '拖曳圖片或點擊此處上傳',
     'images_deleted' => '圖片已刪除',
     'image_preview' => '圖片預覽',
     'image_upload_success' => '圖片上傳成功',
@@ -29,6 +29,6 @@ return [
     'code_editor' => '編輯程式碼',
     'code_language' => '程式語言',
     'code_content' => '程式碼內容',
-    'code_session_history' => 'Session 歷程',
+    'code_session_history' => '工作階段歷史',
     'code_save' => '儲存程式碼',
 ];
index ca98df0d4d321734edc9d96ca6eb186fcc514df4..564cd3891d2a59bbcfad668999dce98aa0ca805c 100644 (file)
@@ -8,58 +8,59 @@ return [
     // Shared
     'recently_created' => '最近建立',
     'recently_created_pages' => '最近建立的頁面',
-    'recently_updated_pages' => '最頁面',
+    'recently_updated_pages' => '最近更新的頁面',
     'recently_created_chapters' => '最近建立的章節',
     'recently_created_books' => '最近建立的書本',
-    'recently_created_shelves' => '最近建立的章節',
+    'recently_created_shelves' => '最近建立的書架',
     'recently_update' => '最近更新',
-    'recently_viewed' => '最近看過',
+    'recently_viewed' => '最近檢視',
     'recent_activity' => '近期活動',
     'create_now' => '立即建立',
-    'revisions' => '修訂歷史',
-    'meta_revision' => '版本號 #:revisionCount',
+    'revisions' => '修訂版本',
+    'meta_revision' => '修訂版本 #:revisionCount',
     'meta_created' => '建立於 :timeLength',
     'meta_created_name' => '由 :user 建立於 :timeLength',
     'meta_updated' => '更新於 :timeLength',
     'meta_updated_name' => '由 :user 更新於 :timeLength',
     'meta_owned_name' => ':user 所擁有',
-    'entity_select' => '選項目',
+    'entity_select' => '選項目',
     'images' => '圖片',
     'my_recent_drafts' => '我最近的草稿',
-    'my_recently_viewed' => '我最近看過',
-    'no_pages_viewed' => '您還沒有看過任何頁面',
-    'no_pages_recently_created' => 'æ\9c\80è¿\91æ²\92æ\9c\89é \81é\9d¢è¢«å»ºç«\8b',
+    'my_recently_viewed' => '我最近檢視',
+    'no_pages_viewed' => '您尚未看過任何頁面',
+    'no_pages_recently_created' => 'æ\9c\80è¿\91æ\9cªå»ºç«\8bä»»ä½\95é \81é\9d¢',
     'no_pages_recently_updated' => '最近沒有頁面被更新',
     'export' => '匯出',
     'export_html' => '網頁檔案',
-    'export_pdf' => 'PDF檔案',
+    'export_pdf' => 'PDF 檔案',
     'export_text' => '純文字檔案',
 
     // Permissions and restrictions
     'permissions' => '權限',
-    'permissions_intro' => '本設定優先權高於每個使用者角色本身所具有的權限。',
+    'permissions_intro' => '一旦啟用,這些權限將優先於任何設定的角色權限。',
     'permissions_enable' => '啟用自訂權限',
     'permissions_save' => '儲存權限',
     'permissions_owner' => '擁有者',
 
     // Search
     'search_results' => '搜尋結果',
-    'search_total_results_found' => '共找到了:count個結果|共找到了:count個結果',
+    'search_total_results_found' => '找到了 :count 個結果 | 總共 :count 個結果',
     'search_clear' => '清除搜尋',
-    'search_no_pages' => '沒有找到符合的頁面',
-    'search_for_term' => '“:term”的搜尋結果',
+    'search_no_pages' => '沒有與此搜尋相符的頁面',
+    'search_for_term' => ':term 的搜尋結果',
     'search_more' => '更多結果',
     'search_advanced' => '進階搜尋',
     'search_terms' => '搜尋字串',
-    'search_content_type' => '種類',
+    'search_content_type' => '內容類型',
     'search_exact_matches' => '精確符合',
     'search_tags' => '標籤搜尋',
     'search_options' => '選項',
-    'search_viewed_by_me' => '我看過的',
-    'search_not_viewed_by_me' => 'æ\88\91æ²\92ç\9c\8bé\81\8eç\9a\84',
+    'search_viewed_by_me' => '被我檢視',
+    'search_not_viewed_by_me' => 'æ\9cªè¢«æ\88\91檢è¦\96',
     'search_permissions_set' => '權限設定',
     'search_created_by_me' => '我建立的',
     'search_updated_by_me' => '我更新的',
+    'search_owned_by_me' => 'Owned by me',
     'search_date_options' => '日期選項',
     'search_updated_before' => '在此之前更新',
     'search_updated_after' => '在此之後更新',
@@ -71,10 +72,10 @@ return [
     // Shelves
     'shelf' => '書架',
     'shelves' => '書架',
-    'x_shelves' => ':count 書架|:count 章節',
+    'x_shelves' => ':count 書架 | :count 章節',
     'shelves_long' => '書架',
-    'shelves_empty' => '不存在已建立的書架',
-    'shelves_create' => '建立書架',
+    'shelves_empty' => '尚未建立書架',
+    'shelves_create' => '建ç«\8bæ\96°æ\9b¸æ\9e¶',
     'shelves_popular' => '熱門書架',
     'shelves_new' => '新書架',
     'shelves_new_action' => '建立新的書架',
@@ -82,57 +83,57 @@ return [
     'shelves_new_empty' => '最近建立的書架將出現在這裡。',
     'shelves_save' => '儲存書架',
     'shelves_books' => '此書架上的書本',
-    'shelves_add_books' => '將書本添加到此書架中',
-    'shelves_drag_books' => '拖動書本到此處來將它添加至此書架中',
+    'shelves_add_books' => '新增書本至此書架',
+    'shelves_drag_books' => '將書本拖曳到此處來將其新增到此書架',
     'shelves_empty_contents' => '此書架沒有分配任何書本',
     'shelves_edit_and_assign' => '編輯書架以分配書本',
-    'shelves_edit_named' => '編輯書架「:name」',
+    'shelves_edit_named' => '編輯書架 :name',
     'shelves_edit' => '編輯書架',
     'shelves_delete' => '刪除書架',
-    'shelves_delete_named' => '刪除書架「:name」',
-    'shelves_delete_explain' => "這將刪除名為「:name」的書架。包含在其中的書本不會被刪除。",
+    'shelves_delete_named' => '刪除書架 :name',
+    'shelves_delete_explain' => "這將刪除名為「:name」的書架。其中的書本不會被刪除。",
     'shelves_delete_confirmation' => '您確定要刪除此書架嗎?',
     'shelves_permissions' => '書架權限',
     'shelves_permissions_updated' => '書架權限已更新',
-    'shelves_permissions_active' => '已啟用此書架的自訂權限',
+    'shelves_permissions_active' => '書架權限已啟用',
     'shelves_copy_permissions_to_books' => '將權限複製到書本',
     'shelves_copy_permissions' => '複製權限',
-    'shelves_copy_permissions_explain' => '這會將此書架目前的權限設定套用到所有包含的書本上。在生效之前,請確認您已儲存任何對此書架權限的變更。',
-    'shelves_copy_permission_success' => '已將書架的權限複製到:count本書上',
+    'shelves_copy_permissions_explain' => '這會將此書架目前的權限設定套用到所有包含的書本上。在啟用前,請確認您已儲存任何對此書架權限的變更。',
+    'shelves_copy_permission_success' => '已將書架的權限複製到 :count 本書上',
 
     // Books
     'book' => '書本',
     'books' => '書本',
-    'x_books' => ':count本書|:count本書',
+    'x_books' => ':count 本書 | :count本書',
     'books_empty' => '不存在已建立的書',
     'books_popular' => '熱門書本',
-    'books_recent' => '最近的書',
-    'books_new' => '新書',
-    'books_new_action' => '新增一本書',
+    'books_recent' => '近期書本',
+    'books_new' => '新書',
+    'books_new_action' => '新書本',
     'books_popular_empty' => '最受歡迎的書本將出現在這裡。',
     'books_new_empty' => '最近建立的書本將出現在這裡。',
-    'books_create' => '建立書本',
+    'books_create' => '建ç«\8bæ\96°æ\9b¸æ\9c¬',
     'books_delete' => '刪除書本',
-    'books_delete_named' => '刪除書本「:bookName」',
+    'books_delete_named' => '刪除書本 :bookName',
     'books_delete_explain' => '這將刪除書本「:bookName」。所有的章節和頁面都會被刪除。',
     'books_delete_confirmation' => '您確定要刪除此書本嗎?',
     'books_edit' => '編輯書本',
     'books_edit_named' => '編輯書本「:bookName」',
-    'books_form_book_name' => '書',
+    'books_form_book_name' => '書本名稱',
     'books_save' => '儲存書本',
     'books_permissions' => '書本權限',
     'books_permissions_updated' => '書本權限已更新',
     'books_empty_contents' => '本書目前沒有頁面或章節。',
-    'books_empty_create_page' => '建立頁面',
+    'books_empty_create_page' => '建立頁面',
     'books_empty_sort_current_book' => '排序目前書本',
-    'books_empty_add_chapter' => '加入章節',
-    'books_permissions_active' => '已啟用此書本的自訂權限',
-    'books_search_this' => '搜尋這本書',
+    'books_empty_add_chapter' => '新增章節',
+    'books_permissions_active' => '書本權限已啟用',
+    'books_search_this' => '搜尋此書本',
     'books_navigation' => '書本導覽',
     'books_sort' => '排序書本內容',
-    'books_sort_named' => '排序書本「:bookName」',
+    'books_sort_named' => '排序書本 :bookName',
     'books_sort_name' => '按名稱排序',
-    'books_sort_created' => 'æ\8c\89å\89µå»º時間排序',
+    'books_sort_created' => 'æ\8c\89建ç«\8b時間排序',
     'books_sort_updated' => '按更新時間排序',
     'books_sort_chapters_first' => '第一章',
     'books_sort_chapters_last' => '最後一章',
@@ -142,43 +143,43 @@ return [
     // Chapters
     'chapter' => '章節',
     'chapters' => '章節',
-    'x_chapters' => ':count個章節|:count個章節',
+    'x_chapters' => ':count個章節 | :count個章節',
     'chapters_popular' => '熱門章節',
     'chapters_new' => '新章節',
     'chapters_create' => '建立章節',
     'chapters_delete' => '刪除章節',
-    'chapters_delete_named' => '刪除章節「: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_named' => '刪除章節 :chapterName',
+    'chapters_delete_explain' => '這將會刪除名稱為「:chapterName」的章節。此章節中的所有頁面都將會被刪除。',
     'chapters_delete_confirm' => '您確定要刪除此章節嗎?',
     'chapters_edit' => '編輯章節',
     'chapters_edit_named' => '編輯章節「:chapterName」',
     'chapters_save' => '儲存章節',
     'chapters_move' => '移動章節',
-    'chapters_move_named' => '移動章節「:chapterName」',
-    'chapter_move_success' => '章節移動到「:bookName」',
+    'chapters_move_named' => '移動章節 :chapterName',
+    'chapter_move_success' => '章節移動到 :bookName',
     'chapters_permissions' => '章節權限',
     'chapters_empty' => '本章目前沒有頁面。',
-    'chapters_permissions_active' => '已啟用此章節的自訂權限',
+    'chapters_permissions_active' => '章節權限已啟用',
     'chapters_permissions_success' => '章節權限已更新',
-    'chapters_search_this' => '從本章節搜尋',
+    'chapters_search_this' => '搜尋此章節',
 
     // Pages
     'page' => '頁面',
     'pages' => '頁面',
-    'x_pages' => ':count個頁面|:count個頁面',
+    'x_pages' => ':count 頁 | :count 頁',
     'pages_popular' => '熱門頁面',
     'pages_new' => '新頁面',
     'pages_attachments' => '附件',
     'pages_navigation' => '頁面導覽',
     'pages_delete' => '刪除頁面',
-    'pages_delete_named' => '刪除頁面“:pageName”',
-    'pages_delete_draft_named' => '刪除草稿頁面“:pageName”',
+    'pages_delete_named' => '刪除頁面 :pageName',
+    'pages_delete_draft_named' => '刪除草稿頁面 :pageName',
     'pages_delete_draft' => '刪除草稿頁面',
     'pages_delete_success' => '頁面已刪除',
     'pages_delete_draft_success' => '草稿頁面已刪除',
     'pages_delete_confirm' => '您確定要刪除此頁面嗎?',
     'pages_delete_draft_confirm' => '您確定要刪除此草稿頁面嗎?',
-    'pages_editing_named' => '正在編輯頁面“:pageName”',
+    'pages_editing_named' => '正在編輯頁面 :pageName',
     'pages_edit_draft_options' => '草稿選項',
     'pages_edit_save_draft' => '儲存草稿',
     'pages_edit_draft' => '編輯頁面草稿',
@@ -187,9 +188,9 @@ return [
     'pages_edit_draft_save_at' => '草稿儲存於 ',
     'pages_edit_delete_draft' => '刪除草稿',
     'pages_edit_discard_draft' => '放棄草稿',
-    'pages_edit_set_changelog' => '更新說明',
-    'pages_edit_enter_changelog_desc' => '輸入對您所做更改的簡易說明',
-    'pages_edit_enter_changelog' => '輸入更新說明',
+    'pages_edit_set_changelog' => '設定變更日誌',
+    'pages_edit_enter_changelog_desc' => '輸入對您所做變動的簡易描述',
+    'pages_edit_enter_changelog' => '輸入變更日誌',
     'pages_save' => '儲存頁面',
     'pages_title' => '頁面標題',
     'pages_name' => '頁面名稱',
@@ -198,7 +199,7 @@ return [
     'pages_md_insert_image' => '插入圖片',
     'pages_md_insert_link' => '插入連結',
     'pages_md_insert_drawing' => '插入繪圖',
-    'pages_not_in_chapter' => '本頁面不在某章節中',
+    'pages_not_in_chapter' => '頁面不在章節中',
     'pages_move' => '移動頁面',
     'pages_move_success' => '頁面已移動到「:parentName」',
     'pages_copy' => '複製頁面',
@@ -206,38 +207,38 @@ return [
     'pages_copy_success' => '頁面已成功複製',
     'pages_permissions' => '頁面權限',
     'pages_permissions_success' => '頁面權限已更新',
-    'pages_revision' => '修訂',
-    'pages_revisions' => '頁面修訂',
-    'pages_revisions_named' => '“:pageName”頁面修訂',
-    'pages_revision_named' => '“:pageName”頁面修訂',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision' => '修訂版本',
+    'pages_revisions' => '頁面修訂版本',
+    'pages_revisions_named' => ':pageName 頁面修訂版本',
+    'pages_revision_named' => ':pageName 頁面修訂版本',
+    'pages_revision_restored_from' => '從 #:id; :summary 復原',
     'pages_revisions_created_by' => '建立者',
     'pages_revisions_date' => '修訂日期',
     'pages_revisions_number' => '#',
-    'pages_revisions_numbered' => 'ä¿®è¨\82ç·¨è\99\9f:id',
-    'pages_revisions_numbered_changes' => 'ä¿®è¨\82ç·¨è\99\9f:id æ\9b´æ\94¹',
-    'pages_revisions_changelog' => '更新說明',
-    'pages_revisions_changes' => '更新紀錄',
+    'pages_revisions_numbered' => 'ä¿®è¨\82ç\89\88æ\9c¬ #:id',
+    'pages_revisions_numbered_changes' => 'ä¿®è¨\82ç\89\88æ\9c¬ #:id è®\8aæ\9b´',
+    'pages_revisions_changelog' => '變動日誌',
+    'pages_revisions_changes' => '變動',
     'pages_revisions_current' => '目前版本',
     'pages_revisions_preview' => '預覽',
-    'pages_revisions_restore' => '恢複',
+    'pages_revisions_restore' => '還原',
     'pages_revisions_none' => '此頁面沒有修訂',
     'pages_copy_link' => '複製連結',
-    'pages_edit_content_link' => 'ç¼\96è¾\91å\86\85容',
-    'pages_permissions_active' => '已啟用此頁面的自訂權限',
+    'pages_edit_content_link' => '編輯å\85§容',
+    'pages_permissions_active' => '頁面權限已啟用',
     'pages_initial_revision' => '初次發布',
     'pages_initial_name' => '新頁面',
-    'pages_editing_draft_notification' => '您正在編輯在 :timeDiff 內儲存的草稿.',
-    'pages_draft_edited_notification' => '此頁面已經被更新過建議您放棄此草稿。',
+    'pages_editing_draft_notification' => '您正在編輯最後儲存為 :timeDiff 的草稿。',
+    'pages_draft_edited_notification' => '此頁面已經被更新過建議您放棄此草稿。',
     'pages_draft_edit_active' => [
-        'start_a' => ':count位使用者正在編輯此頁面',
-        'start_b' => '使用者“:userName”已經開始編輯此頁面',
+        'start_a' => ':count 位使用者已經開始編輯此頁面',
+        'start_b' => '使用者 :userName 已經開始編輯此頁面',
         'time_a' => '自頁面上次更新以來',
         'time_b' => '在最近:minCount分鐘',
-        'message' => ':time,:start。注意不要覆蓋到對方的更新。',
+        'message' => ':start :time。注意不要覆寫其他人的更新!',
     ],
-    'pages_draft_discarded' => '草稿已丟棄,編輯器已更新到目前頁面內容。',
-    'pages_specific' => '定頁面',
+    'pages_draft_discarded' => '草稿已丟棄。編輯器已更新到目前頁面內容',
+    'pages_specific' => '定頁面',
     'pages_is_template' => '頁面模板',
 
     // Editor Sidebar
@@ -248,39 +249,39 @@ return [
     'tag' => '標籤',
     'tags' =>  '標籤',
     'tag_name' =>  '標籤名稱',
-    'tag_value' => '標籤值 (非必要)',
-    'tags_explain' => "加入一些標籤以更好地對您的內容進行分類。\n您可以為標籤分配一個值,以進行更深入的組織。",
-    'tags_add' => '加入另一個標籤',
+    'tag_value' => '標籤值(選擇性)',
+    'tags_explain' => "加入一些標籤以更好地對您的內容進行分類。 \n 您可以為標籤分配一個值,以進行更深入的組織。",
+    'tags_add' => '新增另一個標籤',
     'tags_remove' => '移除此標籤',
     'attachments' => '附件',
-    'attachments_explain' => '上傳一些檔案或附加連結顯示在您的網頁上。將顯示在在頁面的側邊欄。',
-    'attachments_explain_instant_save' => '這裡的更改將立即儲存。Changes here are saved instantly.',
-    'attachments_items' => '附加項目',
+    'attachments_explain' => '上傳一些檔案或附加連結顯示在您的網頁上。將顯示在在頁面的側邊欄。',
+    'attachments_explain_instant_save' => '此處的變動將會立刻儲存。',
+    'attachments_items' => '附',
     'attachments_upload' => '上傳檔案',
     'attachments_link' => '附加連結',
     'attachments_set_link' => '設定連結',
-    'attachments_delete' => '確定要刪除此附件嗎?',
-    'attachments_dropzone' => '刪除檔案或點選此處加入檔案',
+    'attachments_delete' => '確定要刪除此附件嗎?',
+    'attachments_dropzone' => '拖曳檔案或點擊此處來附加檔案',
     'attachments_no_files' => '尚未上傳檔案',
-    'attachments_explain_link' => '如果您不想上傳檔案,則可以附加連結這可以是指向其他頁面的連結,也可以是指向雲端檔案的連結。',
+    'attachments_explain_link' => '如果您不想上傳檔案,則可以附加連結這可以是指向其他頁面的連結,也可以是指向雲端檔案的連結。',
     'attachments_link_name' => '連結名稱',
     'attachment_link' => '附件連結',
     'attachments_link_url' => '連結到檔案',
     'attachments_link_url_hint' => '網站或檔案的網址',
     'attach' => '附加',
-    'attachments_insert_link' => '將附件連結增加到頁面',
+    'attachments_insert_link' => '將附件連結新增到頁面',
     'attachments_edit_file' => '編輯檔案',
     'attachments_edit_file_name' => '檔案名稱',
-    'attachments_edit_drop_upload' => '刪除檔案或點選這裡上傳並覆蓋',
+    'attachments_edit_drop_upload' => '拖曳檔案或點擊此處以上傳並覆寫',
     'attachments_order_updated' => '附件順序已更新',
     'attachments_updated_success' => '附件資訊已更新',
     'attachments_deleted' => '附件已刪除',
     'attachments_file_uploaded' => '附件上傳成功',
     'attachments_file_updated' => '附件更新成功',
     'attachments_link_attached' => '連結成功附加到頁面',
-    'templates' => '本',
-    'templates_set_as_template' => '頁面是模板',
-    'templates_explain_set_as_template' => '您可以將此頁面設置為模板,以便在創建其他頁面時利用其內容。 如果其他用戶對此頁面擁有查看權限,則將可以使用此模板。',
+    'templates' => '本',
+    'templates_set_as_template' => '頁面為範本',
+    'templates_explain_set_as_template' => '您可以將此頁面設定為範本,以便在建立其他頁面時利用其內容。如果其他使用者對此頁面擁有檢視權限,則將可以使用此範本。',
     'templates_replace_content' => '替換頁面內容',
     'templates_append_content' => '附加到頁面內容',
     'templates_prepend_content' => '前置頁面內容',
@@ -288,9 +289,9 @@ return [
     // Profile View
     'profile_user_for_x' => '來這裡:time了',
     'profile_created_content' => '已建立內容',
-    'profile_not_created_pages' => ':userName尚未建立任何頁面',
-    'profile_not_created_chapters' => ':userName尚未建立任何章節',
-    'profile_not_created_books' => ':userName尚未建立任何書本',
+    'profile_not_created_pages' => ':userName 尚未建立任何頁面',
+    'profile_not_created_chapters' => ':userName 尚未建立任何章節',
+    'profile_not_created_books' => ':userName 尚未建立任何書本',
     'profile_not_created_shelves' => ':userName 沒有創建任何書架',
 
     // Comments
@@ -298,22 +299,22 @@ return [
     'comments' => '評論',
     'comment_add' => '新增評論',
     'comment_placeholder' => '在這裡評論',
-    'comment_count' => '{0} 無評論|{1} :count條評論|[2,*] :count條評論',
+    'comment_count' => '{0} 無評論 |{1} :count 則評論 | [2,*] :count 則評論',
     'comment_save' => '儲存評論',
-    'comment_saving' => '正在儲存評論...',
-    'comment_deleting' => '正在刪除評論...',
+    'comment_saving' => '正在儲存評論……',
+    'comment_deleting' => '正在刪除評論……',
     'comment_new' => '新評論',
     'comment_created' => '評論於 :createDiff',
     'comment_updated' => '由 :username 於 :updateDiff 更新',
     'comment_deleted_success' => '評論已刪除',
     'comment_created_success' => '評論已加入',
     'comment_updated_success' => '評論已更新',
-    'comment_delete_confirm' => '你確定要刪除這條評論?',
+    'comment_delete_confirm' => '您確定要刪除這則評論?',
     'comment_in_reply_to' => '回覆 :commentId',
 
     // Revision
-    'revision_delete_confirm' => '您確定要刪除此修訂版嗎?',
-    'revision_restore_confirm' => '您確定要還原此修訂版嗎? 當前頁面內容將被替換。',
-    'revision_delete_success' => '修訂刪除',
-    'revision_cannot_delete_latest' => '無法刪除最新版本。'
-];
\ No newline at end of file
+    'revision_delete_confirm' => '您確定要刪除此修訂版本嗎?',
+    'revision_restore_confirm' => '您確定要還原此修訂版本嗎? 目前頁面內容將被替換。',
+    'revision_delete_success' => '修訂版本已刪除',
+    'revision_cannot_delete_latest' => '無法刪除最新修訂版本。'
+];
index 8604cc6c2690f24f8e62093adedd79f31926faa1..0435fc8ad4c9b8f269bbf4e879913865ca48861d 100644 (file)
@@ -6,61 +6,61 @@ return [
 
     // Permissions
     'permission' => '您沒有權限進入所請求的頁面。',
-    'permissionJson' => '您沒有權限執行所請求的作。',
+    'permissionJson' => '您沒有權限執行所請求的作。',
 
     // Auth
-    'error_user_exists_different_creds' => 'Email為 :email 的使用者已經存在,但具有不同的憑據。',
-    'email_already_confirmed' => 'Email已被確認,請嘗試登錄。',
-    'email_confirmation_invalid' => '此確認 Session 無效或已被使用,請重新註冊。',
-    'email_confirmation_expired' => '確認 Session 已過期,已發送新的確認電子郵件。',
-    'email_confirmation_awaiting' => '用於此賬戶的電子郵箱需要認證',
-    'ldap_fail_anonymous' => '使用匿名綁定的LDAP進入失敗。',
-    'ldap_fail_authed' => '帶有標識名稱和密碼的LDAP進入失敗。',
-    '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 登失敗,系統未提供成功的授權',
-    'social_no_action_defined' => 'æ²\92æ\9c\89å®\9a義è¡\8cç\82º',
-    'social_login_bad_response' => "在 :socialAccount 登錄時遇到錯誤:\n:error",
-    'social_account_in_use' => ':socialAccount 帳號已被使用,請嘗試透過 :socialAccount 選項登。',
-    'social_account_email_in_use' => 'Email :email 已經被使用。如果您已有帳號,則可以在個人資料設定中綁定您的 :socialAccount。',
-    'social_account_existing' => ':socialAccount已經被綁定到您的帳號。',
-    'social_account_already_used_existing' => ':socialAccount帳號已經被其他使用者使用。',
-    'social_account_not_used' => ':socialAccount帳號沒有綁定到任何使用者,請在您的個人資料設定中綁定。 ',
+    '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' => '未安裝 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 登失敗,系統未提供成功的授權',
+    'social_no_action_defined' => 'æ\9cªå®\9a義å\8b\95ä½\9c',
+    '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' => 'æ\9cªæ\89¾到社交驅動程式',
-    'social_driver_not_configured' => '您的:socialAccount社交設定不正確。',
-    'invite_token_expired' => 'æ­¤é\82\80è«\8bé\8f\88æ\8e¥å·²é\81\8eæ\9c\9fï¼\8cæ\82¨å\8f¯ä»¥å\98\97試é\87\8dç½®æ\82¨ç\9a\84賬æ\88密碼。',
+    'social_driver_not_found' => 'æ\89¾ä¸\8d到社交驅動程式',
+    'social_driver_not_configured' => '您的 :socialAccount 社交設定不正確。',
+    'invite_token_expired' => 'æ­¤é\82\80è«\8bé\80£çµ\90å·²é\81\8eæ\9c\9fã\80\82æ\82¨å\8f¯ä»¥å\98\97試é\87\8d設æ\82¨ç\9a\84帳è\99\9f密碼。',
 
     // System
-    'path_not_writable' => '無法上傳到檔案路徑“:filePath”,請確保它可寫入伺服器。',
-    'cannot_get_image_from_url' => '無法從 :url 中獲取圖片',
-    'cannot_create_thumbs' => '伺服器無法建立縮圖,請檢查您是否安裝了GD PHP外掛。',
-    'server_upload_limit' => 'ä¸\8aå\82³ç\9a\84æª\94æ¡\88大å°\8fè¶\85é\81\8e伺æ\9c\8då\99¨å\85\81許ä¸\8aé\99\90。請嘗試較小的檔案。',
-    'uploaded'  => 'ä¸\8aå\82³ç\9a\84æª\94æ¡\88大å°\8fè¶\85é\81\8e伺æ\9c\8då\99¨å\85\81許ä¸\8aé\99\90。請嘗試較小的檔案。',
+    'path_not_writable' => '無法上傳到 :filePath 檔案路徑。請確定其對伺服器來說是可寫入的。',
+    'cannot_get_image_from_url' => '無法從 :url 取得圖片',
+    'cannot_create_thumbs' => '伺服器無法建立縮圖。請檢查您是否安裝了 PHP 的 GD 擴充程式。',
+    'server_upload_limit' => '伺æ\9c\8då\99¨ä¸\8då\85\81許ä¸\8aå\82³é\80\99å\80\8b大ç\9a\84æª\94æ¡\88。請嘗試較小的檔案。',
+    'uploaded'  => '伺æ\9c\8då\99¨ä¸\8då\85\81許ä¸\8aå\82³é\80\99å\80\8b大ç\9a\84æª\94æ¡\88。請嘗試較小的檔案。',
     'image_upload_error' => '上傳圖片時發生錯誤',
-    'image_upload_type_error' => '上傳圖片類型錯誤',
-    'file_upload_timeout' => 'æ\96\87ä»¶ä¸\8aå\82³å·²è¶\85時。',
+    'image_upload_type_error' => '上傳圖片類型無效',
+    'file_upload_timeout' => 'æª\94æ¡\88ä¸\8aå\82³é\80¾時。',
 
     // Attachments
-    'attachment_not_found' => 'æ²\92æ\9c\89æ\89¾到附件',
+    'attachment_not_found' => 'æ\89¾ä¸\8d到附件',
 
     // Pages
-    'page_draft_autosave_fail' => '無法儲存草稿,確保您在儲存頁面之前已經連接到互聯網',
-    'page_custom_home_deletion' => '無法刪除一個被設定為首頁的頁面',
+    'page_draft_autosave_fail' => '無法儲存草稿。請確保您在儲存此頁面前已連線至網際網路',
+    'page_custom_home_deletion' => '無法刪除被設定為首頁的頁面',
 
     // Entities
-    'entity_not_found' => 'æ\9cªæ\89¾到實體',
-    'bookshelf_not_found' => 'æ\9cªæ\89¾到書架',
-    'book_not_found' => 'æ\9cªæ\89¾å\88°å\9c\96æ\9b¸',
-    'page_not_found' => 'æ\9cªæ\89¾到頁面',
-    'chapter_not_found' => 'æ\9cªæ\89¾到章節',
-    'selected_book_not_found' => '選中的書未找到',
-    'selected_book_chapter_not_found' => 'æ\9cªæ\89¾å\88°æ\89\80é\81¸ç\9a\84å\9c\96æ\9b¸或章節',
-    'guests_cannot_save_drafts' => '訪客不能儲存草稿',
+    'entity_not_found' => 'æ\89¾ä¸\8d到實體',
+    'bookshelf_not_found' => 'æ\89¾ä¸\8d到書架',
+    'book_not_found' => 'æ\89¾ä¸\8då\88°æ\9b¸æ\9c¬',
+    'page_not_found' => 'æ\89¾ä¸\8d到頁面',
+    'chapter_not_found' => 'æ\89¾ä¸\8d到章節',
+    'selected_book_not_found' => '找不到選定的書本',
+    'selected_book_chapter_not_found' => 'æ\89¾ä¸\8då\88°é\81¸å®\9aç\9a\84æ\9b¸æ\9c¬或章節',
+    'guests_cannot_save_drafts' => '訪客無法儲存草稿',
 
     // Users
     'users_cannot_delete_only_admin' => '您不能刪除唯一的管理員帳號',
@@ -69,32 +69,35 @@ return [
     // Roles
     'role_cannot_be_edited' => '無法編輯這個角色',
     'role_system_cannot_be_deleted' => '無法刪除系統角色',
-    'role_registration_default_cannot_delete' => '無法刪除設定預設註冊的角色',
-    'role_cannot_remove_only_admin' => '該用戶是分配作為管理員職務的唯一用戶。 在嘗試在此處刪除管理員職務之前,請將其分配給其他用戶。',
+    'role_registration_default_cannot_delete' => '無法刪除設定預設註冊的角色',
+    'role_cannot_remove_only_admin' => '此使用者是唯一被指派為管理員角色的使用者。在試圖移除這裡前,請將管理員角色指派給其他使用者。',
 
     // Comments
-    'comment_list' => '取評論時發生錯誤。',
-    'cannot_add_comment_to_draft' => '您不能為草稿加入評論。',
-    'comment_add' => '加入/更新評論時發生錯誤。',
+    'comment_list' => '取評論時發生錯誤。',
+    'cannot_add_comment_to_draft' => '您無法新增評論到草稿中。',
+    'comment_add' => '新增/更新評論時發生錯誤。',
     'comment_delete' => '刪除評論時發生錯誤。',
-    'empty_comment' => '不能加入空的評論。',
+    'empty_comment' => '無法新增空評論。',
 
     // Error pages
-    '404_page_not_found' => '無法找到頁面',
-    'sorry_page_not_found' => '對不起,無法找到您想進入的頁面。',
+    '404_page_not_found' => '找不到頁面',
+    'sorry_page_not_found' => '抱歉,找不到您在尋找的頁面。',
     'sorry_page_not_found_permission_warning' => '如果您確認這個頁面存在,則代表可能沒有查看它的權限。',
-    'return_home' => '返回首頁',
+    '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' => '回到首頁',
     'error_occurred' => '發生錯誤',
-    'app_down' => ':appName現在正在關閉',
-    'back_soon' => '請耐心等待網站的恢複。',
+    'app_down' => ':appName 離線中',
+    'back_soon' => '它應該很快就會重新上線。',
 
     // API errors
-    'api_no_authorization_found' => '在請求上找不到授權令牌',
-    'api_bad_authorization_format' => '在請求中找到授權令牌,但格式似乎不正確',
-    'api_user_token_not_found' => '找不到提供的授權令牌的匹配API令牌',
-    'api_incorrect_token_secret' => '給定使用的API令牌提供的密鑰不正確',
-    'api_user_no_api_permission' => '使用的API令牌的擁有者者無權進行API調用',
-    'api_user_token_expired' => '授權令牌已過期',
+    'api_no_authorization_found' => '在請求上找不到授權權杖',
+    'api_bad_authorization_format' => '在請求中找到授權權杖,但格式似乎不正確',
+    'api_user_token_not_found' => '找不到與提供的授權權杖相符的 API 權杖',
+    'api_incorrect_token_secret' => '給定使用的 API 權杖的密碼錯誤',
+    'api_user_no_api_permission' => '使用的 API 權杖擁有者無權呼叫 API',
+    'api_user_token_expired' => '使用的授權權杖已過期',
 
     // Settings & Maintenance
     'maintenance_test_email_failure' => '寄送測試電子郵件時發生錯誤:',
index 7040cd89095879af50403ac2be3c886a3074f364..f4d8dc30cab98886c36b54f824948e5b1f6d45a6 100644 (file)
@@ -6,10 +6,10 @@
  */
 return [
 
-    'password' => 'å¯\86碼å¿\85é \88è\87³å°\91å\8c\85å\90«å\85­å\80\8bå­\97å\85\83並è\88\87確èª\8d相符。',
+    'password' => 'å¯\86碼å¿\85é \88è\87³å°\91å\85«å\80\8bå­\97å\85\83ï¼\8c並è\88\87確èª\8då¯\86碼相符。',
     'user' => "沒有使用這個電子郵件位址的使用者。",
-    'token' => '這個電子郵件位址的密碼重置權仗無效。',
-    'sent' => '我們已經透過電子郵件發送您的密碼重連結。',
-    'reset' => '您的密碼已被重置。',
+    'token' => '這個電子郵件地址的密碼重設權杖無效。',
+    'sent' => '我們已經透過電子郵件發送您的密碼重連結。',
+    'reset' => '您的密碼已被重設!',
 
 ];
index 50ef79cbb55b25647d51563e8e46cea0d62b85b0..278049052290778ffdd3fe90126d6d4592e173a2 100644 (file)
@@ -12,38 +12,43 @@ return [
     'settings_save_success' => '設定已儲存',
 
     // App Settings
-    'app_customization' => '自定義',
+    'app_customization' => '自',
     'app_features_security' => '功能與安全',
-    'app_name' => 'App名',
-    'app_name_desc' => '此名稱將在網頁頂端和Email中顯示。',
-    'app_name_header' => '在網頁頂端顯示應用名稱?',
-    'app_public_access' => '公共訪問',
-    'app_public_access_desc' => '啟用此選項將允許未登錄的訪問者訪問BookStack實例中的內容。',
-    'app_public_access_desc_guest' => 'å\8f¯ä»¥é\80\9aé\81\8eâ\80\9c訪客â\80\9dæ\8e§å\88¶å\85¬å\85±è¨ªå\95\8fè\80\85ç\9a\84訪å\95\8f。',
-    'app_public_access_toggle' => '允許公開訪問',
-    'app_public_viewing' => '開放公開閱覽?',
-    'app_secure_images' => '啟用更高安全性的圖片上傳?',
+    'app_name' => '應用程式名稱',
+    'app_name_desc' => '此名稱會在網頁頂端與任何系統傳送的電子郵件中出現。',
+    'app_name_header' => '在網頁頂端顯示名稱',
+    'app_public_access' => '公開存取',
+    'app_public_access_desc' => '啟用此選項將會允許未登入的訪客存取您 BookStack 站台中的內容。',
+    'app_public_access_desc_guest' => 'å\8f¯ä»¥é\80\8fé\81\8eã\80\8c訪客ã\80\8d使ç\94¨è\80\85æ\8e§å\88¶å\85¬é\96\8b訪客ç\9a\84å­\98å\8f\96。',
+    'app_public_access_toggle' => '允許公開存取',
+    'app_public_viewing' => '允許公開檢視?',
+    'app_secure_images' => '更高安全性的圖片上傳',
     'app_secure_images_toggle' => '啟用更高安全性的圖片上傳',
-    'app_secure_images_desc' => 'å\87ºæ\96¼æ\95\88è\83½è\80\83é\87\8fï¼\8cæ\89\80æ\9c\89å\9c\96ç\89\87é\83½æ\98¯å\85¬é\96\8bç\9a\84ã\80\82é\80\99å\80\8bé\81¸é \85æ\9c\83å\9c¨å\9c\96ç\89\87ç\9a\84ç¶²å\9d\80å\89\8då\8a å\85¥ä¸\80å\80\8bé\9a¨æ©\9f並é\9b£ä»¥ç\8c\9c測ç\9a\84å­\97å\85\83串ï¼\8cå¾\9eè\80\8c使ç\9b´æ\8e¥é\80²å\85¥è®\8aå¾\97困難。',
+    'app_secure_images_desc' => 'å\9b ç\82ºæ\95\88è\83½å\9b ç´ ï¼\8cæ\89\80æ\9c\89å\9c\96ç\89\87é\83½æ\98¯å\85¬é\96\8bç\9a\84ã\80\82æ­¤é\81¸é \85æ\9c\83å\9c¨å\9c\96ç\89\87ç\9a\84ç¶²å\9d\80å\89\8då\8a å\85¥ä¸\80串é\9a¨æ©\9fä¸\94é\9b£ä»¥ç\8c\9c測ç\9a\84å­\97串ã\80\82確ä¿\9dæ\9cªå\95\9fç\94¨ç\9b®é\8c\84ç´¢å¼\95ï¼\8cè®\93ç\9b´æ\8e¥é\80²å\85¥è®\8aå¾\97æ\9b´困難。',
     'app_editor' => '頁面編輯器',
-    'app_editor_desc' => '選擇所有使用者將使用哪個編輯器來編輯頁面。',
-    'app_custom_html' => '自訂HTML頂端內容',
-    'app_custom_html_desc' => '此處加入的任何內容都將插入到每個頁面的<head>部分的底部,這對於覆蓋樣式或加入分析程式碼很方便。',
-    'app_custom_html_disabled_notice' => '在此設置頁面上禁用了自定義HTML標題內容,以確保可以恢復所有重大更改。',
-    'app_logo' => 'App Logo',
-    'app_logo_desc' => '這個圖片的高度應該為43px。<br>大圖片將會被縮小。',
-    'app_primary_color' => 'App主要配色',
-    'app_primary_color_desc' => '請使用十六進位數值。<br>保留空白則重置回預設配色。',
-    'app_homepage' => 'App首頁',
-    'app_homepage_desc' => '選擇要做為首頁的頁面,這將會替換預設首頁,而且這個頁面的權限設定將被忽略。',
-    'app_homepage_select' => '預設首頁選擇',
-    'app_disable_comments' => '關閉評論',
-    'app_disable_comments_toggle' => '禁用評論',
-    'app_disable_comments_desc' => '在App的所有頁面上關閉評論,已經存在的評論也不會顯示。',
+    'app_editor_desc' => '選取所有使用者將使用哪個編輯器來編輯頁面。',
+    'app_custom_html' => '自訂 HTML 標題內容',
+    'app_custom_html_desc' => '此處加入的任何內容都將插入到每個頁面的 <head> 部分的底部,這對於覆蓋樣式或加入分析程式碼很方便。',
+    'app_custom_html_disabled_notice' => '在此設定頁面上停用了自訂 HTML 標題內容,以確保任何重大變更都能被還原。',
+    'app_logo' => '應用程式圖示',
+    'app_logo_desc' => '此圖片的高度應為 43px。<br>較大的圖片將會被縮小。',
+    'app_primary_color' => '應用程式主要色彩',
+    'app_primary_color_desc' => '設定應用程式的主要色彩,包含了橫幅、按鈕與連結。',
+    'app_homepage' => '應用程式首頁',
+    'app_homepage_desc' => '選取要作為首頁的頁面,這將會取代預設首頁。選定頁面的頁面權限將會被忽略。',
+    'app_homepage_select' => '選取頁面',
+    'app_footer_links' => '頁面註腳連結',
+    'app_footer_links_desc' => '新增連結以在網站註腳顯示。這些將會顯示在大多數頁面的底部,包含那些不需要登入的頁面。您可以使用 "trans::<key>" 標籤來使用系統定義的翻譯。舉例來說:使用 "trans::common.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' => '在應用程式的所有頁面停用評論。<br>既有的評論將不會顯示。',
 
     // Color settings
     'content_colors' => '內容顏色',
-    'content_colors_desc' => '為頁面組織層次結構中的所有元素設置顏色。 為了提高可讀性,建議選擇亮度與默認顏色相似的顏色。',
+    'content_colors_desc' => '為頁面層次結構中的所有元素設定顏色。 為了提高可讀性,建議選擇亮度與預設顏色相似的顏色。',
     'bookshelf_color' => '書架顔色',
     'book_color' => '書本顔色',
     'chapter_color' => '章節顔色',
@@ -51,173 +56,173 @@ return [
     'page_draft_color' => '頁面草稿顏色',
 
     // Registration Settings
-    'reg_settings' => '註冊設定',
+    'reg_settings' => '註冊',
     'reg_enable' => '啟用註冊',
     'reg_enable_toggle' => '啟用註冊',
-    'reg_enable_desc' => '啟用註冊後,用戶將可以自己註冊為應用程序用戶。註冊後,他們將獲得一個默認的單一用戶角色。',
+    'reg_enable_desc' => '啟用註冊後,使用者將可以自行註冊為應用程式的使用者。註冊後,他們將會得到一個預設的使用者角色。',
     'reg_default_role' => '註冊後的預設使用者角色',
-    'reg_enable_external_warning' => '當外部 LDAP 或 SAML 身份驗證啟用時,將會忽略上述選項。如果外部身份驗證成功,將會自動在本系統建立使用者帳。',
+    'reg_enable_external_warning' => '當外部 LDAP 或 SAML 身份驗證啟用時,將會忽略上述選項。如果外部身份驗證成功,將會自動在本系統建立使用者帳。',
     'reg_email_confirmation' => '電子郵件驗證',
     'reg_email_confirmation_toggle' => '需要電子郵件驗證',
-    'reg_confirm_email_desc' => '如果使用網域名稱限制,則需要電子郵件驗證,並且本設定將被忽略。',
-    'reg_confirm_restrict_domain' => '網域名稱限制',
-    'reg_confirm_restrict_domain_desc' => '輸入您想要限制註冊的Email域域名稱列表,用逗號隔開。在被允許與本系統連結之前,使用者會收到一封Email來確認他們的位址。<br>注意,使用者在註冊成功後可以修改他們的Email位址。',
-    'reg_confirm_restrict_domain_placeholder' => '尚未設定限制的網域',
+    'reg_confirm_email_desc' => '如果使用網域限制,則需要電子郵件驗證,且此選項將被忽略。',
+    'reg_confirm_restrict_domain' => '網域限制',
+    'reg_confirm_restrict_domain_desc' => '輸入您想要限制註冊的電子郵件網域列表,以英文逗號分隔。在可以與應用程式互動前,使用者將會收到電子郵件以確認他們的電子郵件地址。<br>注意,使用者可以在註冊成功後變更他們的電子郵件地址。',
+    'reg_confirm_restrict_domain_placeholder' => '尚未設定限制',
 
     // Maintenance settings
     'maint' => '維護',
-    'maint_image_cleanup' => '清理圖',
-    'maint_image_cleanup_desc' => "掃描頁面和修訂內容以檢查哪些圖像是正在使用的以及哪些圖像是多余的。確保在運行前創建完整的數據庫和映像備份。",
-    'maint_delete_images_only_in_revisions' => '包含刪除僅在舊頁面修訂版中存在的圖像',
-    'maint_image_cleanup_run' => '行清理',
-    'maint_image_cleanup_warning' => '發現了:count 張可能未使用的圖像。您確定要刪除這些圖像嗎?',
-    'maint_image_cleanup_success' => '找到並刪除了:count 張可能未使用的圖像!',
-    'maint_image_cleanup_nothing_found' => '找不到未使用的圖,未刪除任何檔案!',
-    'maint_send_test_email' => '送測試電子郵件',
-    'maint_send_test_email_desc' => '這會將測試電子郵件送到您的個人資料中指定的電子郵件地址。',
-    'maint_send_test_email_run' => '送測試郵件',
-    'maint_send_test_email_success' => 'é\83µä»¶ç\99¼送到 :address',
+    'maint_image_cleanup' => '清理圖',
+    'maint_image_cleanup_desc' => "掃描頁面與修訂版本內容來檢查目前使用了哪些圖片,而哪些圖片又是多餘的。請確保您在執行這個動作前建立了完整的資料庫與映像檔備份。",
+    'maint_delete_images_only_in_revisions' => '也刪除僅存在於舊的頁面修訂版本中存在的圖片',
+    'maint_image_cleanup_run' => '行清理',
+    'maint_image_cleanup_warning' => '發現了 :count 張可能未使用的圖片。您確定要刪除這些圖片嗎?',
+    'maint_image_cleanup_success' => '找到並刪除了 :count 張可能未使用的圖片!',
+    'maint_image_cleanup_nothing_found' => '找不到未使用的圖,未刪除任何檔案!',
+    'maint_send_test_email' => '送測試電子郵件',
+    'maint_send_test_email_desc' => '這會將測試電子郵件送到您的個人資料中指定的電子郵件地址。',
+    'maint_send_test_email_run' => '送測試郵件',
+    'maint_send_test_email_success' => 'é\9b»å­\90é\83µä»¶å·²å\82³送到 :address',
     'maint_send_test_email_mail_subject' => '測試郵件',
     'maint_send_test_email_mail_greeting' => '電子郵件傳遞似乎有效!',
-    'maint_send_test_email_mail_text' => '恭喜你! 收到此電子郵件通知時,您的電子郵件設置已經認證成功。',
-    'maint_recycle_bin_desc' => '刪除的書架,書籍,章節和頁面將發送到回收站,以便可以還原或永久刪除它們。 回收站中的較舊項目可能會在一段時間後自動刪除,具體取決於系統配置。',
-    'maint_recycle_bin_open' => 'Open Recycle Bin',
+    'maint_send_test_email_mail_text' => '恭喜!您收到這封電子郵件通知時,代表您的電子郵件設定已正確設定。',
+    'maint_recycle_bin_desc' => '刪除的書架、書本、章節與頁面將會被傳送到回收桶,這樣仍可以還原或永久刪除。回收桶中較舊的項目可能會在一段時間後自動移除,取決於您的系統設定。',
+    'maint_recycle_bin_open' => '開啟回收桶',
 
     // 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_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_destroy_notification' => 'Deleted :count total items from the recycle bin.',
-    'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.',
+    'recycle_bin' => '資源回收',
+    'recycle_bin_desc' => '在這裡,您可以還原已刪除的項目,或是選擇將其從系統中永久移除。與系統中套用了權限過濾條件類似的活動列表不同的是,此列表並未過濾。',
+    'recycle_bin_deleted_item' => '已刪除項目',
+    'recycle_bin_deleted_by' => '刪除由',
+    'recycle_bin_deleted_at' => '刪除時間',
+    'recycle_bin_permanently_delete' => '永久刪除',
+    'recycle_bin_restore' => '還原',
+    'recycle_bin_contents_empty' => '回收桶目前是空的',
+    'recycle_bin_empty' => '清空回收桶',
+    'recycle_bin_empty_confirm' => '這將會永久破壞回收桶中的所有項目,包括每個項目中包含的內容。您確定您想要清空回收桶嗎?',
+    'recycle_bin_destroy_confirm' => '此動作將會從系統中永久移除此項目以及下方列出的所有下層元素,您將無法還原此內容。您確定您想要永久刪除此項目嗎?',
+    'recycle_bin_destroy_list' => '要被銷毀的項目',
+    'recycle_bin_restore_list' => '要被還原的項目',
+    'recycle_bin_restore_confirm' => '此動作將會還原已被刪除的項目(包含任何下層元素)到其原始位置。如果原始位置已被刪除,且目前位於垃圾桶裡,那麼上層項目也需要被還原。',
+    'recycle_bin_restore_deleted_parent' => '此項目的上層項目也已被刪除。因此將會保持被刪除的狀態,直到上層項目也被還原。',
+    'recycle_bin_destroy_notification' => '已從回收桶刪除共 :count 個項目。',
+    'recycle_bin_restore_notification' => '已從回收桶還原共 :count 個項目。',
 
     // Audit Log
     'audit' => '稽核記錄',
-    '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_desc' => '此稽核日誌會顯示被系統追蹤的活動列表。與系統中套用了權限過濾條件類似的活動列表不同的是,此列表並未過濾。',
+    'audit_event_filter' => '活動過濾條件',
+    'audit_event_filter_no_filter' => '無過濾條件',
+    'audit_deleted_item' => '已刪除的項目',
+    'audit_deleted_item_name' => '名稱::name',
     'audit_table_user' => '使用者',
     'audit_table_event' => '活動',
-    'audit_table_related' => 'Related Item or Detail',
-    'audit_table_date' => 'æ\9c\80å¾\8cæ´»å\8b\95æ\97¥æ\9c\9f',
-    'audit_date_from' => 'Date Range From',
-    'audit_date_to' => 'Date Range To',
+    'audit_table_related' => '相關的項目或詳細資訊',
+    'audit_table_date' => '活動日期',
+    'audit_date_from' => '日期範圍,從',
+    'audit_date_to' => '日期範圍,到',
 
     // Role Settings
     'roles' => '角色',
     'role_user_roles' => '使用者角色',
-    'role_create' => '建立角色',
+    'role_create' => '建立角色',
     'role_create_success' => '角色建立成功',
     'role_delete' => '刪除角色',
-    'role_delete_confirm' => '這將會刪除名為 \':roleName\' 的角色.',
-    'role_delete_users_assigned' => '有:userCount位使用者屬於此角色。如果您想將此角色中的使用者遷移,請在下面選擇一個新角色。',
+    'role_delete_confirm' => '這將會刪除名為「:roleName」的角色.',
+    'role_delete_users_assigned' => '有 :userCount 位使用者屬於此角色。如果您想將此角色中的使用者遷移,請在下面選擇一個新角色。',
     'role_delete_no_migration' => "不要遷移使用者",
-    'role_delete_sure' => '您確定要刪除這個角色?',
+    'role_delete_sure' => '您確定要刪除角色?',
     'role_delete_success' => '角色刪除成功',
     'role_edit' => '編輯角色',
     'role_details' => '角色詳細資訊',
-    'role_name' => '角色名',
-    'role_desc' => '角色簡',
-    'role_external_auth_id' => '外部身份驗證ID',
+    'role_name' => '角色名',
+    'role_desc' => '角色簡短說明',
+    'role_external_auth_id' => '外部身份驗證 ID',
     'role_system' => '系統權限',
     'role_manage_users' => '管理使用者',
     'role_manage_roles' => '管理角色與角色權限',
-    'role_manage_entity_permissions' => '管理所有圖書,章節和頁面的權限',
-    'role_manage_own_entity_permissions' => '管理自己的圖書,章節和頁面的權限',
-    'role_manage_page_templates' => '管理頁面模板',
-    'role_access_api' => '存取系統API',
-    'role_manage_settings' => '管理App設定',
-    'role_asset' => '資源項目',
-    'roles_system_warning' => '請注意,具有上述三項權限任何一項的用戶能更改自己或系統中其他人的權限。應只將具這些權限的角色分配予受信任的用戶。',
-    'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆這裡的權限設定。',
-    'role_asset_admins' => '管理員會自動獲得對所有內容的存取權限,但這些選項可能會顯示或隱藏UI的選項。',
+    'role_manage_entity_permissions' => '管理所有書本、章節與頁面的權限',
+    'role_manage_own_entity_permissions' => '管理自己的書本、章節與頁面的權限',
+    'role_manage_page_templates' => '管理頁面範本',
+    'role_access_api' => '存取系統 API',
+    'role_manage_settings' => '管理應用程式設定',
+    'role_asset' => '資源權限',
+    'roles_system_warning' => '請注意,有上述三項權限中的任一項的使用者都可以更改自己或系統中其他人的權限。有這些權限的角色只應分配給受信任的使用者。',
+    'role_asset_desc' => '對系統內資源的預設權限將由這裡的權限控制。若有單獨設定在書本、章節和頁面上的權限,將會覆這裡的權限設定。',
+    'role_asset_admins' => '管理員會自動取得對所有內容的存取權,但這些選項可能會顯示或隱藏使用者介面的選項。',
     'role_all' => '全部',
     'role_own' => '擁有',
     'role_controlled_by_asset' => '依據隸屬的資源來決定',
     'role_save' => '儲存角色',
     'role_update_success' => '角色更新成功',
-    'role_users' => '此角色的使用者',
-    'role_users_none' => '目前沒有使用者被分配到這個角色',
+    'role_users' => '屬於此角色的使用者',
+    'role_users_none' => '目前沒有使用者被分配到角色',
 
     // Users
     'users' => '使用者',
-    'user_profile' => '使用者資料',
-    'users_add_new' => '加入使用者',
+    'user_profile' => '使用者個人資料',
+    'users_add_new' => '新增使用者',
     'users_search' => '搜尋使用者',
     'users_latest_activity' => '最新活動',
-    'users_details' => '用戶詳情',
-    'users_details_desc' => '請設置用戶的顯示名稱和電子郵件地址, 該電子郵件地址將用於登錄該應用。',
-    'users_details_desc_no_email' => '設置一個用戶的顯示名稱,以便其他人可以認出你。',
+    'users_details' => '使用者詳細資訊',
+    'users_details_desc' => '為此使用者設定顯示名稱與電子郵件地址。電子郵件地址將用於登入應用程式。',
+    'users_details_desc_no_email' => '為此使用者設定顯示名稱,這樣其他人才能認出該使用者。',
     'users_role' => '使用者角色',
-    'users_role_desc' => '選擇一個將分配給該用戶的角色。 如果將一個用戶分配給多個角色,則這些角色的權限將堆疊在一起,並且他們將獲得分配的角色的所有功能。',
-    'users_password' => '用戶密碼',
-    'users_password_desc' => '設置用於登錄賬戶的密碼, 密碼長度必須至少為6個字符。',
-    'users_send_invite_text' => '您可以選擇向該用戶發送邀請電子郵件,允許他們設置自己的密碼,或者您可以自己設置他們的密碼。',
-    'users_send_invite_option' => 'å\90\91ç\94¨æ\88¶ç\99¼é\80\81é\82\80è«\8bé\9b»å­\90é\83µä»¶',
-    'users_external_auth_id' => '外部身份驗證ID',
-    'users_external_auth_id_desc' => '這個 ID 將於外部身份驗證系統時,用於比對使用者。',
+    'users_role_desc' => '選取要分配的此使用者的角色。若使用者被分配到多個角色,則這些角色的權限將會堆疊,使用者將會取得被分配角色的所有功能。',
+    'users_password' => '使用者密碼',
+    'users_password_desc' => '設定用於登入應用程式的密碼。密碼必須至少 6 個字元長。',
+    'users_send_invite_text' => '您可以選擇向此使用者傳送邀請電子郵件,讓他們可以設定自己的密碼,您也可以自行設定他們的密碼。',
+    'users_send_invite_option' => 'å\82³é\80\81é\82\80è«\8bé\9b»å­\90é\83µä»¶çµ¦ä½¿ç\94¨è\80\85',
+    'users_external_auth_id' => '外部身份驗證 ID',
+    'users_external_auth_id_desc' => '與外部身份驗證系統通訊時,此 ID 將用於比對使用者。',
     'users_password_warning' => '如果您想更改密碼,請填寫以下內容:',
-    'users_system_public' => '此使ç\94¨è\80\85代表é\80²å\85¥æ\82¨ç\9a\84Appç\9a\84ä»»ä½\95訪客ã\80\82å®\83ä¸\8dè\83½ç\94¨æ\96¼ç\99»å\85¥ï¼\8cè\80\8cæ\98¯自動分配。',
+    'users_system_public' => '此使ç\94¨è\80\85代表é\80 è¨ªæ\82¨ç«\99å\8f°ç\9a\84ä»»ä½\95訪客使ç\94¨è\80\85ã\80\82å\85¶ä¸\8dè\83½ç\94¨æ\96¼ç\99»å\85¥ï¼\8cè\80\8cæ\9c\83自動分配。',
     'users_delete' => '刪除使用者',
     'users_delete_named' => '刪除使用者 :userName',
-    'users_delete_warning' => '這將從系統中完全刪除名為 \':userName\' 的使用者。',
-    'users_delete_confirm' => '您確定要刪除這個使用者?',
-    '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' => 'æ²\92æ\9c\89é\81¸å®\9aç\9a\84使用者',
-    'users_delete_success' => 'User successfully removed',
+    'users_delete_warning' => '這將從系統中完全刪除名為「:userName」的使用者。',
+    'users_delete_confirm' => '您確定要刪除使用者?',
+    'users_migrate_ownership' => '轉移所有權',
+    'users_migrate_ownership_desc' => '如果您希望其他使用者變成目前此使用者擁有的所有項目的新擁有者,請在此處選取新的使用者。',
+    'users_none_selected' => 'æ\9cªé\81¸å\8f\96使用者',
+    'users_delete_success' => '使用者移除成功',
     'users_edit' => '編輯使用者',
-    'users_edit_profile' => '編輯資料',
+    'users_edit_profile' => '編輯個人資料',
     'users_edit_success' => '使用者更新成功',
     'users_avatar' => '使用者大頭照',
-    'users_avatar_desc' => '目前圖片應該為約256px的正方形。',
-    'users_preferred_language' => '語言',
-    'users_preferred_language_desc' => 'æ­¤é\81¸é \85å°\87æ\9b´æ\94¹ç\94¨æ\96¼æ\87\89ç\94¨ç\94¨æ\88¶ç\95\8cé\9d¢ç\9a\84èª\9eè¨\80ï¼\8cä½\86 é\80\99ä¸\8dæ\9c\83å½±é\9f¿ä»»ä½\95ç\94¨æ\88¶å\89µå»º的內容。',
+    'users_avatar_desc' => '選取一張代表此使用者的圖片。這應該是大約 256px 的正方形。',
+    'users_preferred_language' => '偏好語言',
+    'users_preferred_language_desc' => 'æ­¤é\81¸é \85å°\87æ\9c\83è®\8aæ\9b´ç\94¨æ\96¼æ\87\89ç\94¨ç¨\8bå¼\8f使ç\94¨è\80\85ä»\8bé\9d¢ç\9a\84èª\9eè¨\80ã\80\82ä¸\8dæ\9c\83å½±é\9f¿ä»»ä½\95使ç\94¨è\80\85建ç«\8b的內容。',
     'users_social_accounts' => '社群網站帳號',
-    'users_social_accounts_info' => '在這里,您可以連結您的其他帳號,以便方便地登入。如果您選擇解除連結,之後將不能透過此社群網站帳號登入,請設定社群網站帳號來取消本系統p的進入權限。',
+    'users_social_accounts_info' => '您可以在此處連結您其他的帳號以供快速登入。從此處取消連結帳號並不會撤銷先前已授權的存取。請從您連結的社群網站帳號的個人設定中撤銷存取權。',
     'users_social_connect' => '連結帳號',
-    'users_social_disconnect' => '解除連結帳號',
-    'users_social_connected' => ':socialAccount 帳號已經成功連結到您的資料。',
-    'users_social_disconnected' => ':socialAccount 帳號已經成功解除連結。',
-    'users_api_tokens' => 'API令牌',
-    'users_api_tokens_none' => '尚未為此用戶創建API令牌',
-    'users_api_tokens_create' => 'å\89µå»ºä»¤ç\89\8c',
+    'users_social_disconnect' => '取消連結帳號',
+    'users_social_connected' => ':socialAccount 帳號已經成功連結到您的個人資料。',
+    'users_social_disconnected' => ':socialAccount 帳號已經成功取消連結。',
+    'users_api_tokens' => 'API 權杖',
+    'users_api_tokens_none' => '尚未為此使用者建立 API 權杖',
+    'users_api_tokens_create' => '建ç«\8bæ¬\8aæ\9d\96',
     'users_api_tokens_expires' => '過期',
-    'users_api_tokens_docs' => 'API檔案',
+    'users_api_tokens_docs' => 'API 文件',
 
     // API Tokens
-    'user_api_token_create' => 'å\89µå»º API ä»¤ç\89\8c',
+    'user_api_token_create' => '建ç«\8b API æ¬\8aæ\9d\96',
     'user_api_token_name' => '名稱',
-    'user_api_token_name_desc' => '給您的令牌一個易讀的名稱,以備將來提醒其預期的用途。',
-    'user_api_token_expiry' => '過期日期',
-    'user_api_token_expiry_desc' => '設置此令牌到期的日期, 在此日期之後,使用此令牌發出的請求將不再起作用。 若該項留空則自動將在100年後到期。',
-    'user_api_token_create_secret_message' => 'å\89µå»ºæ­¤ä»¤ç\89\8cå¾\8cï¼\8cå°\87ç«\8bå\8d³ç\94\9fæ\88\90並顯示â\80\9c令ç\89\8cIDâ\80\9då\92\8câ\80\9c令ç\89\8cå¯\86é\91°â\80\9dï¼\8c該å¯\86é\91°å\8fªæ\9c\83顯示ä¸\80次ï¼\8cå\9b æ­¤è«\8b確ä¿\9då\9c¨ç¹¼çº\8cæ\93\8dä½\9cä¹\8b前將其複製到安全的地方。',
-    'user_api_token_create_success' => 'æ\88\90å\8a\9få\89µå»ºAPI令ç\89\8c',
-    'user_api_token_update_success' => '成功更新API令牌',
-    'user_api_token' => 'API 令牌',
-    'user_api_token_id' => '令牌 ID',
-    'user_api_token_id_desc' => '這是此令牌的不可編輯的系統生成的標識符,需要在API請求中提供。',
-    'user_api_token_secret' => '令牌密鑰',
-    'user_api_token_secret_desc' => '這是此令牌的系統生成的密鑰,需要在API請求中提供。 這只會顯示一次,因此請將其複製到安全的地方。',
-    'user_api_token_created' => '令牌已創建於 :timeAgo',
-    'user_api_token_updated' => '令牌已更新:timeAgo',
-    'user_api_token_delete' => '刪除令牌',
-    'user_api_token_delete_warning' => '這將從系統中完全刪除名稱為 ":tokenName" 的API令牌。',
-    'user_api_token_delete_confirm' => '您確定要刪除這個API令牌嗎?',
-    'user_api_token_delete_success' => 'API令牌成功刪除',
+    'user_api_token_name_desc' => '給您的權杖易於辨識的名稱,如此未來才能提醒其預期用途。',
+    'user_api_token_expiry' => '到期日',
+    'user_api_token_expiry_desc' => '設定此權杖的到期日。在此日期後,使用此權杖發出的請求將不再起作用。若將此欄留空,將會設定在100年後過期。',
+    'user_api_token_create_secret_message' => '建ç«\8bæ­¤æ¬\8aæ\9d\96å¾\8cï¼\8cå°\87æ\9c\83ç«\8bå\8d³ç\94\9fæ\88\90並顯示ã\80\8cæ¬\8aæ\9d\96 IDã\80\8dè\88\87ã\80\8cæ¬\8aæ\9d\96å¯\86碼ã\80\8dã\80\82該å¯\86碼å°\87å\8fªæ\9c\83顯示ä¸\80次ï¼\8cå\9b æ­¤è«\8bå\9c¨ç¹¼çº\8cæ\93\8dä½\9c前將其複製到安全的地方。',
+    'user_api_token_create_success' => 'æ\88\90å\8a\9f建ç«\8b API æ¬\8aæ\9d\96',
+    'user_api_token_update_success' => '成功更新 API 權杖',
+    'user_api_token' => 'API 權杖',
+    'user_api_token_id' => '權杖 ID',
+    'user_api_token_id_desc' => '這是此權杖由系統生成的不可編輯識別字串,必須在 API 請求中提供。',
+    'user_api_token_secret' => '權杖密碼',
+    'user_api_token_secret_desc' => '這是此權杖由系統生成的密碼,必須在 API 請求中提供。該密碼將只會顯示一次,因此請在繼續操作前將其複製到安全的地方。',
+    'user_api_token_created' => '權杖建立於:timeAgo',
+    'user_api_token_updated' => '權杖更新於:timeAgo',
+    'user_api_token_delete' => '刪除權杖',
+    'user_api_token_delete_warning' => '這將會從系統中完全刪除名為「:tokenName」的 API 權杖。',
+    'user_api_token_delete_confirm' => '您確定要刪除此 API 權杖嗎?',
+    'user_api_token_delete_success' => 'API 權杖已成功刪除',
 
     //! If editing translations files directly please ignore this in all
     //! languages apart from en. Content will be auto-copied from en.
@@ -226,6 +231,8 @@ return [
         'en' => 'English',
         'ar' => 'العربية',
         'bg' => 'Bǎlgarski',
+        'bs' => 'Bosanski',
+        'ca' => '加泰隆尼亞語',
         'cs' => 'Česky',
         'da' => '丹麥',
         'de' => 'Deutsch (Sie)',
@@ -235,12 +242,15 @@ return [
         'fr' => 'Français',
         'he' => '希伯來語',
         'hu' => 'Magyar',
+        'id' => 'Bahasa Indonesia',
         'it' => 'Italian',
         'ja' => '日本語',
         'ko' => '한국어',
+        'lv' => 'Latviešu Valoda',
         'nl' => 'Nederlands',
         'nb' => 'Norsk (Bokmål)',
         'pl' => 'Polski',
+        'pt' => 'Português',
         'pt_BR' => 'Português do Brasil',
         'ru' => 'Русский',
         'sk' => 'Slovensky',
index 7a46288ad2664c3e9cd848817d1a169215b5ac0d..691ebb619652058994c356803665d5ba4370fcf2 100644 (file)
@@ -8,67 +8,67 @@
 return [
 
     // Standard laravel validation lines
-    'accepted'             => ':attribute 需要被同意。',
-    'active_url'           => ':attribute 並不是一個有效的網址',
+    'accepted'             => '必須同意 :attribute。',
+    'active_url'           => ':attribute 並非有效的網址。',
     'after'                => ':attribute 必須是在 :date 後的日期。',
     'alpha'                => ':attribute 只能包含字母。',
-    'alpha_dash'           => ':attribute 只能包含字母、數字和橫線。',
+    'alpha_dash'           => ':attribute 只能包含字母、數字、破折號與底線。',
     'alpha_num'            => ':attribute 只能包含字母和數字。',
     'array'                => ':attribute 必須是陣列。',
     'before'               => ':attribute 必須是在 :date 前的日期。',
     'between'              => [
-        'numeric' => ':attribute 必須在:min到:max之間。',
-        'file'    => ':attribute 必須為:min到:max KB。',
-        'string'  => ':attribute 必須在:min到:max個字元之間。',
-        'array'   => ':attribute 必須在:min到:max項之間.',
+        'numeric' => ':attribute 必須在 :min 到 :max 之間。',
+        'file'    => ':attribute 必須為 :min 到 :max KB。',
+        'string'  => ':attribute 必須在 :min 到 :max 個字元之間。',
+        'array'   => ':attribute 必須在 :min 到 :max 項之間。',
     ],
-    'boolean'              => ':attribute 字段必須為 true 或 false。',
+    'boolean'              => ':attribute 欄位必須為 true 或 false。',
     'confirmed'            => ':attribute 確認不符。',
-    'date'                 => ':attribute ä¸\8dæ\98¯ä¸\80å\80\8b有效的日期。',
-    'date_format'          => ':attribute 格式不符 :format。',
+    'date'                 => ':attribute ä¸¦é\9d\9e有效的日期。',
+    'date_format'          => ':attribute 與 :format 格式不相符。',
     'different'            => ':attribute 和 :other 必須不同。',
-    'digits'               => ':attribute 必須為:digits位數。',
-    'digits_between'       => ':attribute 必須為:min到:max位數。',
-    'email'                => ':attribute 必須是有效的電子郵件址。',
+    'digits'               => ':attribute 必須為 :digits 位數。',
+    'digits_between'       => ':attribute 必須為 :min 到 :max 位數。',
+    'email'                => ':attribute 必須是有效的電子郵件址。',
     'ends_with' => ':attribute必須以下列之一結尾::values',
-    'filled'               => ':attribute 字段是必需的。',
+    'filled'               => ':attribute 欄位必填。',
     'gt'                   => [
-        'numeric' => ':attribute必須大於:value。',
-        'file'    => ':attribute必須大於:value千字節。',
-        'string'  => ':attribute必須多於:value個字符。',
-        'array'   => ':attribute必須包含比:value多的項目。',
+        'numeric' => ':attribute 必須大於 :value。',
+        'file'    => ':attribute 必須大於 :value KB。',
+        'string'  => ':attribute 必須多於 :value 個字元。',
+        'array'   => ':attribute 必須包含多於 :value 個項目。',
     ],
     'gte'                  => [
-        'numeric' => 'The :attribute必須大於或等於:value。',
-        'file'    => 'The :attribute必須大於或等於:value千字節。',
-        'string'  => 'The :attribute必須多於或等於:value個字符。',
-        'array'   => ':attribute必須具有:value或更多項。',
+        'numeric' => ':attribute 必須大於或等於 :value。',
+        'file'    => ':attribute 必須大於或等於 :value KB。',
+        'string'  => ':attribute 必須多於或等於 :value 個字元。',
+        'array'   => ':attribute 必須有 :value 或更多項。',
     ],
-    'exists'               => '選的 :attribute 無效。',
-    'image'                => ':attribute 必須是一個圖片。',
-    'image_extension'      => 'The :attribute必須具有有效且受支持的圖像擴展名.',
-    'in'                   => '選的 :attribute 無效。',
-    'integer'              => ':attribute 必須是一個整數。',
-    'ip'                   => ':attribute 必須是一個有效的IP位址。',
-    'ipv4'                 => 'The :attribute必須是有效的IPv4地址。',
-    'ipv6'                 => 'The :attribute必須是有效的IPv6地址。',
-    'json'                 => 'The :attribute必須是有效的JSON字符串。',
+    'exists'               => '選的 :attribute 無效。',
+    'image'                => ':attribute 必須圖片。',
+    'image_extension'      => ':attribute 必須包含有效且受支援的圖片副檔名。',
+    'in'                   => '選的 :attribute 無效。',
+    'integer'              => ':attribute 必須是整數。',
+    'ip'                   => ':attribute 必須是有效的 IP 位置。',
+    'ipv4'                 => ':attribute 必須是有效的 IPv4 位置。',
+    'ipv6'                 => ':attribute 必須是有效的 IPv6 位置。',
+    'json'                 => ':attribute 必須是有效的 JSON 字串。',
     'lt'                   => [
-        'numeric' => ':attribute必須小於:value。',
-        'file'    => 'The :attribute必須小於:value千字節。',
-        'string'  => ':attribute必須少於:value個字符。',
-        'array'   => ':attribute必須少於:value個項目。',
+        'numeric' => ':attribute 必須小於 :value。',
+        'file'    => ':attribute 必須小於 :value KB。',
+        'string'  => ':attribute 必須少於 :value 個字元。',
+        'array'   => ':attribute 必須少於 :value 個項目。',
     ],
     'lte'                  => [
-        'numeric' => ':attribute必須小於或等於:value。',
-        'file'    => ':attribute必須小於或等於:value KB。',
-        'string'  => ':attribute必須少於或等於:value個字符。',
-        'array'   => ':attribute不得多過:value個項目。',
+        'numeric' => ':attribute 必須小於或等於 :value。',
+        'file'    => ':attribute 必須小於或等於 :value KB。',
+        'string'  => ':attribute 必須少於或等於 :value 個字元。',
+        'array'   => ':attribute 不能超過 :value 個項目。',
     ],
     'max'                  => [
-        'numeric' => ':attribute 不能超過:max。',
-        'file'    => ':attribute 不能超過:max KB。',
-        'string'  => ':attribute 不能超過:max個字元。',
+        'numeric' => ':attribute 不能超過 :max。',
+        'file'    => ':attribute 不能超過 :max KB。',
+        'string'  => ':attribute 不能超過 :max 個字元。',
         'array'   => ':attribute 不能有超過:max項。',
     ],
     'mimes'                => ':attribute 必須是 :values 類型的檔案。',
@@ -78,7 +78,6 @@ return [
         'string'  => ':attribute 至少為:min個字元。',
         'array'   => ':attribute 至少有:min項。',
     ],
-    'no_double_extension'  => 'The :attribute必須僅具有一個文件擴展名。',
     'not_in'               => '選中的 :attribute 無效。',
     'not_regex'            => 'The :attribute格式無效。',
     'numeric'              => ':attribute 必須是一個數。',
@@ -90,7 +89,7 @@ return [
     'required_without'     => '當:values不存在時,:attribute 字段是必需的。',
     'required_without_all' => '當:values均不存在時,:attribute 字段是必需的。',
     'same'                 => ':attribute 與 :other 必須匹配。',
-    'safe_url'             => 'The provided link may not be safe.',
+    'safe_url'             => '提供的連結可能不安全。',
     'size'                 => [
         'numeric' => ':attribute 必須為:size。',
         'file'    => ':attribute 必須為:size KB。',
index 75adf12aacde34a99b795c7278320f6ebdfee62b..1ee4e070983744e2b848d7c6eee63f08ae12a0d0 100644 (file)
   margin-bottom: $-xs;
   margin-inline-end: $-xs;
   border-radius: 4px;
-  border: 1px solid #CCC;
+  border: 1px solid;
   overflow: hidden;
   font-size: 0.85em;
-  a, a:hover, a:active {
+  @include lightDark(border-color, #CCC, #666);
+  a, span, a:hover, a:active {
     padding: 4px 8px;
-    @include lightDark(color, #777, #999);
+    @include lightDark(color, rgba(0, 0, 0, 0.6), rgba(255, 255, 255, 0.8));
     transition: background-color ease-in-out 80ms;
     text-decoration: none;
   }
     @include lightDark(background-color, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.3));
   }
   svg {
-    fill: #888;
+    @include lightDark(fill, rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.5));
   }
   .tag-value {
-    border-inline-start: 1px solid #DDD;
+    border-inline-start: 1px solid;
+    @include lightDark(border-color, #DDD, #666);
     @include lightDark(background-color, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.2))
   }
 }
index bd76090523ca70a8ce24cba5ec5037ed7f5b74ae..850443d9a7c02a500832990eaa0b9d4e7a7ca8d5 100644 (file)
@@ -117,6 +117,7 @@ button {
   align-items: center;
   padding: $-s $-m;
   padding-bottom: ($-s - 2px);
+  width: 100%;
   svg {
     display: inline-block;
     width: 24px;
index ede26c51ca3d16bd68530105bfab92b4482c5aaa..ad630469438f770dcb6c95c204801f70475274ab 100644 (file)
@@ -783,6 +783,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 
 .custom-select-input {
   max-width: 280px;
-  border: 1px solid #DDD;
-  border-radius: 4px;
+  border: 1px solid #D4D4D4;
+  border-radius: 3px;
 }
\ No newline at end of file
diff --git a/resources/sass/_footer.scss b/resources/sass/_footer.scss
new file mode 100644 (file)
index 0000000..1c58bcc
--- /dev/null
@@ -0,0 +1,17 @@
+/**
+ * Includes the footer links.
+ */
+
+ footer {
+    flex-shrink: 0;
+    padding: 1rem 1rem 2rem 1rem;
+    text-align: center;
+  }
+  
+  footer a {
+    margin: 0 .5em;
+  }
+  
+  body.flexbox footer {
+    display: none;
+  }
\ No newline at end of file
index 11ea1cc7f89bc6b29ff52001613b10e568f868e0..c42399de11efdf91ef0a1bd61761740aa48b96e6 100644 (file)
@@ -196,6 +196,16 @@ input[type="color"], input[type="password"], select, textarea {
   @extend .input-base;
 }
 
+select {
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  appearance: none;
+  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%23666666'><polygon points='0,0 100,0 50,50'/></svg>");
+  background-size: 12px;
+  background-position: calc(100% - 20px) 70%;
+  background-repeat: no-repeat;
+}
+
 input[type=date] {
   width: 190px;
 }
index 246ef4b5bdabdfba587b93d96b363edd67407808..1a7015078e3891f44fa3511e0b88985073ce7f96 100644 (file)
@@ -3,7 +3,7 @@
  */
 
 header .grid {
-  grid-template-columns: auto min-content auto;
+  grid-template-columns: minmax(max-content, 2fr) 1fr minmax(max-content, 2fr);
 }
 
 @include smaller-than($l) {
@@ -77,9 +77,6 @@ header {
 }
 
 
-.header-search {
-  display: inline-block;
-}
 header .search-box {
   display: inline-block;
   margin-top: 10px;
@@ -194,6 +191,11 @@ header .search-box {
       @include lightDark(color, #000, #fff);
       text-decoration: none;
     }
+    &:focus {
+      @include lightDark(background-color, #eee, #333);
+      outline-color: var(--color-primary);
+      color: var(--color-primary);
+    }
   }
   header .dropdown-container {
     display: block;
@@ -219,17 +221,22 @@ header .search-box {
   z-index: 5;
   background-color: #FFF;
   border-bottom: 1px solid #DDD;
+  @include lightDark(border-bottom-color, #DDD, #333);
   box-shadow: $bs-card;
 }
 .tri-layout-mobile-tab {
   text-align: center;
   border-bottom: 3px solid #BBB;
   cursor: pointer;
+  margin: 0;
+  @include lightDark(background-color, #FFF, #222);
+  @include lightDark(border-bottom-color, #BBB, #333);
   &:first-child {
     border-inline-end: 1px solid #DDD;
+    @include lightDark(border-inline-end-color, #DDD, #000);
   }
-  &.active {
-    border-bottom-color: currentColor;
+  &[aria-selected="true"] {
+    border-bottom-color: currentColor !important;
   }
 }
 
@@ -272,7 +279,7 @@ header .search-box {
 .dropdown-search {
   position: relative;
   .dropdown-search-toggle {
-    padding: 6px;
+    padding: $-xs;
     border: 1px solid transparent;
     border-radius: 4px;
     &:hover {
@@ -284,6 +291,14 @@ header .search-box {
   }
 }
 
+.dropdown-search-toggle.compact {
+  padding: $-xxs $-xs;
+  .avatar {
+    height: 22px;
+    width: 22px;
+  }
+}
+
 .faded {
   a, button, span, span > div {
     color: #666;
index 57869d6520b04aa6a90ea5405c36d8a95de0927f..1d5defa9765fea5fdb4d2fca441fcc3a71d15691 100644 (file)
@@ -25,4 +25,7 @@ body {
   line-height: 1.6;
   @include lightDark(color, #444, #AAA);
   -webkit-font-smoothing: antialiased;
-}
\ No newline at end of file
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
index 57800d41ef436f791062f62d0b0288387866c0fb..c5af303f8751738b444e8beafdbb24f480e95d81 100644 (file)
   }
 }
 
+#content {
+  flex: 1 0 auto;
+}
+
 /**
  * Flexbox layout system
  */
@@ -168,6 +172,9 @@ body.flexbox {
 .justify-center {
   justify-content: center;
 }
+.justify-space-between {
+  justify-content: space-between;
+}
 .items-center {
   align-items: center;
 }
@@ -267,6 +274,7 @@ body.flexbox {
   .tri-layout-middle {
     grid-area: b;
     padding-top: $-m;
+    min-width: 0;
   }
 }
 @include smaller-than($xxl) {
index a3a58e6c6cd5c66f678d2681d6744f6847748a31..0bef608a7e2d4c9b0f925414b9ebeb7e895024ea 100644 (file)
@@ -430,6 +430,9 @@ ul.pagination {
     flex: 1;
     text-align: start;
   }
+  > .content {
+    min-width: 0;
+  }
   &:not(.no-hover) {
     cursor: pointer;
   }
@@ -547,6 +550,17 @@ ul.pagination {
   }
 }
 
+.entity-item-tags {
+  font-size: .75rem;
+  opacity: 1;
+  .primary-background-light {
+    background: transparent;
+  }
+  .tag-name {
+    background-color: rgba(0, 0, 0, 0.05);
+  }
+}
+
 .dropdown-container {
   display: inline-block;
   vertical-align: top;
index 315f979f3efdb2c215d8de56ab66609ca8e11e48..c78e13446129644f1ea9501c3b177435f281983e 100644 (file)
@@ -11,6 +11,7 @@ table {
     border: 1px solid #DDD;
     overflow: auto;
     line-height: 1.2;
+       word-break: break-word;
   }
   td p, th p {
     margin: 0;
index dfaf6683ed5888a236d1db4a42aecd21618d62fa..05f48b073c0385cabd655940cb924daf17ca3c25 100644 (file)
 }
 
 .page-content.mce-content-body {
-  padding-top: 16px;
+  padding-block-start: 1rem;
+  padding-block-end: 1rem;
   outline: none;
+  display: block;
+}
+
+.page-content.mce-content-body > :last-child {
+  margin-bottom: 3rem;
 }
 
 // Fix to prevent 'No color' option from not being clickable.
index d0d1f27d3715b0fc8599b661b671f0c604c568d7..6b57147eff5ff16d387453b65ffcd4abb94ee2d5 100644 (file)
@@ -4,15 +4,11 @@
 // Screen breakpoints
 $xxl: 1400px;
 $xl: 1100px;
-$ipad-width: 1028px; // Is actually 1024 but we go over to ensure functionality.
 $l: 1000px;
 $m: 880px;
 $s: 600px;
 $xs: 400px;
 $xxs: 360px;
-$screen-lg: 1200px;
-$screen-md: 992px;
-$screen-sm: 768px;
 
 // List of screen sizes
 $screen-sizes: (('xxs', $xxs), ('xs', $xs), ('s', $s), ('m', $m), ('l', $l), ('xl', $xl));
@@ -28,7 +24,7 @@ $-xs: 6px;
 $-xxs: 3px;
 
 // List of our spacing sizes
-$spacing: (('none', 0), ('xxs', $-xxs), ('xs', $-xs), ('s', $-s), ('m', $-m), ('l', $-l), ('xl', $-xl), ('xxl', $-xxl));
+$spacing: (('none', 0), ('xxs', $-xxs), ('xs', $-xs), ('s', $-s), ('m', $-m), ('l', $-l), ('xl', $-xl), ('xxl', $-xxl), ('auto', auto));
 
 // Fonts
 $text: -apple-system, BlinkMacSystemFont,
@@ -36,7 +32,6 @@ $text: -apple-system, BlinkMacSystemFont,
 "Fira Sans", "Droid Sans", "Helvetica Neue",
 sans-serif;
 $mono: "Lucida Console", "DejaVu Sans Mono", "Ubuntu Mono", Monaco, monospace;
-$heading: $text;
 $fs-m: 14px;
 $fs-s: 12px;
 
@@ -59,7 +54,6 @@ $warning: #cf4d03;
 
 // Text colours
 $text-dark: #444;
-$text-light: #EEE;
 
 // Shadows
 $bs-light: 0 0 4px 1px #CCC;
index 6d9a1a7182afb7aa734175ee4c2bdc898f32a6f7..b8682ed05f69b030e6f8fbef343cf3b995d39f3d 100644 (file)
@@ -1,16 +1,13 @@
 @import "variables";
 @import "mixins";
-@import "spacing";
 @import "html";
 @import "text";
 @import "layout";
 @import "blocks";
 @import "tables";
-@import "header";
 @import "lists";
 @import "pages";
 
-
 html, body {
   background-color: #FFF;
 }
@@ -19,6 +16,7 @@ body {
   font-family: 'DejaVu Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Roboto", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
   margin: 0;
   padding: 0;
+  display: block;
 }
 
 table {
@@ -39,4 +37,25 @@ pre:after {
 }
 pre code {
   white-space: pre-wrap;
+}
+
+.page-break {
+  page-break-after: always;
+}
+@media screen {
+  .page-break {
+    border-top: 1px solid #DDD;
+  }
+}
+
+ul.contents ul li {
+  list-style: circle;
+}
+
+.chapter-hint {
+  color: #888;
+  margin-top: 32px;
+}
+.chapter-hint + h1 {
+  margin-top: 0;
 }
\ No newline at end of file
index 78d94f977f8d0043679557148fc00af97a8cd276..743db9888f771b69bc42d429cbfb8d81e88e1031 100644 (file)
@@ -15,6 +15,7 @@
 @import "codemirror";
 @import "components";
 @import "header";
+@import "footer";
 @import "lists";
 @import "pages";
 
@@ -192,8 +193,12 @@ $btt-size: 40px;
   .entity-list-item p {
     margin-bottom: 0;
   }
+  .entity-list-item:focus {
+    outline: 2px dotted var(--color-primary);
+    outline-offset: -4px;
+  }
   .entity-list-item.selected {
-    background-color: rgba(0, 0, 0, 0.05) !important;
+    @include lightDark(background-color, rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
   }
   .loading {
     height: 400px;
index d9c3d659513507c952e3aaecac81d6f7d6c6df06..56f7135c36bca4776474eaa839bb6d5aa80e81fc 100644 (file)
                             <h5 id="{{ $endpoint['name'] }}" class="text-mono mb-m">
                                 <span class="api-method" data-method="{{ $endpoint['method'] }}">{{ $endpoint['method'] }}</span>
                                 @if($endpoint['controller_method_kebab'] === 'list')
-                                    <a style="color: inherit;" target="_blank" href="{{ url($endpoint['uri']) }}">{{ url($endpoint['uri']) }}</a>
+                                    <a style="color: inherit;" target="_blank" rel="noopener" href="{{ url($endpoint['uri']) }}">{{ url($endpoint['uri']) }}</a>
                                 @else
                                     {{ url($endpoint['uri']) }}
                                 @endif
index 313faa5755f34e9aebda614f47f563a762911956..b48fde9c01d90c0efe6341fcf88c7ec5d1b5d8a4 100644 (file)
@@ -7,7 +7,7 @@
              class="card drag-card">
             <div class="handle">@icon('grip')</div>
             <div class="py-s">
-                <a href="{{ $attachment->getUrl() }}" target="_blank">{{ $attachment->name }}</a>
+                <a href="{{ $attachment->getUrl() }}" target="_blank" rel="noopener">{{ $attachment->name }}</a>
             </div>
             <div class="flex-fill justify-flex-end">
                 <button component="event-emit-select"
index 7d6595894fa5222c974e670415bd7dceddc04b3f..1afd2d9bb6d6540b659a3aa9bb32ae48d1b76127 100644 (file)
@@ -2,7 +2,7 @@
     {!! csrf_field() !!}
 
     <div>
-        <button id="saml-login" class="button outline block svg">
+        <button id="saml-login" class="button outline svg">
             @icon('saml2')
             <span>{{ trans('auth.log_in_with', ['socialDriver' => config('saml2.name')]) }}</span>
         </button>
index 868e0555fd5fe7a13f6cad9f0ce5142892223492..4212c1964c8c0c478aa8c7f3c51da1607f13fe94 100644 (file)
@@ -15,7 +15,7 @@
                 <hr class="my-l">
                 @foreach($socialDrivers as $driver => $name)
                     <div>
-                        <a id="social-login-{{$driver}}" class="button outline block svg" href="{{ url("/login/service/" . $driver) }}">
+                        <a id="social-login-{{$driver}}" class="button outline svg" href="{{ url("/login/service/" . $driver) }}">
                             @icon('auth/' . $driver)
                             <span>{{ trans('auth.log_in_with', ['socialDriver' => $name]) }}</span>
                         </a>
index 34aa81d7bf991f078104f8cff5dbecca69773737..d3483c6e610f5a3d370a8255b053c58d8b665b02 100644 (file)
                     </div>
                 </div>
 
-
             </form>
 
             @if(count($socialDrivers) > 0)
                 <hr class="my-l">
                 @foreach($socialDrivers as $driver => $name)
                     <div>
-                        <a id="social-register-{{$driver}}" class="button block outline svg" href="{{ url("/register/service/" . $driver) }}">
+                        <a id="social-register-{{$driver}}" class="button outline svg" href="{{ url("/register/service/" . $driver) }}">
                             @icon('auth/' . $driver)
                             <span>{{ trans('auth.sign_up_with', ['socialDriver' => $name]) }}</span>
                         </a>
index a5404a36506ae9d70225034a6eeef749eeb86d28..66604345f3bfb0c4136138eb6269b116dc1ea7da 100644 (file)
     @include('partials.notifications')
     @include('common.header')
 
-    <div id="content" class="block">
+    <div id="content" components="@yield('content-components')" class="block">
         @yield('content')
     </div>
 
+    @include('common.footer')
+
     <div back-to-top class="primary-background print-hidden">
         <div class="inner">
             @icon('chevron-up') <span>{{ trans('common.back_to_top') }}</span>
index f62b895827b7c87b0a3f47ed82d66037fd1d5ad1..9cd5618a53f3be19fe609fefdeadb3e6e1fed4f0 100644 (file)
@@ -1,38 +1,8 @@
-<!doctype html>
-<html lang="{{ config('app.lang') }}">
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-    <title>{{ $book->name }}</title>
+@extends('export-layout')
 
-    @include('partials.export-styles', ['format' => $format])
-
-    <style>
-        .page-break {
-            page-break-after: always;
-        }
-        .chapter-hint {
-            color: #888;
-            margin-top: 32px;
-        }
-        .chapter-hint + h1 {
-            margin-top: 0;
-        }
-        ul.contents ul li {
-            list-style: circle;
-        }
-        @media screen {
-            .page-break {
-                border-top: 1px solid #DDD;
-            }
-        }
-    </style>
-    @yield('head')
-    @include('partials.custom-head')
-</head>
-<body>
-
-<div class="page-content">
+@section('title', $book->name)
 
+@section('content')
     <h1 style="font-size: 4.8em">{{$book->name}}</h1>
 
     <p>{{ $book->description }}</p>
@@ -73,8 +43,4 @@
         @endif
 
     @endforeach
-
-</div>
-
-</body>
-</html>
+@endsection
\ No newline at end of file
index 840d0604c8e0c22185d6e33ea43eac02e880ff88..9fdcaea61be259070a57dcc0d76b1800a280e4bc 100644 (file)
@@ -2,7 +2,7 @@
 {{ csrf_field() }}
 <div class="form-group title-input">
     <label for="name">{{ trans('common.name') }}</label>
-    @include('form.text', ['name' => 'name'])
+    @include('form.text', ['name' => 'name', 'autofocus' => true])
 </div>
 
 <div class="form-group description-input">
index 17cf4c71f9878117471b6afef38f3eccff615331..a3ff0971f117cd6a8d7f748718d7176ba2af1b8f 100644 (file)
@@ -5,7 +5,7 @@
     <div class="content">
         <h4 class="entity-list-item-name break-text">{{ $book->name }}</h4>
         <div class="entity-item-snippet">
-            <p class="text-muted break-text mb-s">{{ $book->getExcerpt() }}</p>
+            <p class="text-muted break-text mb-s text-limit-lines-1">{{ $book->description }}</p>
         </div>
     </div>
 </a>
\ No newline at end of file
index def198bddac2450b34958534cd4572da01ee0ba5..b850127ff5f595220df52722ba0ecc75180d2ec2 100644 (file)
 
             <hr class="primary-background">
 
+            @if(signedInUser())
+                @include('partials.entity-favourite-action', ['entity' => $book])
+            @endif
             @include('partials.entity-export-menu', ['entity' => $book])
         </div>
     </div>
index 506e8db3d40f7c18b5c7ca60fefb3ca4fe0c5ff6..18f056f27f175570b8546be9322bc858afecbd19 100644 (file)
@@ -1,30 +1,8 @@
-<!doctype html>
-<html lang="{{ config('app.lang') }}">
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-    <title>{{ $chapter->name }}</title>
+@extends('export-layout')
 
-    @include('partials.export-styles', ['format' => $format])
-
-    <style>
-        .page-break {
-            page-break-after: always;
-        }
-        ul.contents ul li {
-            list-style: circle;
-        }
-        @media screen {
-            .page-break {
-                border-top: 1px solid #DDD;
-            }
-        }
-    </style>
-    @include('partials.custom-head')
-</head>
-<body>
-
-<div class="page-content">
+@section('title', $chapter->name)
 
+@section('content')
     <h1 style="font-size: 4.8em">{{$chapter->name}}</h1>
 
     <p>{{ $chapter->description }}</p>
@@ -42,8 +20,4 @@
         <h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
         {!! $page->html !!}
     @endforeach
-
-</div>
-
-</body>
-</html>
+@endsection
\ No newline at end of file
index 60cfe6674f1b7eeaf7382575a24e970264698593..0de665744474c40a20c918741ccf7bfd71070062 100644 (file)
@@ -3,7 +3,7 @@
 
 <div class="form-group title-input">
     <label for="name">{{ trans('common.name') }}</label>
-    @include('form.text', ['name' => 'name'])
+    @include('form.text', ['name' => 'name', 'autofocus' => true])
 </div>
 
 <div class="form-group description-input">
index db02ebcc4f9ae6b6d686a591a8eb2398f5d905d8..8aa3be1112b21c4984ea93956a0779b29dd90964 100644 (file)
 
             <hr class="primary-background"/>
 
+            @if(signedInUser())
+                @include('partials.entity-favourite-action', ['entity' => $chapter])
+            @endif
             @include('partials.entity-export-menu', ['entity' => $chapter])
         </div>
     </div>
similarity index 82%
rename from resources/views/pages/detailed-listing.blade.php
rename to resources/views/common/detailed-listing-paginated.blade.php
index c2bbdb53711629d86bc7a3272f8fcab12ff6ba20..af9490a410f897a62891230268130591f8e253c3 100644 (file)
@@ -6,11 +6,11 @@
             <h1 class="list-heading">{{ $title }}</h1>
 
             <div class="book-contents">
-                @include('partials.entity-list', ['entities' => $pages, 'style' => 'detailed'])
+                @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed'])
             </div>
 
             <div class="text-center">
-                {!! $pages->links() !!}
+                {!! $entities->links() !!}
             </div>
         </main>
     </div>
diff --git a/resources/views/common/detailed-listing-with-more.blade.php b/resources/views/common/detailed-listing-with-more.blade.php
new file mode 100644 (file)
index 0000000..5790f20
--- /dev/null
@@ -0,0 +1,19 @@
+@extends('simple-layout')
+
+@section('body')
+    <div class="container small pt-xl">
+        <main class="card content-wrap">
+            <h1 class="list-heading">{{ $title }}</h1>
+
+            <div class="book-contents">
+                @include('partials.entity-list', ['entities' => $entities, 'style' => 'detailed'])
+            </div>
+
+            <div class="text-right">
+                @if($hasMoreLink)
+                    <a href="{{ $hasMoreLink }}" class="button outline">{{ trans('common.more') }}</a>
+                @endif
+            </div>
+        </main>
+    </div>
+@stop
\ No newline at end of file
diff --git a/resources/views/common/footer.blade.php b/resources/views/common/footer.blade.php
new file mode 100644 (file)
index 0000000..dd488dc
--- /dev/null
@@ -0,0 +1,7 @@
+@if(count(setting('app-footer-links', [])) > 0)
+<footer>
+    @foreach(setting('app-footer-links', []) as $link)
+        <a href="{{ $link['url'] }}" target="_blank" rel="noopener">{{ strpos($link['label'], 'trans::') === 0 ? trans(str_replace('trans::', '', $link['label'])) : $link['label'] }}</a>
+    @endforeach
+</footer>
+@endif
\ No newline at end of file
index 80e79410a04ff3aeee9c4324ca3978e6b4eabf35..274a09996125f21303d4d38a58b91f4214fe65f1 100644 (file)
@@ -1,4 +1,4 @@
-<header id="header" header-mobile-toggle class="primary-background">
+<header id="header" component="header-mobile-toggle" class="primary-background">
     <div class="grid mx-l">
 
         <div>
                     <span class="logo-text">{{ setting('app-name') }}</span>
                 @endif
             </a>
-            <div class="mobile-menu-toggle hide-over-l">@icon('more')</div>
+            <button type="button"
+                    refs="header-mobile-toggle@toggle"
+                    title="{{ trans('common.header_menu_expand') }}"
+                    aria-expanded="false"
+                    class="mobile-menu-toggle hide-over-l">@icon('more')</button>
         </div>
 
-        <div class="header-search hide-under-l">
+        <div class="flex-container-row justify-center hide-under-l">
             @if (hasAppAccess())
             <form action="{{ url('/search') }}" method="GET" class="search-box" role="search">
                 <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button>
@@ -25,7 +29,7 @@
         </div>
 
         <div class="text-right">
-            <nav class="header-links">
+            <nav refs="header-mobile-toggle@menu" class="header-links">
                 <div class="links text-center">
                     @if (hasAppAccess())
                         <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
@@ -50,7 +54,7 @@
                 </div>
                 @if(signedInUser())
                     <?php $currentUser = user(); ?>
-                    <div class="dropdown-container" component="dropdown">
+                    <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
                         <span class="user-name py-s hide-under-l" refs="dropdown@toggle"
                               aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
                             <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
                         </span>
                         <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
                             <li>
-                                <a href="{{ url("/user/{$currentUser->id}") }}">@icon('user'){{ trans('common.view_profile') }}</a>
+                                <a href="{{ url('/favourites') }}">@icon('star'){{ trans('entities.my_favourites') }}</a>
                             </li>
                             <li>
-                                <a href="{{ url("/settings/users/{$currentUser->id}") }}">@icon('edit'){{ trans('common.edit_profile') }}</a>
+                                <a href="{{ $currentUser->getProfileUrl() }}">@icon('user'){{ trans('common.view_profile') }}</a>
+                            </li>
+                            <li>
+                                <a href="{{ $currentUser->getEditUrl() }}">@icon('edit'){{ trans('common.edit_profile') }}</a>
                             </li>
                             <li>
                                 @if(config('auth.method') === 'saml2')
index 3dbcd2875f9eb8457165dea32680a8832623c9d1..1c18edb246bc1ce93040c7af6325d2a0250647c1 100644 (file)
     <div class="actions mb-xl">
         <h5>{{ trans('common.actions') }}</h5>
         <div class="icon-list text-primary">
+            @if(user()->can('book-create-all'))
+                <a href="{{ url("/create-book") }}" class="icon-list-item">
+                    <span>@icon('add')</span>
+                    <span>{{ trans('entities.books_create') }}</span>
+                </a>
+            @endif
             @include('partials.view-toggle', ['view' => $view, 'type' => 'books'])
             @include('components.expand-toggle', ['target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
             @include('partials.dark-mode-toggle', ['classes' => 'text-muted icon-list-item text-primary'])
         </div>
     </div>
-@stop
\ No newline at end of file
+@stop
index fccbef288730d42af2cbd22d860254d943101ebf..957fa6578fffd35e349c550fdf521a237fd09440 100644 (file)
     <div class="actions mb-xl">
         <h5>{{ trans('common.actions') }}</h5>
         <div class="icon-list text-primary">
+            @if(user()->can('bookshelf-create-all'))
+                <a href="{{ url("/create-shelf") }}" class="icon-list-item">
+                    <span>@icon('add')</span>
+                    <span>{{ trans('entities.shelves_new_action') }}</span>
+                </a>
+            @endif
             @include('partials.view-toggle', ['view' => $view, 'type' => 'shelves'])
             @include('components.expand-toggle', ['target' => '.entity-list.compact .entity-item-snippet', 'key' => 'home-details'])
             @include('partials.dark-mode-toggle', ['classes' => 'text-muted icon-list-item text-primary'])
         </div>
     </div>
-@stop
\ No newline at end of file
+@stop
index 4c36ce61a9be9648f48dbc68462b2b75bc73f83a..5db89e8709147b66f745e9cf4f59042816b933c3 100644 (file)
@@ -5,6 +5,20 @@
     </div>
 @endif
 
+@if(count($favourites) > 0)
+    <div id="top-favourites" class="card mb-xl">
+        <h3 class="card-title">
+            <a href="{{ url('/favourites') }}" class="no-color">{{ trans('entities.my_most_viewed_favourites') }}</a>
+        </h3>
+        <div class="px-m">
+            @include('partials.entity-list', [
+            'entities' => $favourites,
+            'style' => 'compact',
+            ])
+        </div>
+    </div>
+@endif
+
 <div class="mb-xl">
     <h5>{{ trans('entities.' . (auth()->check() ? 'my_recently_viewed' : 'books_recent')) }}</h5>
     @include('partials.entity-list', [
index ad503463e46f1db404882fbf58018dfae39b227c..3a360b19cc16ac7f12d1a7f1564ff4ccd1e67ae5 100644 (file)
             </div>
 
             <div>
+                @if(count($favourites) > 0)
+                    <div id="top-favourites" class="card mb-xl">
+                        <h3 class="card-title">
+                            <a href="{{ url('/favourites') }}" class="no-color">{{ trans('entities.my_most_viewed_favourites') }}</a>
+                        </h3>
+                        <div class="px-m">
+                            @include('partials.entity-list', [
+                            'entities' => $favourites,
+                            'style' => 'compact',
+                            ])
+                        </div>
+                    </div>
+                @endif
+
                 <div id="recent-pages" class="card mb-xl">
                     <h3 class="card-title"><a class="no-color" href="{{ url("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h3>
                     <div id="recently-updated-pages" class="px-m">
index cb41950cb0e1426cc21100be417613e6187a1d33..c71fdff633ad3e8f7ab1c9d002b29e253b661398 100644 (file)
@@ -1,12 +1,15 @@
 <div class="form-group entity-selector-container">
-    <div entity-selector class="entity-selector {{$selectorSize ?? ''}}" entity-types="{{ $entityTypes ?? 'book,chapter,page' }}" entity-permission="{{ $entityPermission ?? 'view' }}">
-        <input type="hidden" entity-selector-input name="{{$name}}" value="">
-        <input type="text" placeholder="{{ trans('common.search') }}" entity-selector-search>
-        <div class="text-center loading" entity-selector-loading>@include('partials.loading-icon')</div>
-        <div entity-selector-results></div>
+    <div component="entity-selector"
+         class="entity-selector {{$selectorSize ?? ''}}"
+         option:entity-selector:entity-types="{{ $entityTypes ?? 'book,chapter,page' }}"
+         option:entity-selector:entity-permission="{{ $entityPermission ?? 'view' }}">
+        <input refs="entity-selector@input" type="hidden" name="{{$name}}" value="">
+        <input type="text" placeholder="{{ trans('common.search') }}" @if($autofocus ?? false) autofocus @endif refs="entity-selector@search">
+        <div class="text-center loading" refs="entity-selector@loading">@include('partials.loading-icon')</div>
+        <div refs="entity-selector@results"></div>
         @if($showAdd ?? false)
             <div class="entity-selector-add">
-                <button entity-selector-add-button type="button"
+                <button refs="entity-selector@add" type="button"
                         class="button outline">@icon('add'){{ trans('common.add') }}</button>
             </div>
         @endif
index e49a5fca723f8c96fbc1182856b4dae676ac15a8..6d62552266945fc7d27579cab7044a835289907f 100644 (file)
@@ -7,7 +7,7 @@
           option:ajax-form:url="{{ url('images/' . $image->id) }}">
 
         <div class="image-manager-viewer">
-            <a href="{{ $image->url }}" target="_blank" class="block">
+            <a href="{{ $image->url }}" target="_blank" rel="noopener" class="block">
                 <img src="{{ $image->thumbs['display'] }}"
                      alt="{{ $image->name }}"
                      class="anim fadeIn"
@@ -40,6 +40,7 @@
                     <li>
                         <a href="{{ $page->url }}"
                            target="_blank"
+                           rel="noopener"
                            class="text-neg">{{ $page->name }}</a>
                     </li>
                 @endforeach
index c59615d92a30a38fbb0aa66feba92d5a56c0ad1f..0df42e3cef9993f12c7552881a44a76ebbfbcb03 100644 (file)
@@ -3,7 +3,7 @@
 <div page-picker>
     <div class="input-base">
         <span @if($value) style="display: none" @endif page-picker-default class="text-muted italic">{{ $placeholder }}</span>
-        <a @if(!$value) style="display: none" @endif href="{{ url('/link/' . $value) }}" target="_blank" class="text-page" page-picker-display>#{{$value}}, {{$value ? \BookStack\Entities\Models\Page::find($value)->name : '' }}</a>
+        <a @if(!$value) style="display: none" @endif href="{{ url('/link/' . $value) }}" target="_blank" rel="noopener" class="text-page" page-picker-display>#{{$value}}, {{$value ? \BookStack\Entities\Models\Page::find($value)->name : '' }}</a>
     </div>
     <br>
     <input type="hidden" value="{{$value}}" name="{{$name}}" id="{{$name}}">
index f7a9c6c48623c36d92f9e0434532a1388f5753c7..ffbd5c3303f46cebb93795f6b58f210886b51293 100644 (file)
@@ -1,6 +1,11 @@
 @foreach($entity->tags as $tag)
     <div class="tag-item primary-background-light">
-        <div class="tag-name"><a href="{{ url('/search?term=%5B' . urlencode($tag->name) .'%5D') }}">@icon('tag'){{ $tag->name }}</a></div>
-        @if($tag->value) <div class="tag-value"><a href="{{ url('/search?term=%5B' . urlencode($tag->name) .'%3D' . urlencode($tag->value) . '%5D') }}">{{$tag->value}}</a></div> @endif
+        @if($linked ?? true)
+            <div class="tag-name"><a href="{{ $tag->nameUrl() }}">@icon('tag'){{ $tag->name }}</a></div>
+            @if($tag->value) <div class="tag-value"><a href="{{ $tag->valueUrl() }}">{{$tag->value}}</a></div> @endif
+        @else
+            <div class="tag-name"><span>@icon('tag'){{ $tag->name }}</span></div>
+            @if($tag->value) <div class="tag-value"><span>{{$tag->value}}</span></div> @endif
+        @endif
     </div>
 @endforeach
\ No newline at end of file
index 2c49e965dae1e1dd8da76bead5be3b3349fc87ff..0018d64bf474d1652ec29a8f8f1146ff8da30096 100644 (file)
@@ -1,3 +1,6 @@
+<a href="#" class="flex-container-row items-center dropdown-search-item" data-id="">
+    <span>{{ trans('settings.users_none_selected') }}</span>
+</a>
 @foreach($users as $user)
     <a href="#" class="flex-container-row items-center dropdown-search-item" data-id="{{ $user->id }}">
         <img class="avatar mr-m" src="{{ $user->getAvatar(30) }}" alt="{{ $user->name }}">
index 2a07f0bde5044e03aa00e7eaaff724b2deffce63..50c731efd6e00352f0c388c112fabc516aadfa53 100644 (file)
@@ -3,17 +3,17 @@
 >
     <input refs="user-select@input" type="hidden" name="{{ $name }}" value="{{ $user->id ?? '' }}">
     <div refs="dropdown@toggle"
-         class="dropdown-search-toggle flex-container-row items-center"
+         class="dropdown-search-toggle {{ $compact ? 'compact' : '' }} flex-container-row items-center"
          aria-haspopup="true" aria-expanded="false" tabindex="0">
         <div refs="user-select@user-info" class="flex-container-row items-center px-s">
             @if($user)
-                <img class="avatar mr-m" src="{{ $user->getAvatar(30) }}" alt="{{ $user->name }}">
+                <img class="avatar small mr-m" src="{{ $user->getAvatar($compact ? 22 : 30) }}" alt="{{ $user->name }}">
                 <span>{{ $user->name }}</span>
             @else
                 <span>{{ trans('settings.users_none_selected') }}</span>
             @endif
         </div>
-        <span style="font-size: 1.5rem; margin-left: auto;">
+        <span style="font-size: {{ $compact ? '1.15rem' : '1.5rem' }}; margin-left: auto;">
             @icon('caret-down')
         </span>
     </div>
index 02f97fc546fcbc195ec04421731fba44b15cfb1d..d4d8ed76ba50bd47b83832ea8f04e17ebadca98a 100644 (file)
@@ -7,8 +7,8 @@
         <div class="grid half v-center">
             <div>
                 <h1 class="list-heading">{{ $message ?? trans('errors.404_page_not_found') }}</h1>
-                <h5>{{ trans('errors.sorry_page_not_found') }}</h5>
-                <p>{{ trans('errors.sorry_page_not_found_permission_warning') }}</p>
+                <h5>{{ $subtitle ?? trans('errors.sorry_page_not_found') }}</h5>
+                <p>{{ $details ?? trans('errors.sorry_page_not_found_permission_warning') }}</p>
             </div>
             <div class="text-right">
                 @if(!signedInUser())
@@ -26,7 +26,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['page']), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
@@ -34,7 +34,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['book']), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
@@ -42,7 +42,7 @@
                 <div class="card mb-xl">
                     <h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
                     <div class="px-m">
-                        @include('partials.entity-list', ['entities' => Views::getPopular(10, 0, ['chapter']), 'style' => 'compact'])
+                        @include('partials.entity-list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact'])
                     </div>
                 </div>
             </div>
diff --git a/resources/views/export-layout.blade.php b/resources/views/export-layout.blade.php
new file mode 100644 (file)
index 0000000..f23b3cc
--- /dev/null
@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="{{ config('app.lang') }}">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <title>@yield('title')</title>
+
+    @include('partials.export-styles', ['format' => $format])
+    @include('partials.export-custom-head')
+</head>
+<body>
+<div class="page-content">
+    @yield('content')
+</div>
+</body>
+</html>
\ No newline at end of file
index 35490bed9a89e74f3eeb071e708e5618fa719807..6cf5ab8bdcac9db8b02f3debb384005ddaa27c80 100644 (file)
@@ -15,7 +15,7 @@
         <div>
             <div class="form-group">
                 <label for="owner">{{ trans('entities.permissions_owner') }}</label>
-                @include('components.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by'])
+                @include('components.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by', 'compact' => false])
             </div>
         </div>
     </div>
index a3c868a8dc08ba293e624f27aa8f1229aa5aa039..65df748772598a9f8de8498f4ba705a21041c317 100644 (file)
@@ -1,7 +1,8 @@
 <input type="password" id="{{ $name }}" name="{{ $name }}"
        @if($errors->has($name)) class="text-neg" @endif
        @if(isset($placeholder)) placeholder="{{$placeholder}}" @endif
+       @if(isset($autocomplete)) autocomplete="{{$autocomplete}}" @endif
        @if(old($name)) value="{{ old($name)}}" @endif>
 @if($errors->has($name))
     <div class="text-neg text-small">{{ $errors->first($name) }}</div>
-@endif
\ No newline at end of file
+@endif
index 47a4d870a041be8e10d4a11704e5b2bd9a4bb1d2..74d17c128f794be4c7857c6eb584c07211a3a534 100644 (file)
@@ -1,51 +1,13 @@
-<!doctype html>
-<html lang="{{ config('app.lang') }}">
-<head>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-    <title>{{ $page->name }}</title>
+@extends('export-layout')
 
-    @include('partials.export-styles', ['format' => $format])
+@section('title', $page->name)
 
-    @if($format === 'pdf')
-        <style>
-            body {
-                font-size: 14px;
-                line-height: 1.2;
-            }
+@section('content')
+    @include('pages.page-display')
 
-            h1, h2, h3, h4, h5, h6 {
-                line-height: 1.2;
-            }
-
-            table {
-                max-width: 800px !important;
-                font-size: 0.8em;
-                width: 100% !important;
-            }
-
-            table td {
-                width: auto !important;
-            }
-        </style>
-    @endif
-
-    @include('partials.custom-head')
-</head>
-<body>
-
-<div id="page-show">
-    <div class="page-content">
-
-        @include('pages.page-display')
-
-        <hr>
-
-        <div class="text-muted text-small">
-            @include('partials.entity-export-meta', ['entity' => $page])
-        </div>
+    <hr>
 
+    <div class="text-muted text-small">
+        @include('partials.entity-export-meta', ['entity' => $page])
     </div>
-</div>
-
-</body>
-</html>
+@endsection
\ No newline at end of file
index a9d1f1174bbbabf9f3266f09a6e59a4694beed33..39d628e17d0ce6ec503d6f8ea76051fa7d1238a2 100644 (file)
@@ -2,6 +2,7 @@
      option:markdown-editor:page-id="{{ $model->id ?? 0 }}"
      option:markdown-editor:text-direction="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"
      option:markdown-editor:image-upload-error-text="{{ trans('errors.image_upload_error') }}"
+     option:markdown-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}"
      class="flex-fill flex code-fill">
 
     <div class="markdown-editor-wrap active">
@@ -35,8 +36,6 @@
         </div>
         <iframe src="about:blank" class="markdown-display" sandbox="allow-same-origin"></iframe>
     </div>
-    <input type="hidden" name="html"/>
-
 </div>
 
 
index 3bf1db5e46671f98ead3e3cd8c879f0bb92887ec..26b872cdd769952aa843232daf17d68f32d591b3 100644 (file)
@@ -23,7 +23,7 @@
                 {!! csrf_field() !!}
                 <input type="hidden" name="_method" value="PUT">
 
-                @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create'])
+                @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create', 'autofocus' => true])
 
                 <div class="form-group text-right">
                     <a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
index 6ff33c68de4b7e2baddc4db1301f111a0161c865..6624620c5e0d46d3d6da4120d01e841e89939965 100644 (file)
                             <td><small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }} <br> ({{ $revision->created_at->diffForHumans() }})</small></td>
                             <td>{{ $revision->summary }}</td>
                             <td class="actions">
-                                <a href="{{ $revision->getUrl('changes') }}" target="_blank">{{ trans('entities.pages_revisions_changes') }}</a>
+                                <a href="{{ $revision->getUrl('changes') }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_changes') }}</a>
                                 <span class="text-muted">&nbsp;|&nbsp;</span>
 
 
                                 @if ($index === 0)
-                                    <a target="_blank" href="{{ $page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
+                                    <a target="_blank" rel="noopener" href="{{ $page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
                                 @else
-                                    <a href="{{ $revision->getUrl() }}" target="_blank">{{ trans('entities.pages_revisions_preview') }}</a>
+                                    <a href="{{ $revision->getUrl() }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_preview') }}</a>
                                     <span class="text-muted">&nbsp;|&nbsp;</span>
                                     <div component="dropdown" class="dropdown-container">
                                         <a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('entities.pages_revisions_restore') }}</a>
index 38e819a9e1baecd2dc6d037409cc97fe3770ca43..3476f3a3d732815502c2d93aba8f20c560adc2e0 100644 (file)
 
             <hr class="primary-background"/>
 
-            {{--Export--}}
+            @if(signedInUser())
+                @include('partials.entity-favourite-action', ['entity' => $page])
+            @endif
             @include('partials.entity-export-menu', ['entity' => $page])
         </div>
 
index d8b8b1c353c73f53c10b639635345fe78c86ee14..02948fa2ecd18ed01d2d60754964d7e0207184a3 100644 (file)
@@ -2,6 +2,7 @@
      option:wysiwyg-editor:page-id="{{ $model->id ?? 0 }}"
      option:wysiwyg-editor:text-direction="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"
      option:wysiwyg-editor:image-upload-error-text="{{ trans('errors.image_upload_error') }}"
+     option:wysiwyg-editor:server-upload-limit-text="{{ trans('errors.server_upload_limit') }}"
      class="flex-fill flex">
 
     <textarea id="html-editor"  name="html" rows="5"
diff --git a/resources/views/partials/custom-head-content.blade.php b/resources/views/partials/custom-head-content.blade.php
deleted file mode 100644 (file)
index b245b7a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-@if(setting('app-custom-head', false))
-    <!-- Custom user content -->
-    {!! setting('app-custom-head') !!}
-    <!-- End custom user content -->
-@endif
\ No newline at end of file
index dd7cc41e43a2f81da99d4bef76362294c71c3d8b..fa5ba0cc456333776de144372098e68fc7f3fe65 100644 (file)
@@ -1,5 +1,5 @@
 @if(setting('app-custom-head') && \Route::currentRouteName() !== 'settings')
-    <!-- Custom user content -->
-    {!! setting('app-custom-head') !!}
-    <!-- End custom user content -->
+<!-- Custom user content -->
+{!! setting('app-custom-head') !!}
+<!-- End custom user content -->
 @endif
\ No newline at end of file
index 4d847bcaef801c4700a74a84de8fc7a025fc8fe6..6d23af07c24bc4ae046edf3ee9b4b3cd063783a3 100644 (file)
@@ -5,8 +5,8 @@
         <span>{{ trans('entities.export') }}</span>
     </div>
     <ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
-        <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
-        <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
-        <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
+        <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" rel="noopener">{{ trans('entities.export_html') }} <span class="text-muted float right">.html</span></a></li>
+        <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" rel="noopener">{{ trans('entities.export_pdf') }} <span class="text-muted float right">.pdf</span></a></li>
+        <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" rel="noopener">{{ trans('entities.export_text') }} <span class="text-muted float right">.txt</span></a></li>
     </ul>
 </div>
\ No newline at end of file
index fa1394ed47d5a2d1ae1840f9aa850fb292ab79f3..32b26750eddadd8750b0d8e2a7adba224d402fd8 100644 (file)
@@ -1,33 +1,16 @@
 <div class="entity-meta">
-    @if($entity->isA('revision'))
-        @icon('history'){{ trans('entities.pages_revision') }}
-        {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
-        <br>
-    @endif
-
     @if ($entity->isA('page'))
-        @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
         @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }} <br>
-        @if (userCan('page-update', $entity))</a>@endif
-    @endif
-
-    @if ($entity->createdBy)
-        @icon('star'){!! trans('entities.meta_created_name', [
-            'timeLength' => '<span>'.$entity->created_at->toDayDateTimeString() . '</span>',
-            'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".htmlentities($entity->createdBy->name). "</a>"
-            ]) !!}
-    @else
-        @icon('star')<span>{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->toDayDateTimeString()]) }}</span>
     @endif
 
+    @icon('star'){!! trans('entities.meta_created' . ($entity->createdBy ? '_name' : ''), [
+        'timeLength' => $entity->created_at->toDayDateTimeString(),
+        'user' => e($entity->createdBy->name ?? ''),
+        ]) !!}
     <br>
 
-    @if ($entity->updatedBy)
-        @icon('edit'){!! trans('entities.meta_updated_name', [
-                'timeLength' => '<span>' . $entity->updated_at->toDayDateTimeString() .'</span>',
-                'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".htmlentities($entity->updatedBy->name). "</a>"
-            ]) !!}
-    @elseif (!$entity->isA('revision'))
-        @icon('edit')<span>{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->toDayDateTimeString()]) }}</span>
-    @endif
+    @icon('edit'){!! trans('entities.meta_updated' . ($entity->updatedBy ? '_name' : ''), [
+            'timeLength' => $entity->updated_at->toDayDateTimeString(),
+            'user' => e($entity->updatedBy->name ?? '')
+        ]) !!}
 </div>
\ No newline at end of file
diff --git a/resources/views/partials/entity-favourite-action.blade.php b/resources/views/partials/entity-favourite-action.blade.php
new file mode 100644 (file)
index 0000000..49ba6aa
--- /dev/null
@@ -0,0 +1,12 @@
+@php
+ $isFavourite = $entity->isFavourite();
+@endphp
+<form action="{{ url('/favourites/' . ($isFavourite ? 'remove' : 'add')) }}" method="POST">
+    {{ csrf_field() }}
+    <input type="hidden" name="type" value="{{ get_class($entity) }}">
+    <input type="hidden" name="id" value="{{ $entity->id }}">
+    <button type="submit" class="icon-list-item text-primary">
+        <span>@icon($isFavourite ? 'star' : 'star-outline')</span>
+        <span>{{ $isFavourite ? trans('common.unfavourite') : trans('common.favourite') }}</span>
+    </button>
+</form>
\ No newline at end of file
index d42b1967fcd230a83537cbe76ef6caae369ff184..d605953c77fd38541ff18e976ea81433e3329b52 100644 (file)
@@ -1,4 +1,5 @@
 @component('partials.entity-list-item-basic', ['entity' => $entity])
+
 <div class="entity-item-snippet">
 
     @if($showPath ?? false)
 
     <p class="text-muted break-text">{{ $entity->getExcerpt() }}</p>
 </div>
+
+@if(($showTags ?? false) && $entity->tags->count() > 0)
+    <div class="entity-item-tags mt-xs">
+        @include('components.tag-list', ['entity' => $entity, 'linked' => false ])
+    </div>
+@endif
+
 @endcomponent
\ No newline at end of file
index be826f1ac4f495ba2f61d2eff9cdfd779c44d4fa..393f4e8a792c5af7f92afc92020bb4c599bcb598 100644 (file)
@@ -1,7 +1,7 @@
 @if(count($entities) > 0)
     <div class="entity-list {{ $style ?? '' }}">
         @foreach($entities as $index => $entity)
-            @include('partials.entity-list-item', ['entity' => $entity, 'showPath' => $showPath ?? false])
+            @include('partials.entity-list-item', ['entity' => $entity, 'showPath' => $showPath ?? false, 'showTags' => $showTags ?? false])
         @endforeach
     </div>
 @else
index 8996df9bb67d0f491d9a8de5e5d18b56560883fb..298cc7c3e421750c6ec9669b2fcd9fb0a229f319 100644 (file)
@@ -14,7 +14,7 @@
         </div>
     @endif
 
-    @if ($entity->ownedBy && $entity->ownedBy->id !== $entity->createdBy->id)
+    @if ($entity->ownedBy && $entity->owned_by !== $entity->created_by)
         <div>
             @icon('user'){!! trans('entities.meta_owned_name', [
             'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
diff --git a/resources/views/partials/export-custom-head.blade.php b/resources/views/partials/export-custom-head.blade.php
new file mode 100644 (file)
index 0000000..f428e9f
--- /dev/null
@@ -0,0 +1,5 @@
+@if(setting('app-custom-head'))
+<!-- Custom user content -->
+{!! \BookStack\Util\HtmlContentFilter::removeScripts(setting('app-custom-head')) !!}
+<!-- End custom user content -->
+@endif
\ No newline at end of file
index 52bfda2a6fc40c6c02d745b5079261e88093c017..967dc19ec709264de9302900d967189358aa39e8 100644 (file)
@@ -6,6 +6,27 @@
 
 @if ($format === 'pdf')
     <style>
+
+        /* PDF size adjustments */
+        body {
+            font-size: 14px;
+            line-height: 1.2;
+        }
+
+        h1, h2, h3, h4, h5, h6 {
+            line-height: 1.2;
+        }
+
+        table {
+            max-width: 800px !important;
+            font-size: 0.8em;
+            width: 100% !important;
+        }
+
+        table td {
+            width: auto !important;
+        }
+
         /* Patches for CSS variable colors */
         a {
             color: {{ setting('app-color') }};
index df137bd2a3bcbef73662ff1355b921df8d76f14c..49a6c1f653dcaed97e257df2aa15a4314ffd93c3 100644 (file)
@@ -50,6 +50,9 @@
                             @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'updated_by', 'value' => 'me'])
                                 {{ trans('entities.search_updated_by_me') }}
                             @endcomponent
+                            @component('search.form.boolean-filter', ['filters' => $options->filters, 'name' => 'owned_by', 'value' => 'me'])
+                                {{ trans('entities.search_owned_by_me') }}
+                            @endcomponent
                         @endif
 
                         <h6>{{ trans('entities.search_date_options') }}</h6>
@@ -74,7 +77,7 @@
 
                     <h6 class="text-muted">{{ trans_choice('entities.search_total_results_found', $totalResults, ['count' => $totalResults]) }}</h6>
                     <div class="book-contents">
-                        @include('partials.entity-list', ['entities' => $entities, 'showPath' => true])
+                        @include('partials.entity-list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
                     </div>
 
                     @if($hasNextPage)
index c52390f73c4e7c453ecc71984efc1d2fa17011ba..9fda39a317c96e6536576d30d2d7a209e488ada4 100644 (file)
                 </ul>
             </div>
 
-            @foreach(['date_from', 'date_to'] as $filterKey)
-                <form action="{{ url('/settings/audit') }}" method="get" class="block mr-m">
-                    @foreach($listDetails as $param => $val)
-                        @if(!empty($val) && $param !== $filterKey)
-                            <input type="hidden" name="{{ $param }}" value="{{ $val }}">
-                        @endif
-                    @endforeach
-                    <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
-                    <input id="audit_filter_{{ $filterKey }}"
-                           component="submit-on-change"
-                           type="date"
-                           name="{{ $filterKey }}"
-                           value="{{ $listDetails[$filterKey] ?? '' }}">
-                </form>
-            @endforeach
+            <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row mr-m">
+                @if(!empty($listDetails['event']))
+                    <input type="hidden" name="event" value="{{ $listDetails['event'] }}">
+                @endif
+
+                @foreach(['date_from', 'date_to'] as $filterKey)
+                    <div class="mr-m">
+                        <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
+                        <input id="audit_filter_{{ $filterKey }}"
+                               component="submit-on-change"
+                               type="date"
+                               name="{{ $filterKey }}"
+                               value="{{ $listDetails[$filterKey] ?? '' }}">
+                    </div>
+                @endforeach
+
+                <div class="form-group ml-auto" component="submit-on-change">
+                    <label for="owner">{{ trans('settings.audit_table_user') }}</label>
+                    @include('components.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' =>  true])
+                </div>
+            </form>
         </div>
 
         <hr class="mt-l mb-s">
diff --git a/resources/views/settings/footer-links.blade.php b/resources/views/settings/footer-links.blade.php
new file mode 100644 (file)
index 0000000..10bf756
--- /dev/null
@@ -0,0 +1,34 @@
+{{--
+$value - Setting value
+$name - Setting input name
+--}}
+<div components="add-remove-rows"
+     option:add-remove-rows:row-selector=".card"
+     option:add-remove-rows:remove-selector="button.text-neg">
+
+    <div component="sortable-list"
+         option:sortable-list:handle-selector=".handle">
+        @foreach(array_merge($value, [['label' => '', 'url' => '']]) as $index => $link)
+            <div class="card drag-card {{ $loop->last ? 'hidden' : '' }}" @if($loop->last) refs="add-remove-rows@model" @endif>
+                <div class="handle">@icon('grip')</div>
+                @foreach(['label', 'url'] as $prop)
+                    <div class="outline">
+                        <input value="{{ $link[$prop] ?? '' }}"
+                               placeholder="{{ trans('settings.app_footer_links_' . $prop) }}"
+                               aria-label="{{ trans('settings.app_footer_links_' . $prop) }}"
+                               name="{{ $name }}[{{ $loop->parent->last ? 'randrowid' : $index }}][{{$prop}}]"
+                               type="text"
+                               autocomplete="off"/>
+                    </div>
+                @endforeach
+                <button type="button"
+                        aria-label="{{ trans('common.remove') }}"
+                        class="text-center drag-card-action text-neg">
+                    @icon('close')
+                </button>
+            </div>
+        @endforeach
+    </div>
+
+    <button refs="add-remove-rows@add" type="button" class="text-button">{{ trans('settings.app_footer_links_add') }}</button>
+</div>
\ No newline at end of file
index 8adc1045b46d15a8744e15f06d54b3992d07a3e6..ad03b6c917fdfecc670a3950650bfa092f7c263a 100644 (file)
                         </div>
                     </div>
 
+                    <div>
+                        <label for="setting-app-privacy-link" class="setting-list-label">{{ trans('settings.app_footer_links') }}</label>
+                        <p class="small mb-m">{{ trans('settings.app_footer_links_desc') }}</p>
+                        @include('settings.footer-links', ['name' => 'setting-app-footer-links', 'value' => setting('app-footer-links', [])])
+                    </div>
+
 
                     <div>
                         <label for="setting-app-custom-head" class="setting-list-label">{{ trans('settings.app_custom_html') }}</label>
index e635455bfd93a359ae2d3352b716fd9dc399085d..c088bad892aa4e25bd76d7d2f90e0908f21c94d5 100644 (file)
@@ -2,7 +2,7 @@
 
 <div class="form-group title-input">
     <label for="name">{{ trans('common.name') }}</label>
-    @include('form.text', ['name' => 'name'])
+    @include('form.text', ['name' => 'name', 'autofocus' => true])
 </div>
 
 <div class="form-group description-input">
index 46432c1b92594372b196fdb35fec9951833fffa9..431fa54cc260a406d350b4d83f47c7b53eaa2775 100644 (file)
@@ -9,19 +9,32 @@
     </div>
 
     <main class="card content-wrap">
-        <h1 class="break-text">{{$shelf->name}}</h1>
+
+        <div class="flex-container-row wrap v-center">
+            <h1 class="flex fit-content break-text">{{ $shelf->name }}</h1>
+            <div class="flex"></div>
+            <div class="flex fit-content text-m-right my-m ml-m">
+                @include('partials.sort', ['options' => [
+                    'default' => trans('common.sort_default'),
+                    'name' => trans('common.sort_name'),
+                    'created_at' => trans('common.sort_created_at'),
+                    'updated_at' => trans('common.sort_updated_at'),
+                ], 'order' => $order, 'sort' => $sort, 'type' => 'shelf_books'])
+            </div>
+        </div>
+
         <div class="book-content">
             <p class="text-muted">{!! nl2br(e($shelf->description)) !!}</p>
-            @if(count($shelf->visibleBooks) > 0)
+            @if(count($sortedVisibleShelfBooks) > 0)
                 @if($view === 'list')
                     <div class="entity-list">
-                        @foreach($shelf->visibleBooks as $book)
+                        @foreach($sortedVisibleShelfBooks as $book)
                             @include('books.list-item', ['book' => $book])
                         @endforeach
                     </div>
                 @else
                     <div class="grid third">
-                        @foreach($shelf->visibleBooks as $key => $book)
+                        @foreach($sortedVisibleShelfBooks as $book)
                             @include('partials.entity-grid-item', ['entity' => $book])
                         @endforeach
                     </div>
                 </a>
             @endif
 
+            @if(signedInUser())
+                <hr class="primary-background">
+                @include('partials.entity-favourite-action', ['entity' => $shelf])
+            @endif
+
         </div>
     </div>
 @stop
index 71c546964ef6fb31d89a553256654324998de35b..dc4115f291f1a874aa794cb7525a89a6db6fd39d 100644 (file)
@@ -1,21 +1,31 @@
 @extends('base')
 
 @section('body-class', 'tri-layout')
+@section('content-components', 'tri-layout')
 
 @section('content')
 
-    <div class="tri-layout-mobile-tabs text-primary print-hidden">
+    <div class="tri-layout-mobile-tabs print-hidden">
         <div class="grid half no-break no-gap">
-            <div class="tri-layout-mobile-tab px-m py-s" tri-layout-mobile-tab="info">
+            <button type="button"
+                    refs="tri-layout@tab"
+                    data-tab="info"
+                    aria-label="{{ trans('common.tab_info_label') }}"
+                    class="tri-layout-mobile-tab px-m py-m text-primary">
                 {{ trans('common.tab_info') }}
-            </div>
-            <div class="tri-layout-mobile-tab px-m py-s active" tri-layout-mobile-tab="content">
+            </button>
+            <button type="button"
+                    refs="tri-layout@tab"
+                    data-tab="content"
+                    aria-label="{{ trans('common.tab_content_label') }}"
+                    aria-selected="true"
+                    class="tri-layout-mobile-tab px-m py-m text-primary active">
                 {{ trans('common.tab_content') }}
-            </div>
+            </button>
         </div>
     </div>
 
-    <div class="tri-layout-container" tri-layout @yield('container-attrs') >
+    <div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
 
         <div class="tri-layout-left print-hidden pt-m" id="sidebar">
             <aside class="tri-layout-left-contents">
index aba6f5cc1f6de6cee8da0beec9b64a51fc563eab..7b1d38d340b1eb9cdf789806cf9a0421486be0f3 100644 (file)
@@ -20,7 +20,7 @@
                     <p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p>
                 </div>
                 <div>
-                    @include('components.user-select', ['name' => 'new_owner_id', 'user' => null])
+                    @include('components.user-select', ['name' => 'new_owner_id', 'user' => null, 'compact' => false])
                 </div>
             </div>
 
index df3d06c2f34f232b17fe033472d4078f293285cb..763c387d4601bca12bba95b71dd9ba2cfecb200f 100644 (file)
@@ -74,7 +74,7 @@
             <div class="grid half mt-m gap-xl">
                 <div>
                     <label for="password">{{ trans('auth.password') }}</label>
-                    @include('form.password', ['name' => 'password'])
+                    @include('form.password', ['name' => 'password', 'autocomplete' => 'new-password'])
                 </div>
                 <div>
                     <label for="password-confirm">{{ trans('auth.password_confirm') }}</label>
index 6bc229ec682a1fac2ae1599b4e1bfe0717c406a2..5eef511753dc16e41efb10fbff17f3dc9b742fd1 100644 (file)
@@ -9,11 +9,11 @@
 
         <main class="card content-wrap">
 
-            <div class="grid right-focus v-center">
+            <div class="flex-container-row wrap justify-space-between items-center">
                 <h1 class="list-heading">{{ trans('settings.users') }}</h1>
 
-                <div class="text-right">
-                    <div class="block inline mr-s">
+                <div>
+                    <div class="block inline mr-xs">
                         <form method="get" action="{{ url("/settings/users") }}">
                             @foreach(collect($listDetails)->except('search') as $name => $val)
                                 <input type="hidden" name="{{ $name }}" value="{{ $val }}">
index 64d08e165a9659c0bcd09111a8db6fd37a33269a..59a6eddc630db85801523895be76970be027520b 100644 (file)
@@ -99,7 +99,7 @@ Route::group(['middleware' => 'auth'], function () {
     });
 
     // User Profile routes
-    Route::get('/user/{userId}', 'UserController@showProfilePage');
+    Route::get('/user/{slug}', 'UserProfileController@show');
 
     // Image routes
     Route::get('/images/gallery', 'Images\GalleryImageController@list');
@@ -152,9 +152,15 @@ Route::group(['middleware' => 'auth'], function () {
     // User Search
     Route::get('/search/users/select', 'UserSearchController@forSelect');
 
+    // Template System
     Route::get('/templates', 'PageTemplateController@list');
     Route::get('/templates/{templateId}', 'PageTemplateController@get');
 
+    // Favourites
+    Route::get('/favourites', 'FavouriteController@index');
+    Route::post('/favourites/add', 'FavouriteController@add');
+    Route::post('/favourites/remove', 'FavouriteController@remove');
+
     // Other Pages
     Route::get('/', 'HomeController@index');
     Route::get('/home', 'HomeController@index');
@@ -217,12 +223,12 @@ Route::group(['middleware' => 'auth'], function () {
 });
 
 // Social auth routes
-Route::get('/login/service/{socialDriver}', 'Auth\SocialController@getSocialLogin');
-Route::get('/login/service/{socialDriver}/callback', 'Auth\SocialController@socialCallback');
+Route::get('/login/service/{socialDriver}', 'Auth\SocialController@login');
+Route::get('/login/service/{socialDriver}/callback', 'Auth\SocialController@callback');
 Route::group(['middleware' => 'auth'], function () {
-    Route::get('/login/service/{socialDriver}/detach', 'Auth\SocialController@detachSocialAccount');
+    Route::get('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach');
 });
-Route::get('/register/service/{socialDriver}', 'Auth\SocialController@socialRegister');
+Route::get('/register/service/{socialDriver}', 'Auth\SocialController@register');
 
 // Login/Logout routes
 Route::get('/login', 'Auth\LoginController@getLogin');
@@ -254,4 +260,4 @@ Route::post('/password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail
 Route::get('/password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
 Route::post('/password/reset', 'Auth\ResetPasswordController@reset');
 
-Route::fallback('HomeController@getNotFound');
\ No newline at end of file
+Route::fallback('HomeController@getNotFound')->name('fallback');
\ No newline at end of file
index 3dc6fd7c2ecfd46b19cde94ba81f04da4a8f5d9a..55a458786b2e2f4b4f7eacc56d8c089c65b80192 100644 (file)
@@ -4,6 +4,7 @@ use BookStack\Actions\Activity;
 use BookStack\Actions\ActivityService;
 use BookStack\Actions\ActivityType;
 use BookStack\Auth\UserRepo;
+use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Tools\TrashCan;
 use BookStack\Entities\Models\Page;
 use BookStack\Entities\Repos\PageRepo;
@@ -117,4 +118,26 @@ class AuditLogTest extends TestCase
         $resp->assertDontSeeText($page->name);
     }
 
+    public function test_user_filter()
+    {
+        $admin = $this->getAdmin();
+        $editor = $this->getEditor();
+        $this->actingAs($admin);
+        $page = Page::query()->first();
+        $this->activityService->addForEntity($page, ActivityType::PAGE_CREATE);
+
+        $this->actingAs($editor);
+        $chapter = Chapter::query()->first();
+        $this->activityService->addForEntity($chapter, ActivityType::CHAPTER_UPDATE);
+
+        $resp = $this->actingAs($admin)->get('settings/audit?user=' . $admin->id);
+        $resp->assertSeeText($page->name);
+        $resp->assertDontSeeText($chapter->name);
+
+        $resp = $this->actingAs($admin)->get('settings/audit?user=' . $editor->id);
+        $resp->assertSeeText($chapter->name);
+        $resp->assertDontSeeText($page->name);
+
+    }
+
 }
\ No newline at end of file
index a0de7f803505860647964c68a917c2d49833cf0d..f88fc19044286d5c3e4b937745cff0511437ce13 100644 (file)
@@ -9,6 +9,7 @@ use BookStack\Settings\SettingService;
 use DB;
 use Hash;
 use Illuminate\Support\Facades\Notification;
+use Illuminate\Support\Str;
 use Tests\BrowserKitTest;
 
 class AuthTest extends BrowserKitTest
@@ -221,6 +222,7 @@ class AuthTest extends BrowserKitTest
 
     public function test_user_creation()
     {
+        /** @var User $user */
         $user = factory(User::class)->make();
         $adminRole = Role::getRole('admin');
 
@@ -234,8 +236,11 @@ class AuthTest extends BrowserKitTest
             ->type($user->password, '#password-confirm')
             ->press('Save')
             ->seePageIs('/settings/users')
-            ->seeInDatabase('users', $user->toArray())
+            ->seeInDatabase('users', $user->only(['name', 'email']))
             ->see($user->name);
+
+        $user->refresh();
+        $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
     }
 
     public function test_user_updating()
@@ -252,6 +257,9 @@ class AuthTest extends BrowserKitTest
             ->seePageIs('/settings/users')
             ->seeInDatabase('users', ['id' => $user->id, 'name' => 'Barry Scott', 'password' => $password])
             ->notSeeInDatabase('users', ['name' => $user->name]);
+
+        $user->refresh();
+        $this->assertStringStartsWith(Str::slug($user->name), $user->slug);
     }
 
     public function test_user_password_update()
index 3cb39ca2c59c63a8315a76c44ebbaed1881bbfe2..a04ed4400d42584cc2bd773a07ac0be0e23705dc 100644 (file)
@@ -34,12 +34,21 @@ class LdapTest extends BrowserKitTest
             'services.ldap.user_filter' => '(&(uid=${user}))',
             'services.ldap.follow_referrals' => false,
             'services.ldap.tls_insecure' => false,
+            'services.ldap.thumbnail_attribute' => null,
         ]);
         $this->mockLdap = \Mockery::mock(Ldap::class);
         $this->app[Ldap::class] = $this->mockLdap;
         $this->mockUser = factory(User::class)->make();
     }
 
+    protected function runFailedAuthLogin()
+    {
+        $this->commonLdapMocks(1, 1, 1, 1, 1);
+        $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
+            ->andReturn(['count' => 0]);
+        $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
+    }
+
     protected function mockEscapes($times = 1)
     {
         $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function($val) {
@@ -550,6 +559,22 @@ class LdapTest extends BrowserKitTest
         ]);
     }
 
+    public function test_start_tls_called_if_option_set()
+    {
+        config()->set(['services.ldap.start_tls' => true]);
+        $this->mockLdap->shouldReceive('startTls')->once()->andReturn(true);
+        $this->runFailedAuthLogin();
+    }
+
+    public function test_connection_fails_if_tls_fails()
+    {
+        config()->set(['services.ldap.start_tls' => true]);
+        $this->mockLdap->shouldReceive('startTls')->once()->andReturn(false);
+        $this->commonLdapMocks(1, 1, 0, 0, 0);
+        $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
+        $this->assertResponseStatus(500);
+    }
+
     public function test_ldap_attributes_can_be_binary_decoded_if_marked()
     {
         config()->set(['services.ldap.id_attribute' => 'BIN;uid']);
@@ -640,12 +665,31 @@ class LdapTest extends BrowserKitTest
     {
         $log = $this->withTestLogger();
         config()->set(['logging.failed_login.message' => 'Failed login for %u']);
+        $this->runFailedAuthLogin();
+        $this->assertTrue($log->hasWarningThatContains('Failed login for timmyjenkins'));
+    }
 
-        $this->commonLdapMocks(1, 1, 1, 1, 1);
+    public function test_thumbnail_attribute_used_as_user_avatar_if_configured()
+    {
+        config()->set(['services.ldap.thumbnail_attribute' => 'jpegPhoto']);
+
+        $this->commonLdapMocks(1, 1, 1, 2, 1);
+        $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
-            ->andReturn(['count' => 0]);
+            ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+            ->andReturn(['count' => 1, 0 => [
+                'cn' => [$this->mockUser->name],
+                'dn' => $ldapDn,
+                'jpegphoto' => [base64_decode('/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8Q
+EBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=')],
+                'mail' => [$this->mockUser->email]
+            ]]);
 
-        $this->post('/login', ['username' => 'timmyjenkins', 'password' => 'cattreedog']);
-        $this->assertTrue($log->hasWarningThatContains('Failed login for timmyjenkins'));
+        $this->mockUserLogin()
+            ->seePageIs('/');
+
+        $user = User::query()->where('email', '=' , $this->mockUser->email)->first();
+        $this->assertNotNull($user->avatar);
+        $this->assertEquals('8c90748342f19b195b9c6b4eff742ded', md5_file(public_path($user->avatar->path)));
     }
 }
index 58c02b47188a625b83a5aeb3d0ad80c5f4f81de0..b6b02e2f76d8aef7a65493bfda7449b8ca5271dd 100644 (file)
@@ -28,6 +28,7 @@ class Saml2Test extends TestCase
             'saml2.autoload_from_metadata' => false,
             'saml2.onelogin.idp.x509cert' => $this->testCert,
             'saml2.onelogin.debug' => false,
+            'saml2.onelogin.security.requestedAuthnContext' => true,
         ]);
     }
 
@@ -328,6 +329,40 @@ class Saml2Test extends TestCase
         });
     }
 
+    public function test_login_request_contains_expected_default_authncontext()
+    {
+        $authReq = $this->getAuthnRequest();
+        $this->assertStringContainsString('samlp:RequestedAuthnContext Comparison="exact"', $authReq);
+        $this->assertStringContainsString('<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>', $authReq);
+    }
+
+    public function test_false_idp_authncontext_option_does_not_pass_authncontext_in_saml_request()
+    {
+        config()->set(['saml2.onelogin.security.requestedAuthnContext' => false]);
+        $authReq = $this->getAuthnRequest();
+        $this->assertStringNotContainsString('samlp:RequestedAuthnContext', $authReq);
+        $this->assertStringNotContainsString('<saml:AuthnContextClassRef>', $authReq);
+    }
+
+    public function test_array_idp_authncontext_option_passes_value_as_authncontextclassref_in_request()
+    {
+        config()->set(['saml2.onelogin.security.requestedAuthnContext' => ['urn:federation:authentication:windows', 'urn:federation:authentication:linux']]);
+        $authReq = $this->getAuthnRequest();
+        $this->assertStringContainsString('samlp:RequestedAuthnContext', $authReq);
+        $this->assertStringContainsString('<saml:AuthnContextClassRef>urn:federation:authentication:windows</saml:AuthnContextClassRef>', $authReq);
+        $this->assertStringContainsString('<saml:AuthnContextClassRef>urn:federation:authentication:linux</saml:AuthnContextClassRef>', $authReq);
+    }
+
+    protected function getAuthnRequest(): string
+    {
+        $req = $this->post('/saml2/login');
+        $location = $req->headers->get('Location');
+        $query = explode('?', $location)[1];
+        $params = [];
+        parse_str($query, $params);
+        return gzinflate(base64_decode($params['SAMLRequest']));
+    }
+
     protected function withGet(array $options, callable $callback)
     {
         return $this->withGlobal($_GET, $options, $callback);
index d448b567e5ef1462bf168500bc2dc6b8ed1df2f2..4369d8b7abca6faa84780f73c13bdd4176176aac 100644 (file)
@@ -17,8 +17,7 @@ class SocialAuthTest extends TestCase
         $this->setSettings(['registration-enabled' => 'true']);
         config(['GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']);
 
-        $mockSocialite = Mockery::mock(Factory::class);
-        $this->app[Factory::class] = $mockSocialite;
+        $mockSocialite = $this->mock(Factory::class);
         $mockSocialDriver = Mockery::mock(Provider::class);
         $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class);
 
@@ -46,8 +45,7 @@ class SocialAuthTest extends TestCase
             'APP_URL' => 'http://localhost'
         ]);
 
-        $mockSocialite = Mockery::mock(Factory::class);
-        $this->app[Factory::class] = $mockSocialite;
+        $mockSocialite = $this->mock(Factory::class);
         $mockSocialDriver = Mockery::mock(Provider::class);
         $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class);
 
@@ -93,8 +91,7 @@ class SocialAuthTest extends TestCase
         ]);
 
         $user = factory(User::class)->make();
-        $mockSocialite = Mockery::mock(Factory::class);
-        $this->app[Factory::class] = $mockSocialite;
+        $mockSocialite = $this->mock(Factory::class);
         $mockSocialDriver = Mockery::mock(Provider::class);
         $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class);
 
@@ -132,8 +129,7 @@ class SocialAuthTest extends TestCase
         ]);
 
         $user = factory(User::class)->make();
-        $mockSocialite = Mockery::mock(Factory::class);
-        $this->app[Factory::class] = $mockSocialite;
+        $mockSocialite = $this->mock(Factory::class);
         $mockSocialDriver = Mockery::mock(Provider::class);
         $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class);
 
@@ -169,8 +165,7 @@ class SocialAuthTest extends TestCase
         $this->setSettings(['registration-enabled' => 'true']);
         config(['GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']);
 
-        $mockSocialite = Mockery::mock(Factory::class);
-        $this->app[Factory::class] = $mockSocialite;
+        $mockSocialite = $this->mock(Factory::class);
         $mockSocialDriver = Mockery::mock(Provider::class);
         $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class);
 
index f2a1d0e78177e94330515b37eb924e19770c9c95..b6f521eaa23196466114b27a04d5cca41b885115 100644 (file)
@@ -18,13 +18,15 @@ class UserInviteTest extends TestCase
         Notification::fake();
         $admin = $this->getAdmin();
 
-        $this->actingAs($admin)->post('/settings/users/create', [
+        $email = Str::random(16) . '@example.com';
+        $resp = $this->actingAs($admin)->post('/settings/users/create', [
             'name' => 'Barry',
-            'email' => '[email protected]',
+            'email' => $email,
             'send_invite' => 'true',
         ]);
+        $resp->assertRedirect('/settings/users');
 
-        $newUser = User::query()->where('email', '=', '[email protected]')->orderBy('id', 'desc')->first();
+        $newUser = User::query()->where('email', '=', $email)->orderBy('id', 'desc')->first();
 
         Notification::assertSentTo($newUser, UserInvite::class);
         $this->assertDatabaseHas('user_invites', [
index 6c332a98469c4f5141972ee0b7e562b2f74b4ab2..45e20b5e284aad7875b9b8e05abe534efea523de 100644 (file)
@@ -4,7 +4,6 @@ use BookStack\Auth\User;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Models\Entity;
-use BookStack\Auth\Role;
 use BookStack\Auth\Permissions\PermissionService;
 use BookStack\Entities\Models\Page;
 use BookStack\Settings\SettingService;
@@ -120,7 +119,7 @@ abstract class BrowserKitTest extends TestCase
      */
     protected function seeInNthElement($element, $position, $text, $negate = false)
     {
-        $method = $negate ? 'assertNotRegExp' : 'assertRegExp';
+        $method = $negate ? 'assertDoesNotMatchRegularExpression' : 'assertMatchesRegularExpression';
 
         $rawPattern = preg_quote($text, '/');
 
diff --git a/tests/Commands/AddAdminCommandTest.php b/tests/Commands/AddAdminCommandTest.php
new file mode 100644 (file)
index 0000000..6b03c86
--- /dev/null
@@ -0,0 +1,25 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Auth\User;
+use Tests\TestCase;
+
+class AddAdminCommandTest extends TestCase
+{
+    public function test_add_admin_command()
+    {
+        $exitCode = \Artisan::call('bookstack:create-admin', [
+            '--email' => '[email protected]',
+            '--name' => 'Admin Test',
+            '--password' => 'testing-4',
+        ]);
+        $this->assertTrue($exitCode === 0, 'Command executed successfully');
+
+        $this->assertDatabaseHas('users', [
+            'email' => '[email protected]',
+            'name' => 'Admin Test'
+        ]);
+
+        $this->assertTrue(User::query()->where('email', '=', '[email protected]')->first()->hasSystemRole('admin'), 'User has admin role as expected');
+        $this->assertTrue(\Auth::attempt(['email' => '[email protected]', 'password' => 'testing-4']), 'Password stored as expected');
+    }
+}
\ No newline at end of file
diff --git a/tests/Commands/ClearActivityCommandTest.php b/tests/Commands/ClearActivityCommandTest.php
new file mode 100644 (file)
index 0000000..751a165
--- /dev/null
@@ -0,0 +1,33 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Entities\Models\Page;
+use Illuminate\Support\Facades\DB;
+use Tests\TestCase;
+
+class ClearActivityCommandTest extends TestCase
+{
+    public function test_clear_activity_command()
+    {
+        $this->asEditor();
+        $page = Page::first();
+        \Activity::addForEntity($page, ActivityType::PAGE_UPDATE);
+
+        $this->assertDatabaseHas('activities', [
+            'type' => 'page_update',
+            'entity_id' => $page->id,
+            'user_id' => $this->getEditor()->id
+        ]);
+
+
+        DB::rollBack();
+        $exitCode = \Artisan::call('bookstack:clear-activity');
+        DB::beginTransaction();
+        $this->assertTrue($exitCode === 0, 'Command executed successfully');
+
+
+        $this->assertDatabaseMissing('activities', [
+            'type' => 'page_update'
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/tests/Commands/ClearRevisionsCommandTest.php b/tests/Commands/ClearRevisionsCommandTest.php
new file mode 100644 (file)
index 0000000..e0293fa
--- /dev/null
@@ -0,0 +1,47 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Entities\Models\Page;
+use BookStack\Entities\Repos\PageRepo;
+use Illuminate\Support\Facades\Artisan;
+use Tests\TestCase;
+
+class ClearRevisionsCommandTest extends TestCase
+{
+    public function test_clear_revisions_command()
+    {
+        $this->asEditor();
+        $pageRepo = app(PageRepo::class);
+        $page = Page::first();
+        $pageRepo->update($page, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
+        $pageRepo->updatePageDraft($page, ['name' => 'updated page', 'html' => '<p>new content in draft</p>', 'summary' => 'page revision testing']);
+
+        $this->assertDatabaseHas('page_revisions', [
+            'page_id' => $page->id,
+            'type' => 'version'
+        ]);
+        $this->assertDatabaseHas('page_revisions', [
+            'page_id' => $page->id,
+            'type' => 'update_draft'
+        ]);
+
+        $exitCode = Artisan::call('bookstack:clear-revisions');
+        $this->assertTrue($exitCode === 0, 'Command executed successfully');
+
+        $this->assertDatabaseMissing('page_revisions', [
+            'page_id' => $page->id,
+            'type' => 'version'
+        ]);
+        $this->assertDatabaseHas('page_revisions', [
+            'page_id' => $page->id,
+            'type' => 'update_draft'
+        ]);
+
+        $exitCode = Artisan::call('bookstack:clear-revisions', ['--all' => true]);
+        $this->assertTrue($exitCode === 0, 'Command executed successfully');
+
+        $this->assertDatabaseMissing('page_revisions', [
+            'page_id' => $page->id,
+            'type' => 'update_draft'
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/tests/Commands/ClearViewsCommandTest.php b/tests/Commands/ClearViewsCommandTest.php
new file mode 100644 (file)
index 0000000..04665ad
--- /dev/null
@@ -0,0 +1,32 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Entities\Models\Page;
+use Illuminate\Support\Facades\DB;
+use Tests\TestCase;
+
+class ClearViewsCommandTest extends TestCase
+{
+
+    public function test_clear_views_command()
+    {
+        $this->asEditor();
+        $page = Page::first();
+
+        $this->get($page->getUrl());
+
+        $this->assertDatabaseHas('views', [
+            'user_id' => $this->getEditor()->id,
+            'viewable_id' => $page->id,
+            'views' => 1
+        ]);
+
+        DB::rollBack();
+        $exitCode = \Artisan::call('bookstack:clear-views');
+        DB::beginTransaction();
+        $this->assertTrue($exitCode === 0, 'Command executed successfully');
+
+        $this->assertDatabaseMissing('views', [
+            'user_id' => $this->getEditor()->id
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/tests/Commands/CopyShelfPermissionsCommandTest.php b/tests/Commands/CopyShelfPermissionsCommandTest.php
new file mode 100644 (file)
index 0000000..87199bd
--- /dev/null
@@ -0,0 +1,54 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Entities\Models\Bookshelf;
+use Tests\TestCase;
+
+class CopyShelfPermissionsCommandTest extends TestCase
+{
+    public function test_copy_shelf_permissions_command_shows_error_when_no_required_option_given()
+    {
+        $this->artisan('bookstack:copy-shelf-permissions')
+            ->expectsOutput('Either a --slug or --all option must be provided.')
+            ->assertExitCode(0);
+    }
+
+    public function test_copy_shelf_permissions_command_using_slug()
+    {
+        $shelf = Bookshelf::first();
+        $child = $shelf->books()->first();
+        $editorRole = $this->getEditor()->roles()->first();
+        $this->assertFalse(boolval($child->restricted), "Child book should not be restricted by default");
+        $this->assertTrue($child->permissions()->count() === 0, "Child book should have no permissions by default");
+
+        $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]);
+        $this->artisan('bookstack:copy-shelf-permissions', [
+            '--slug' => $shelf->slug,
+        ]);
+        $child = $shelf->books()->first();
+
+        $this->assertTrue(boolval($child->restricted), "Child book should now be restricted");
+        $this->assertTrue($child->permissions()->count() === 2, "Child book should have copied permissions");
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'view', 'role_id' => $editorRole->id]);
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
+    }
+
+    public function test_copy_shelf_permissions_command_using_all()
+    {
+        $shelf = Bookshelf::query()->first();
+        Bookshelf::query()->where('id', '!=', $shelf->id)->delete();
+        $child = $shelf->books()->first();
+        $editorRole = $this->getEditor()->roles()->first();
+        $this->assertFalse(boolval($child->restricted), "Child book should not be restricted by default");
+        $this->assertTrue($child->permissions()->count() === 0, "Child book should have no permissions by default");
+
+        $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]);
+        $this->artisan('bookstack:copy-shelf-permissions --all')
+            ->expectsQuestion('Permission settings for all shelves will be cascaded. Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. Are you sure you want to proceed?', 'y');
+        $child = $shelf->books()->first();
+
+        $this->assertTrue(boolval($child->restricted), "Child book should now be restricted");
+        $this->assertTrue($child->permissions()->count() === 2, "Child book should have copied permissions");
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'view', 'role_id' => $editorRole->id]);
+        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
+    }
+}
\ No newline at end of file
diff --git a/tests/Commands/RegenerateCommentContentCommandTest.php b/tests/Commands/RegenerateCommentContentCommandTest.php
new file mode 100644 (file)
index 0000000..1deeaa7
--- /dev/null
@@ -0,0 +1,29 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Actions\Comment;
+use Tests\TestCase;
+
+class RegenerateCommentContentCommandTest extends TestCase
+{
+    public function test_regenerate_comment_content_command()
+    {
+        Comment::query()->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' => "<p>some_fresh_content</p>\n",
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/tests/Commands/RegeneratePermissionsCommandTest.php b/tests/Commands/RegeneratePermissionsCommandTest.php
new file mode 100644 (file)
index 0000000..d5b34ba
--- /dev/null
@@ -0,0 +1,24 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Auth\Permissions\JointPermission;
+use BookStack\Entities\Models\Page;
+use Illuminate\Support\Facades\DB;
+use Tests\TestCase;
+
+class RegeneratePermissionsCommandTest extends TestCase
+{
+    public function test_regen_permissions_command()
+    {
+        \DB::rollBack();
+        JointPermission::query()->truncate();
+        $page = Page::first();
+
+        $this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
+
+        $exitCode = \Artisan::call('bookstack:regenerate-permissions');
+        $this->assertTrue($exitCode === 0, 'Command executed successfully');
+        DB::beginTransaction();
+
+        $this->assertDatabaseHas('joint_permissions', ['entity_id' => $page->id]);
+    }
+}
\ No newline at end of file
diff --git a/tests/Commands/UpdateUrlCommandTest.php b/tests/Commands/UpdateUrlCommandTest.php
new file mode 100644 (file)
index 0000000..7043ce0
--- /dev/null
@@ -0,0 +1,59 @@
+<?php namespace Tests\Commands;
+
+use BookStack\Entities\Models\Page;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Tests\TestCase;
+
+class UpdateUrlCommandTest extends TestCase
+{
+    public function test_command_updates_page_content()
+    {
+        $page = Page::query()->first();
+        $page->html = '<a href="https://example.com/donkeys"></a>';
+        $page->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');
+
+        $this->assertDatabaseHas('pages', [
+            'id' => $page->id,
+            'html' => '<a href="https://cats.example.com/donkeys"></a>'
+        ]);
+    }
+
+    public function test_command_requires_valid_url()
+    {
+        $badUrlMessage = "The given urls are expected to be full urls starting with http:// or https://";
+        $this->artisan('bookstack:update-url //example.com https://cats.example.com')->expectsOutput($badUrlMessage);
+        $this->artisan('bookstack:update-url https://example.com htts://cats.example.com')->expectsOutput($badUrlMessage);
+        $this->artisan('bookstack:update-url example.com https://cats.example.com')->expectsOutput($badUrlMessage);
+
+        $this->expectException(RuntimeException::class);
+        $this->artisan('bookstack:update-url https://cats.example.com');
+    }
+
+    public function test_command_updates_settings()
+    {
+        setting()->put('my-custom-item', 'https://example.com/donkey/cat');
+        $this->runUpdate('https://example.com', 'https://cats.example.com');
+
+        $settingVal = setting('my-custom-item');
+        $this->assertEquals('https://cats.example.com/donkey/cat', $settingVal);
+    }
+
+    public function test_command_updates_array_settings()
+    {
+        setting()->put('my-custom-array-item', [['name' => 'a https://example.com/donkey/cat url']]);
+        $this->runUpdate('https://example.com', 'https://cats.example.com');
+        $settingVal = setting('my-custom-array-item');
+        $this->assertEquals('a https://cats.example.com/donkey/cat url', $settingVal[0]['name']);
+    }
+
+    protected function runUpdate(string $oldUrl, string $newUrl)
+    {
+        $this->artisan("bookstack:update-url {$oldUrl} {$newUrl}")
+            ->expectsQuestion("This will search for \"{$oldUrl}\" in your database and replace it with  \"{$newUrl}\".\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');
+    }
+}
\ No newline at end of file
diff --git a/tests/CommandsTest.php b/tests/CommandsTest.php
deleted file mode 100644 (file)
index 8c6ea84..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-<?php namespace Tests;
-
-use BookStack\Actions\ActivityType;
-use BookStack\Actions\Comment;
-use BookStack\Actions\CommentRepo;
-use BookStack\Auth\Permissions\JointPermission;
-use BookStack\Entities\Models\Bookshelf;
-use BookStack\Entities\Models\Page;
-use BookStack\Auth\User;
-use BookStack\Entities\Repos\PageRepo;
-use Symfony\Component\Console\Exception\RuntimeException;
-
-class CommandsTest extends TestCase
-{
-
-    public function test_clear_views_command()
-    {
-        $this->asEditor();
-        $page = Page::first();
-
-        $this->get($page->getUrl());
-
-        $this->assertDatabaseHas('views', [
-            'user_id' => $this->getEditor()->id,
-            'viewable_id' => $page->id,
-            'views' => 1
-        ]);
-
-        $exitCode = \Artisan::call('bookstack:clear-views');
-        $this->assertTrue($exitCode === 0, 'Command executed successfully');
-
-        $this->assertDatabaseMissing('views', [
-            'user_id' => $this->getEditor()->id
-        ]);
-    }
-
-    public function test_clear_activity_command()
-    {
-        $this->asEditor();
-        $page = Page::first();
-        \Activity::addForEntity($page, ActivityType::PAGE_UPDATE);
-
-        $this->assertDatabaseHas('activities', [
-            'type' => 'page_update',
-            'entity_id' => $page->id,
-            'user_id' => $this->getEditor()->id
-        ]);
-
-        $exitCode = \Artisan::call('bookstack:clear-activity');
-        $this->assertTrue($exitCode === 0, 'Command executed successfully');
-
-
-        $this->assertDatabaseMissing('activities', [
-            'type' => 'page_update'
-        ]);
-    }
-
-    public function test_clear_revisions_command()
-    {
-        $this->asEditor();
-        $pageRepo = app(PageRepo::class);
-        $page = Page::first();
-        $pageRepo->update($page, ['name' => 'updated page', 'html' => '<p>new content</p>', 'summary' => 'page revision testing']);
-        $pageRepo->updatePageDraft($page, ['name' => 'updated page', 'html' => '<p>new content in draft</p>', 'summary' => 'page revision testing']);
-
-        $this->assertDatabaseHas('page_revisions', [
-            'page_id' => $page->id,
-            'type' => 'version'
-        ]);
-        $this->assertDatabaseHas('page_revisions', [
-            'page_id' => $page->id,
-            'type' => 'update_draft'
-        ]);
-
-        $exitCode = \Artisan::call('bookstack:clear-revisions');
-        $this->assertTrue($exitCode === 0, 'Command executed successfully');
-
-        $this->assertDatabaseMissing('page_revisions', [
-            'page_id' => $page->id,
-            'type' => 'version'
-        ]);
-        $this->assertDatabaseHas('page_revisions', [
-            'page_id' => $page->id,
-            'type' => 'update_draft'
-        ]);
-
-        $exitCode = \Artisan::call('bookstack:clear-revisions', ['--all' => true]);
-        $this->assertTrue($exitCode === 0, 'Command executed successfully');
-
-        $this->assertDatabaseMissing('page_revisions', [
-            'page_id' => $page->id,
-            'type' => 'update_draft'
-        ]);
-    }
-
-    public function test_regen_permissions_command()
-    {
-        JointPermission::query()->truncate();
-        $page = Page::first();
-
-        $this->assertDatabaseMissing('joint_permissions', ['entity_id' => $page->id]);
-
-        $exitCode = \Artisan::call('bookstack:regenerate-permissions');
-        $this->assertTrue($exitCode === 0, 'Command executed successfully');
-
-        $this->assertDatabaseHas('joint_permissions', ['entity_id' => $page->id]);
-    }
-
-    public function test_add_admin_command()
-    {
-        $exitCode = \Artisan::call('bookstack:create-admin', [
-            '--email' => '[email protected]',
-            '--name' => 'Admin Test',
-            '--password' => 'testing-4',
-        ]);
-        $this->assertTrue($exitCode === 0, 'Command executed successfully');
-
-        $this->assertDatabaseHas('users', [
-            'email' => '[email protected]',
-            'name' => 'Admin Test'
-        ]);
-
-        $this->assertTrue(User::where('email', '=', '[email protected]')->first()->hasSystemRole('admin'), 'User has admin role as expected');
-        $this->assertTrue(\Auth::attempt(['email' => '[email protected]', 'password' => 'testing-4']), 'Password stored as expected');
-    }
-
-    public function test_copy_shelf_permissions_command_shows_error_when_no_required_option_given()
-    {
-        $this->artisan('bookstack:copy-shelf-permissions')
-            ->expectsOutput('Either a --slug or --all option must be provided.')
-            ->assertExitCode(0);
-    }
-
-    public function test_copy_shelf_permissions_command_using_slug()
-    {
-        $shelf = Bookshelf::first();
-        $child = $shelf->books()->first();
-        $editorRole = $this->getEditor()->roles()->first();
-        $this->assertFalse(boolval($child->restricted), "Child book should not be restricted by default");
-        $this->assertTrue($child->permissions()->count() === 0, "Child book should have no permissions by default");
-
-        $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]);
-        $this->artisan('bookstack:copy-shelf-permissions', [
-            '--slug' => $shelf->slug,
-        ]);
-        $child = $shelf->books()->first();
-
-        $this->assertTrue(boolval($child->restricted), "Child book should now be restricted");
-        $this->assertTrue($child->permissions()->count() === 2, "Child book should have copied permissions");
-        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'view', 'role_id' => $editorRole->id]);
-        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
-    }
-
-    public function test_copy_shelf_permissions_command_using_all()
-    {
-        $shelf = Bookshelf::query()->first();
-        Bookshelf::query()->where('id', '!=', $shelf->id)->delete();
-        $child = $shelf->books()->first();
-        $editorRole = $this->getEditor()->roles()->first();
-        $this->assertFalse(boolval($child->restricted), "Child book should not be restricted by default");
-        $this->assertTrue($child->permissions()->count() === 0, "Child book should have no permissions by default");
-
-        $this->setEntityRestrictions($shelf, ['view', 'update'], [$editorRole]);
-        $this->artisan('bookstack:copy-shelf-permissions --all')
-            ->expectsQuestion('Permission settings for all shelves will be cascaded. Books assigned to multiple shelves will receive only the permissions of it\'s last processed shelf. Are you sure you want to proceed?', 'y');
-        $child = $shelf->books()->first();
-
-        $this->assertTrue(boolval($child->restricted), "Child book should now be restricted");
-        $this->assertTrue($child->permissions()->count() === 2, "Child book should have copied permissions");
-        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'view', 'role_id' => $editorRole->id]);
-        $this->assertDatabaseHas('entity_permissions', ['restrictable_id' => $child->id, 'action' => 'update', 'role_id' => $editorRole->id]);
-    }
-
-    public function test_update_url_command_updates_page_content()
-    {
-        $page = Page::query()->first();
-        $page->html = '<a href="https://example.com/donkeys"></a>';
-        $page->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');
-
-        $this->assertDatabaseHas('pages', [
-            'id' => $page->id,
-            'html' => '<a href="https://cats.example.com/donkeys"></a>'
-        ]);
-    }
-
-    public function test_update_url_command_requires_valid_url()
-    {
-        $badUrlMessage = "The given urls are expected to be full urls starting with http:// or https://";
-        $this->artisan('bookstack:update-url //example.com https://cats.example.com')->expectsOutput($badUrlMessage);
-        $this->artisan('bookstack:update-url https://example.com htts://cats.example.com')->expectsOutput($badUrlMessage);
-        $this->artisan('bookstack:update-url example.com https://cats.example.com')->expectsOutput($badUrlMessage);
-
-        $this->expectException(RuntimeException::class);
-        $this->artisan('bookstack:update-url https://cats.example.com');
-    }
-
-    public function test_regenerate_comment_content_command()
-    {
-        Comment::query()->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' => "<p>some_fresh_content</p>\n",
-        ]);
-    }
-}
index 9b3290370c197a14bd1d558a9728e1c96cae89e6..60658f6b2b8a87ecf36609a32f79bda7b46434e6 100644 (file)
@@ -59,7 +59,7 @@ class BookShelfTest extends TestCase
     public function test_book_not_visible_in_shelf_list_view_if_user_cant_view_shelf()
     {
         config()->set([
-            'app.views.bookshelves' => 'list',
+            'setting-defaults.user.bookshelves_view_type' => 'list',
         ]);
         $shelf = Bookshelf::query()->first();
         $book = $shelf->books()->first();
@@ -156,6 +156,47 @@ class BookShelfTest extends TestCase
         $resp->assertDontSee($shelf->getUrl('/permissions'));
     }
 
+    public function test_shelf_view_has_sort_control_that_defaults_to_default()
+    {
+        $shelf = Bookshelf::query()->first();
+        $resp = $this->asAdmin()->get($shelf->getUrl());
+        $resp->assertElementExists('form[action$="change-sort/shelf_books"]');
+        $resp->assertElementContains('form[action$="change-sort/shelf_books"] [aria-haspopup="true"]', 'Default');
+    }
+
+    public function test_shelf_view_sort_takes_action()
+    {
+        $shelf = Bookshelf::query()->whereHas('books')->with('books')->first();
+        $books = Book::query()->take(3)->get(['id', 'name']);
+        $books[0]->fill(['name' => 'bsfsdfsdfsd'])->save();
+        $books[1]->fill(['name' => 'adsfsdfsdfsd'])->save();
+        $books[2]->fill(['name' => 'hdgfgdfg'])->save();
+
+        // Set book ordering
+        $this->asAdmin()->put($shelf->getUrl(), [
+            'books' => $books->implode('id', ','),
+            'tags' => [], 'description' => 'abc', 'name' => 'abc'
+        ]);
+        $this->assertEquals(3, $shelf->books()->count());
+        $shelf->refresh();
+
+        $resp = $this->asEditor()->get($shelf->getUrl());
+        $resp->assertElementContains('.book-content a.grid-card', $books[0]->name, 1);
+        $resp->assertElementNotContains('.book-content a.grid-card', $books[0]->name, 3);
+
+        setting()->putUser($this->getEditor(), 'shelf_books_sort_order', 'desc');
+        $resp = $this->asEditor()->get($shelf->getUrl());
+        $resp->assertElementNotContains('.book-content a.grid-card', $books[0]->name, 1);
+        $resp->assertElementContains('.book-content a.grid-card', $books[0]->name, 3);
+
+        setting()->putUser($this->getEditor(), 'shelf_books_sort_order', 'desc');
+        setting()->putUser($this->getEditor(), 'shelf_books_sort', 'name');
+        $resp = $this->asEditor()->get($shelf->getUrl());
+        $resp->assertElementContains('.book-content a.grid-card', 'hdgfgdfg', 1);
+        $resp->assertElementContains('.book-content a.grid-card', 'bsfsdfsdfsd', 2);
+        $resp->assertElementContains('.book-content a.grid-card', 'adsfsdfsdfsd', 3);
+    }
+
     public function test_shelf_edit()
     {
         $shelf = Bookshelf::first();
index 2b5dc6d749cdbf40e24ae9f93c5f5d446c0fc051..6b53b2cb61705cec2391217737ee83ee6a475a10 100644 (file)
@@ -122,6 +122,7 @@ class EntitySearchTest extends TestCase
         $page = $this->newPage(['name' => 'My new test quaffleachits', 'html' => 'this is about an orange donkey danzorbhsing']);
         $this->asEditor();
         $editorId = $this->getEditor()->id;
+        $editorSlug = $this->getEditor()->slug;
 
         // Viewed filter searches
         $this->get('/search?term=' . urlencode('danzorbhsing {not_viewed_by_me}'))->assertSee($page->name);
@@ -133,16 +134,23 @@ class EntitySearchTest extends TestCase
         // User filters
         $this->get('/search?term=' . urlencode('danzorbhsing {created_by:me}'))->assertDontSee($page->name);
         $this->get('/search?term=' . urlencode('danzorbhsing {updated_by:me}'))->assertDontSee($page->name);
-        $this->get('/search?term=' . urlencode('danzorbhsing {updated_by:'.$editorId.'}'))->assertDontSee($page->name);
+        $this->get('/search?term=' . urlencode('danzorbhsing {owned_by:me}'))->assertDontSee($page->name);
+        $this->get('/search?term=' . urlencode('danzorbhsing {updated_by:'.$editorSlug.'}'))->assertDontSee($page->name);
         $page->created_by = $editorId;
         $page->save();
         $this->get('/search?term=' . urlencode('danzorbhsing {created_by:me}'))->assertSee($page->name);
-        $this->get('/search?term=' . urlencode('danzorbhsing {created_by:'.$editorId.'}'))->assertSee($page->name);
+        $this->get('/search?term=' . urlencode('danzorbhsing {created_by: '.$editorSlug.'}'))->assertSee($page->name);
         $this->get('/search?term=' . urlencode('danzorbhsing {updated_by:me}'))->assertDontSee($page->name);
+        $this->get('/search?term=' . urlencode('danzorbhsing {owned_by:me}'))->assertDontSee($page->name);
         $page->updated_by = $editorId;
         $page->save();
         $this->get('/search?term=' . urlencode('danzorbhsing {updated_by:me}'))->assertSee($page->name);
-        $this->get('/search?term=' . urlencode('danzorbhsing {updated_by:'.$editorId.'}'))->assertSee($page->name);
+        $this->get('/search?term=' . urlencode('danzorbhsing {updated_by:'.$editorSlug.'}'))->assertSee($page->name);
+        $this->get('/search?term=' . urlencode('danzorbhsing {owned_by:me}'))->assertDontSee($page->name);
+        $page->owned_by = $editorId;
+        $page->save();
+        $this->get('/search?term=' . urlencode('danzorbhsing {owned_by:me}'))->assertSee($page->name);
+        $this->get('/search?term=' . urlencode('danzorbhsing {owned_by:'.$editorSlug.'}'))->assertSee($page->name);
 
         // Content filters
         $this->get('/search?term=' . urlencode('{in_name:danzorbhsing}'))->assertDontSee($page->name);
index 3a363e2b87bfeaaa7424946f49acff2556554c7d..52f9a3ae29cdba5e26ef6c13b85abf3bc359620a 100644 (file)
@@ -162,7 +162,7 @@ class EntityTest extends BrowserKitTest
             ->press('Save Book');
 
         $expectedPattern = '/\/books\/my-first-book-[0-9a-zA-Z]{3}/';
-        $this->assertRegExp($expectedPattern, $this->currentUri, "Did not land on expected page [$expectedPattern].\n");
+        $this->assertMatchesRegularExpression($expectedPattern, $this->currentUri, "Did not land on expected page [$expectedPattern].\n");
 
         $book = Book::where('slug', '=', 'my-first-book')->first();
         return $book;
index 1e44f015a5a0b69f8520c9227b971e79f17c0b63..482e82ae6d4eb18ad84a6dee2ff52604e299089c 100644 (file)
@@ -1,6 +1,6 @@
 <?php namespace Tests\Entity;
 
-
+use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Models\Page;
 use Illuminate\Support\Facades\Storage;
@@ -151,6 +151,16 @@ class ExportTest extends TestCase
         $resp->assertDontSee($page->updated_at->diffForHumans());
     }
 
+    public function test_page_export_does_not_include_user_or_revision_links()
+    {
+        $page = Page::first();
+
+        $resp = $this->asEditor()->get($page->getUrl('/export/html'));
+        $resp->assertDontSee($page->getUrl('/revisions'));
+        $resp->assertDontSee($page->createdBy->getProfileUrl());
+        $resp->assertSee($page->createdBy->name);
+    }
+
     public function test_page_export_sets_right_data_type_for_svg_embeds()
     {
         $page = Page::first();
@@ -205,4 +215,36 @@ class ExportTest extends TestCase
         $resp->assertSee('src="/uploads/svg_test.svg"');
     }
 
+    public function test_exports_removes_scripts_from_custom_head()
+    {
+        $entities = [
+            Page::query()->first(), Chapter::query()->first(), Book::query()->first(),
+        ];
+        setting()->put('app-custom-head', '<script>window.donkey = "cat";</script><style>.my-test-class { color: red; }</style>');
+
+        foreach ($entities as $entity) {
+            $resp = $this->asEditor()->get($entity->getUrl('/export/html'));
+            $resp->assertDontSee('window.donkey');
+            $resp->assertDontSee('script');
+            $resp->assertSee('.my-test-class { color: red; }');
+        }
+    }
+
+    public function test_page_export_with_deleted_creator_and_updater()
+    {
+        $user = $this->getViewer(['name' => 'ExportWizardTheFifth']);
+        $page = Page::first();
+        $page->created_by = $user->id;
+        $page->updated_by = $user->id;
+        $page->save();
+
+        $resp = $this->asEditor()->get($page->getUrl('/export/html'));
+        $resp->assertSee('ExportWizardTheFifth');
+
+        $user->delete();
+        $resp = $this->get($page->getUrl('/export/html'));
+        $resp->assertStatus(200);
+        $resp->assertDontSee('ExportWizardTheFifth');
+    }
+
 }
index 68a8f01a9ef347a362b545b7db633435bc26b33f..62fbfbf3140d46b1544b9af26fdb60e38a4c8f9b 100644 (file)
@@ -66,6 +66,36 @@ class PageRevisionTest extends TestCase
         $pageView->assertSee('def456');
     }
 
+    public function test_page_revision_restore_with_markdown_retains_markdown_content()
+    {
+        $this->asEditor();
+
+        $pageRepo = app(PageRepo::class);
+        $page = Page::first();
+        $pageRepo->update($page, ['name' => 'updated page abc123', 'markdown' => '## New Content def456', 'summary' => 'initial page revision testing']);
+        $pageRepo->update($page, ['name' => 'updated page again', 'markdown' => '## New Content Updated', 'summary' => 'page revision testing']);
+        $page = Page::find($page->id);
+
+        $pageView = $this->get($page->getUrl());
+        $pageView->assertDontSee('abc123');
+        $pageView->assertDontSee('def456');
+
+        $revToRestore = $page->revisions()->where('name', 'like', '%abc123')->first();
+        $restoreReq = $this->put($page->getUrl() . '/revisions/' . $revToRestore->id . '/restore');
+        $page = Page::find($page->id);
+
+        $restoreReq->assertStatus(302);
+        $restoreReq->assertRedirect($page->getUrl());
+
+        $pageView = $this->get($page->getUrl());
+        $this->assertDatabaseHas('pages', [
+            'id' => $page->id,
+            'markdown' => '## New Content def456',
+        ]);
+        $pageView->assertSee('abc123');
+        $pageView->assertSee('def456');
+    }
+
     public function test_page_revision_restore_sets_new_revision_with_summary()
     {
         $this->asEditor();
index 4fc6b9c16175b992cd5784ca5c7cd5729118b1fe..a6f6f9d508e070b0c5a41b007a6ad72a72a1ee58 100644 (file)
@@ -6,6 +6,22 @@ use Tests\TestCase;
 
 class PageTest extends TestCase
 {
+
+    public function test_page_view_when_creator_is_deleted_but_owner_exists()
+    {
+        $page = Page::query()->first();
+        $user = $this->getViewer();
+        $owner = $this->getEditor();
+        $page->created_by = $user->id;
+        $page->owned_by = $owner->id;
+        $page->save();
+        $user->delete();
+
+        $resp = $this->asAdmin()->get($page->getUrl());
+        $resp->assertStatus(200);
+        $resp->assertSeeText('Owned by ' . $owner->name);
+    }
+
     public function test_page_creation_with_markdown_content()
     {
         $this->setSettings(['app-editor' => 'markdown']);
@@ -55,6 +71,33 @@ class PageTest extends TestCase
         $redirectReq->assertNotificationContains('Page Successfully Deleted');
     }
 
+    public function test_page_full_delete_removes_all_revisions()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $page->revisions()->create([
+            'html' => '<p>ducks</p>',
+            'name' => 'my page revision',
+            'type' => 'draft',
+        ]);
+        $page->revisions()->create([
+            'html' => '<p>ducks</p>',
+            'name' => 'my page revision',
+            'type' => 'revision',
+        ]);
+
+        $this->assertDatabaseHas('page_revisions', [
+            'page_id' => $page->id,
+        ]);
+
+        $this->asEditor()->delete($page->getUrl());
+        $this->asAdmin()->post('/settings/recycle-bin/empty');
+
+        $this->assertDatabaseMissing('page_revisions', [
+            'page_id' => $page->id,
+        ]);
+    }
+
     public function test_page_copy()
     {
         $page = Page::first();
@@ -145,4 +188,27 @@ class PageTest extends TestCase
             'book_id' => $newBook->id,
         ]);
     }
+
+    public function test_empty_markdown_still_saves_without_error()
+    {
+        $this->setSettings(['app-editor' => 'markdown']);
+        $book = Book::query()->first();
+
+        $this->asEditor()->get($book->getUrl('/create-page'));
+        $draft = Page::query()->where('book_id', '=', $book->id)
+            ->where('draft', '=', true)->first();
+
+        $details = [
+            'name' => 'my page',
+            'markdown' => '',
+        ];
+        $resp = $this->post($book->getUrl("/draft/{$draft->id}"), $details);
+        $resp->assertRedirect();
+
+        $this->assertDatabaseHas('pages', [
+            'markdown' => $details['markdown'],
+            'id' => $draft->id,
+            'draft' => false
+        ]);
+    }
 }
\ No newline at end of file
index d75a134ea5c298bac5d0f6d826436b24e6dbbd70..c27c41f29120e83bb7e635568a4c5b98e6b9e261 100644 (file)
@@ -91,19 +91,19 @@ class SortTest extends TestCase
 
     public function test_page_move_requires_create_permissions_on_parent()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $currentBook = $page->book;
-        $newBook = Book::where('id', '!=', $currentBook->id)->first();
+        $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
         $editor = $this->getEditor();
 
-        $this->setEntityRestrictions($newBook, ['view', 'update', 'delete'], $editor->roles);
+        $this->setEntityRestrictions($newBook, ['view', 'update', 'delete'], $editor->roles->all());
 
         $movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
         ]);
         $this->assertPermissionError($movePageResp);
 
-        $this->setEntityRestrictions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles);
+        $this->setEntityRestrictions($newBook, ['view', 'update', 'delete', 'create'], $editor->roles->all());
         $movePageResp = $this->put($page->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
         ]);
@@ -121,8 +121,8 @@ class SortTest extends TestCase
         $newBook = Book::where('id', '!=', $currentBook->id)->first();
         $editor = $this->getEditor();
 
-        $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles);
-        $this->setEntityRestrictions($page, ['view', 'update', 'create'], $editor->roles);
+        $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
+        $this->setEntityRestrictions($page, ['view', 'update', 'create'], $editor->roles->all());
 
         $movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
@@ -131,7 +131,7 @@ class SortTest extends TestCase
         $pageView = $this->get($page->getUrl());
         $pageView->assertDontSee($page->getUrl('/move'));
 
-        $this->setEntityRestrictions($page, ['view', 'update', 'create', 'delete'], $editor->roles);
+        $this->setEntityRestrictions($page, ['view', 'update', 'create', 'delete'], $editor->roles->all());
         $movePageResp = $this->put($page->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
         ]);
@@ -176,8 +176,8 @@ class SortTest extends TestCase
         $newBook = Book::where('id', '!=', $currentBook->id)->first();
         $editor = $this->getEditor();
 
-        $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles);
-        $this->setEntityRestrictions($chapter, ['view', 'update', 'create'], $editor->roles);
+        $this->setEntityRestrictions($newBook, ['view', 'update', 'create', 'delete'], $editor->roles->all());
+        $this->setEntityRestrictions($chapter, ['view', 'update', 'create'], $editor->roles->all());
 
         $moveChapterResp = $this->actingAs($editor)->put($chapter->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
@@ -186,7 +186,7 @@ class SortTest extends TestCase
         $pageView = $this->get($chapter->getUrl());
         $pageView->assertDontSee($chapter->getUrl('/move'));
 
-        $this->setEntityRestrictions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles);
+        $this->setEntityRestrictions($chapter, ['view', 'update', 'create', 'delete'], $editor->roles->all());
         $moveChapterResp = $this->put($chapter->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
         ]);
@@ -196,6 +196,24 @@ class SortTest extends TestCase
         $this->assertTrue($chapter->book->id == $newBook->id, 'Page book is now the new book');
     }
 
+    public function test_chapter_move_changes_book_for_deleted_pages_within()
+    {
+        /** @var Chapter $chapter */
+        $chapter = Chapter::query()->whereHas('pages')->first();
+        $currentBook = $chapter->book;
+        $pageToCheck = $chapter->pages->first();
+        $newBook = Book::query()->where('id', '!=', $currentBook->id)->first();
+
+        $pageToCheck->delete();
+
+        $this->asEditor()->put($chapter->getUrl('/move'), [
+            'entity_selection' => 'book:' . $newBook->id
+        ]);
+
+        $pageToCheck->refresh();
+        $this->assertEquals($newBook->id, $pageToCheck->book_id);
+    }
+
     public function test_book_sort()
     {
         $oldBook = Book::query()->first();
index 3ad10641ef3d0196ce15800d18d0ed149e3ed50a..62ebf5f1ba4e68840fddad353a0f10020a5c982b 100644 (file)
@@ -1,28 +1,23 @@
 <?php namespace Tests\Entity;
 
-use BookStack\Entities\Models\Book;
-use BookStack\Entities\Models\Chapter;
 use BookStack\Actions\Tag;
 use BookStack\Entities\Models\Entity;
 use BookStack\Entities\Models\Page;
-use BookStack\Auth\Permissions\PermissionService;
-use Tests\BrowserKitTest;
+use Tests\TestCase;
 
-class TagTest extends BrowserKitTest
+class TagTest extends TestCase
 {
 
     protected $defaultTagCount = 20;
 
     /**
      * Get an instance of a page that has many tags.
-     * @param \BookStack\Actions\Tag[]|bool $tags
-     * @return Entity
      */
-    protected function getEntityWithTags($class, $tags = false): Entity
+    protected function getEntityWithTags($class, ?array $tags = null): Entity
     {
         $entity = $class::first();
 
-        if (!$tags) {
+        if (is_null($tags)) {
             $tags = factory(Tag::class, $this->defaultTagCount)->make();
         }
 
@@ -40,12 +35,12 @@ class TagTest extends BrowserKitTest
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'county']));
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'planet']));
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'plans']));
-        $page = $this->getEntityWithTags(Page::class, $attrs);
+        $page = $this->getEntityWithTags(Page::class, $attrs->all());
 
-        $this->asAdmin()->get('/ajax/tags/suggest/names?search=dog')->seeJsonEquals([]);
-        $this->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country', 'county']);
-        $this->get('/ajax/tags/suggest/names?search=cou')->seeJsonEquals(['country', 'county']);
-        $this->get('/ajax/tags/suggest/names?search=pla')->seeJsonEquals(['planet', 'plans']);
+        $this->asAdmin()->get('/ajax/tags/suggest/names?search=dog')->assertExactJson([]);
+        $this->get('/ajax/tags/suggest/names?search=co')->assertExactJson(['color', 'country', 'county']);
+        $this->get('/ajax/tags/suggest/names?search=cou')->assertExactJson(['country', 'county']);
+        $this->get('/ajax/tags/suggest/names?search=pla')->assertExactJson(['planet', 'plans']);
     }
 
     public function test_tag_value_suggestions()
@@ -58,34 +53,48 @@ class TagTest extends BrowserKitTest
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'county', 'value' => 'dog']));
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'planet', 'value' => 'catapult']));
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'plans', 'value' => 'dodgy']));
-        $page = $this->getEntityWithTags(Page::class, $attrs);
+        $page = $this->getEntityWithTags(Page::class, $attrs->all());
 
-        $this->asAdmin()->get('/ajax/tags/suggest/values?search=ora')->seeJsonEquals([]);
-        $this->get('/ajax/tags/suggest/values?search=cat')->seeJsonEquals(['cats', 'cattery', 'catapult']);
-        $this->get('/ajax/tags/suggest/values?search=do')->seeJsonEquals(['dog', 'dodgy']);
-        $this->get('/ajax/tags/suggest/values?search=cas')->seeJsonEquals(['castle']);
+        $this->asAdmin()->get('/ajax/tags/suggest/values?search=ora')->assertExactJson([]);
+        $this->get('/ajax/tags/suggest/values?search=cat')->assertExactJson(['cats', 'cattery', 'catapult']);
+        $this->get('/ajax/tags/suggest/values?search=do')->assertExactJson(['dog', 'dodgy']);
+        $this->get('/ajax/tags/suggest/values?search=cas')->assertExactJson(['castle']);
     }
 
     public function test_entity_permissions_effect_tag_suggestions()
     {
-        $permissionService = $this->app->make(PermissionService::class);
-
         // Create some tags with similar names to test with and save to a page
         $attrs = collect();
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'country']));
         $attrs = $attrs->merge(factory(Tag::class, 5)->make(['name' => 'color']));
-        $page = $this->getEntityWithTags(Page::class, $attrs);
+        $page = $this->getEntityWithTags(Page::class, $attrs->all());
 
-        $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);
-        $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);
+        $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->assertExactJson(['color', 'country']);
+        $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->assertExactJson(['color', 'country']);
 
         // Set restricted permission the page
         $page->restricted = true;
         $page->save();
         $page->rebuildPermissions();
 
-        $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals(['color', 'country']);
-        $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->seeJsonEquals([]);
+        $this->asAdmin()->get('/ajax/tags/suggest/names?search=co')->assertExactJson(['color', 'country']);
+        $this->asEditor()->get('/ajax/tags/suggest/names?search=co')->assertExactJson([]);
+    }
+
+    public function test_tags_shown_on_search_listing()
+    {
+        $tags = [
+            factory(Tag::class)->make(['name' => 'category', 'value' => 'buckets']),
+            factory(Tag::class)->make(['name' => 'color', 'value' => 'red']),
+        ];
+
+        $page = $this->getEntityWithTags(Page::class, $tags);
+        $resp = $this->asEditor()->get("/search?term=[category]");
+        $resp->assertSee($page->name);
+        $resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'category');
+        $resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'buckets');
+        $resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'color');
+        $resp->assertElementContains('[href="' . $page->getUrl() . '"]', 'red');
     }
 
 }
index 1558df78d1c200d59b29d559ceef38f687b81e4f..6b69355fcd3a8cc218469557e0b45b48258e190c 100644 (file)
@@ -38,4 +38,11 @@ class ErrorTest extends TestCase
 
         $this->assertCount(1, $handler->getRecords());
     }
+
+    public function test_access_to_non_existing_image_location_provides_404_response()
+    {
+        $resp = $this->actingAs($this->getViewer())->get('/uploads/images/gallery/2021-05/anonexistingimage.png');
+        $resp->assertStatus(404);
+        $resp->assertSeeText('Image Not Found');
+    }
 }
\ No newline at end of file
diff --git a/tests/FavouriteTest.php b/tests/FavouriteTest.php
new file mode 100644 (file)
index 0000000..7209063
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+
+use BookStack\Actions\Favourite;
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Chapter;
+use BookStack\Entities\Models\Page;
+use Tests\TestCase;
+
+class FavouriteTest extends TestCase
+{
+
+    public function test_page_add_favourite_flow()
+    {
+        $page = Page::query()->first();
+        $editor = $this->getEditor();
+
+        $resp = $this->actingAs($editor)->get($page->getUrl());
+        $resp->assertElementContains('button', 'Favourite');
+        $resp->assertElementExists('form[method="POST"][action$="/favourites/add"]');
+
+        $resp = $this->post('/favourites/add', [
+            'type' => get_class($page),
+            'id' => $page->id,
+        ]);
+        $resp->assertRedirect($page->getUrl());
+        $resp->assertSessionHas('success', "\"{$page->name}\" has been added to your favourites");
+
+        $this->assertDatabaseHas('favourites', [
+            'user_id' => $editor->id,
+            'favouritable_type' => $page->getMorphClass(),
+            'favouritable_id' => $page->id,
+        ]);
+    }
+
+    public function test_page_remove_favourite_flow()
+    {
+        $page = Page::query()->first();
+        $editor = $this->getEditor();
+        Favourite::query()->forceCreate([
+            'user_id' => $editor->id,
+            'favouritable_id' => $page->id,
+            'favouritable_type' => $page->getMorphClass(),
+        ]);
+
+        $resp = $this->actingAs($editor)->get($page->getUrl());
+        $resp->assertElementContains('button', 'Unfavourite');
+        $resp->assertElementExists('form[method="POST"][action$="/favourites/remove"]');
+
+        $resp = $this->post('/favourites/remove', [
+            'type' => get_class($page),
+            'id' => $page->id,
+        ]);
+        $resp->assertRedirect($page->getUrl());
+        $resp->assertSessionHas('success', "\"{$page->name}\" has been removed from your favourites");
+
+        $this->assertDatabaseMissing('favourites', [
+            'user_id' => $editor->id,
+        ]);
+    }
+
+    public function test_book_chapter_shelf_pages_contain_favourite_button()
+    {
+        $entities = [
+            Bookshelf::query()->first(),
+            Book::query()->first(),
+            Chapter::query()->first(),
+        ];
+        $this->actingAs($this->getEditor());
+
+        foreach ($entities as $entity) {
+            $resp = $this->get($entity->getUrl());
+            $resp->assertElementExists('form[method="POST"][action$="/favourites/add"]');
+        }
+    }
+
+    public function test_header_contains_link_to_favourites_page_when_logged_in()
+    {
+        $this->setSettings(['app-public' => 'true']);
+        $this->get('/')->assertElementNotContains('header', 'My Favourites');
+        $this->actingAs($this->getViewer())->get('/')->assertElementContains('header a', 'My Favourites');
+    }
+
+    public function test_favourites_shown_on_homepage()
+    {
+        $editor = $this->getEditor();
+
+        $resp = $this->actingAs($editor)->get('/');
+        $resp->assertElementNotExists('#top-favourites');
+
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $page->favourites()->save((new Favourite)->forceFill(['user_id' => $editor->id]));
+
+        $resp = $this->get('/');
+        $resp->assertElementExists('#top-favourites');
+        $resp->assertElementContains('#top-favourites', $page->name);
+    }
+
+    public function test_favourites_list_page_shows_favourites_and_has_working_pagination()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $editor = $this->getEditor();
+
+        $resp = $this->actingAs($editor)->get('/favourites');
+        $resp->assertDontSee($page->name);
+
+        $page->favourites()->save((new Favourite)->forceFill(['user_id' => $editor->id]));
+
+        $resp = $this->get('/favourites');
+        $resp->assertSee($page->name);
+
+        $resp = $this->get('/favourites?page=2');
+        $resp->assertDontSee($page->name);
+    }
+
+}
\ No newline at end of file
diff --git a/tests/FooterLinksTest.php b/tests/FooterLinksTest.php
new file mode 100644 (file)
index 0000000..f0ff0c4
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+use Tests\TestCase;
+
+class FooterLinksTest extends TestCase
+{
+
+    public function test_saving_setting()
+    {
+        $resp = $this->asAdmin()->post("/settings", [
+            'setting-app-footer-links' => [
+                ['label' => 'My custom link 1', 'url' => 'https://example.com/1'],
+                ['label' => 'My custom link 2', 'url' => 'https://example.com/2'],
+            ],
+        ]);
+        $resp->assertRedirect('/settings');
+
+        $result = setting('app-footer-links');
+        $this->assertIsArray($result);
+        $this->assertCount(2, $result);
+        $this->assertEquals('My custom link 2', $result[1]['label']);
+        $this->assertEquals('https://example.com/1', $result[0]['url']);
+    }
+
+    public function test_set_options_visible_on_settings_page()
+    {
+        $this->setSettings(['app-footer-links' => [
+            ['label' => 'My custom link', 'url' => 'https://example.com/link-a'],
+            ['label' => 'Another Link', 'url' => 'https://example.com/link-b'],
+        ]]);
+
+        $resp = $this->asAdmin()->get('/settings');
+        $resp->assertSee('value="My custom link"');
+        $resp->assertSee('value="Another Link"');
+        $resp->assertSee('value="https://example.com/link-a"');
+        $resp->assertSee('value="https://example.com/link-b"');
+    }
+
+    public function test_footer_links_show_on_pages()
+    {
+        $this->setSettings(['app-footer-links' => [
+            ['label' => 'My custom link', 'url' => 'https://example.com/link-a'],
+            ['label' => 'Another Link', 'url' => 'https://example.com/link-b'],
+        ]]);
+
+        $this->get('/login')->assertElementContains('footer a[href="https://example.com/link-a"]', 'My custom link');
+        $this->asEditor()->get('/')->assertElementContains('footer a[href="https://example.com/link-b"]', 'Another link');
+    }
+
+    public function test_using_translation_system_for_labels()
+    {
+        $this->setSettings(['app-footer-links' => [
+            ['label' => 'trans::common.privacy_policy', 'url' => 'https://example.com/privacy'],
+            ['label' => 'trans::common.terms_of_service', 'url' => 'https://example.com/terms'],
+        ]]);
+
+        $resp = $this->get('/login');
+        $resp->assertElementContains('footer a[href="https://example.com/privacy"]', 'Privacy Policy');
+        $resp->assertElementContains('footer a[href="https://example.com/terms"]', 'Terms of Service');
+    }
+}
\ No newline at end of file
index 943a3160a1915af2a803ed88cd82411e67f2336d..a8e33465d108b1b9ad5000ef796ce761789b3db4 100644 (file)
@@ -1,6 +1,9 @@
 <?php namespace Tests;
 
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
 use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Page;
 
 class HomepageTest extends TestCase
 {
@@ -141,4 +144,14 @@ class HomepageTest extends TestCase
         $homeVisit->assertElementContains('.content-wrap', $shelf->name);
         $homeVisit->assertElementContains('.content-wrap', $book->name);
     }
+
+    public function test_new_users_dont_have_any_recently_viewed()
+    {
+        $user = factory(User::class)->create();
+        $viewRole = Role::getRole('Viewer');
+        $user->attachRole($viewRole);
+
+        $homeVisit = $this->actingAs($user)->get('/');
+        $homeVisit->assertElementContains('#recently-viewed', 'You have not viewed any pages');
+    }
 }
index 1e6d1cc325e6bff83ff55322dc05e0c99e0c22d1..8dc112e57d8f8182775c4bcc8d60ff3a65e43b12 100644 (file)
@@ -29,13 +29,13 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->viewer = $this->getViewer();
     }
 
-    protected function setEntityRestrictions(Entity $entity, $actions = [], $roles = [])
+    protected function setRestrictionsForTestRoles(Entity $entity, array $actions = [])
     {
         $roles = [
             $this->user->roles->first(),
             $this->viewer->roles->first(),
         ];
-        parent::setEntityRestrictions($entity, $actions, $roles);
+        $this->setEntityRestrictions($entity, $actions, $roles);
     }
 
     public function test_bookshelf_view_restriction()
@@ -46,12 +46,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($shelf->getUrl())
             ->seePageIs($shelf->getUrl());
 
-        $this->setEntityRestrictions($shelf, []);
+        $this->setRestrictionsForTestRoles($shelf, []);
 
         $this->forceVisit($shelf->getUrl())
             ->see('Bookshelf not found');
 
-        $this->setEntityRestrictions($shelf, ['view']);
+        $this->setRestrictionsForTestRoles($shelf, ['view']);
 
         $this->visit($shelf->getUrl())
             ->see($shelf->name);
@@ -65,12 +65,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($shelf->getUrl('/edit'))
             ->see('Edit Book');
 
-        $this->setEntityRestrictions($shelf, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
 
         $this->forceVisit($shelf->getUrl('/edit'))
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($shelf, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
 
         $this->visit($shelf->getUrl('/edit'))
             ->seePageIs($shelf->getUrl('/edit'));
@@ -84,12 +84,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($shelf->getUrl('/delete'))
             ->see('Delete Book');
 
-        $this->setEntityRestrictions($shelf, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
 
         $this->forceVisit($shelf->getUrl('/delete'))
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($shelf, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
 
         $this->visit($shelf->getUrl('/delete'))
             ->seePageIs($shelf->getUrl('/delete'))->see('Delete Book');
@@ -106,7 +106,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($bookUrl)
             ->seePageIs($bookUrl);
 
-        $this->setEntityRestrictions($book, []);
+        $this->setRestrictionsForTestRoles($book, []);
 
         $this->forceVisit($bookUrl)
             ->see('Book not found');
@@ -115,7 +115,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->forceVisit($bookChapter->getUrl())
             ->see('Chapter not found');
 
-        $this->setEntityRestrictions($book, ['view']);
+        $this->setRestrictionsForTestRoles($book, ['view']);
 
         $this->visit($bookUrl)
             ->see($book->name);
@@ -139,7 +139,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->seeInElement('.actions', 'New Page')
             ->seeInElement('.actions', 'New Chapter');
 
-        $this->setEntityRestrictions($book, ['view', 'delete', 'update']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
 
         $this->forceVisit($bookUrl . '/create-chapter')
             ->see('You do not have permission')->seePageIs('/');
@@ -148,7 +148,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->visit($bookUrl)->dontSeeInElement('.actions', 'New Page')
             ->dontSeeInElement('.actions', 'New Chapter');
 
-        $this->setEntityRestrictions($book, ['view', 'create']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'create']);
 
         $this->visit($bookUrl . '/create-chapter')
             ->type('test chapter', 'name')
@@ -175,7 +175,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($bookUrl . '/edit')
             ->see('Edit Book');
 
-        $this->setEntityRestrictions($book, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
 
         $this->forceVisit($bookUrl . '/edit')
             ->see('You do not have permission')->seePageIs('/');
@@ -184,7 +184,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->forceVisit($bookChapter->getUrl() . '/edit')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($book, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'update']);
 
         $this->visit($bookUrl . '/edit')
             ->seePageIs($bookUrl . '/edit');
@@ -205,7 +205,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($bookUrl . '/delete')
             ->see('Delete Book');
 
-        $this->setEntityRestrictions($book, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'update']);
 
         $this->forceVisit($bookUrl . '/delete')
             ->see('You do not have permission')->seePageIs('/');
@@ -214,7 +214,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->forceVisit($bookChapter->getUrl() . '/delete')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($book, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
 
         $this->visit($bookUrl . '/delete')
             ->seePageIs($bookUrl . '/delete')->see('Delete Book');
@@ -234,14 +234,14 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($chapterUrl)
             ->seePageIs($chapterUrl);
 
-        $this->setEntityRestrictions($chapter, []);
+        $this->setRestrictionsForTestRoles($chapter, []);
 
         $this->forceVisit($chapterUrl)
             ->see('Chapter not found');
         $this->forceVisit($chapterPage->getUrl())
             ->see('Page not found');
 
-        $this->setEntityRestrictions($chapter, ['view']);
+        $this->setRestrictionsForTestRoles($chapter, ['view']);
 
         $this->visit($chapterUrl)
             ->see($chapter->name);
@@ -258,13 +258,13 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($chapterUrl)
             ->seeInElement('.actions', 'New Page');
 
-        $this->setEntityRestrictions($chapter, ['view', 'delete', 'update']);
+        $this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
 
         $this->forceVisit($chapterUrl . '/create-page')
             ->see('You do not have permission')->seePageIs('/');
         $this->visit($chapterUrl)->dontSeeInElement('.actions', 'New Page');
 
-        $this->setEntityRestrictions($chapter, ['view', 'create']);
+        $this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
 
 
         $this->visit($chapterUrl . '/create-page')
@@ -286,14 +286,14 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($chapterUrl . '/edit')
             ->see('Edit Chapter');
 
-        $this->setEntityRestrictions($chapter, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
 
         $this->forceVisit($chapterUrl . '/edit')
             ->see('You do not have permission')->seePageIs('/');
         $this->forceVisit($chapterPage->getUrl() . '/edit')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($chapter, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
 
         $this->visit($chapterUrl . '/edit')
             ->seePageIs($chapterUrl . '/edit')->see('Edit Chapter');
@@ -311,14 +311,14 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($chapterUrl . '/delete')
             ->see('Delete Chapter');
 
-        $this->setEntityRestrictions($chapter, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
 
         $this->forceVisit($chapterUrl . '/delete')
             ->see('You do not have permission')->seePageIs('/');
         $this->forceVisit($chapterPage->getUrl() . '/delete')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($chapter, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
 
         $this->visit($chapterUrl . '/delete')
             ->seePageIs($chapterUrl . '/delete')->see('Delete Chapter');
@@ -335,12 +335,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($pageUrl)
             ->seePageIs($pageUrl);
 
-        $this->setEntityRestrictions($page, ['update', 'delete']);
+        $this->setRestrictionsForTestRoles($page, ['update', 'delete']);
 
         $this->forceVisit($pageUrl)
             ->see('Page not found');
 
-        $this->setEntityRestrictions($page, ['view']);
+        $this->setRestrictionsForTestRoles($page, ['view']);
 
         $this->visit($pageUrl)
             ->see($page->name);
@@ -355,12 +355,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($pageUrl . '/edit')
             ->seeInField('name', $page->name);
 
-        $this->setEntityRestrictions($page, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
 
         $this->forceVisit($pageUrl . '/edit')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($page, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($page, ['view', 'update']);
 
         $this->visit($pageUrl . '/edit')
             ->seePageIs($pageUrl . '/edit')->seeInField('name', $page->name);
@@ -375,12 +375,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($pageUrl . '/delete')
             ->see('Delete Page');
 
-        $this->setEntityRestrictions($page, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($page, ['view', 'update']);
 
         $this->forceVisit($pageUrl . '/delete')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($page, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
 
         $this->visit($pageUrl . '/delete')
             ->seePageIs($pageUrl . '/delete')->see('Delete Page');
@@ -460,7 +460,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $page = $chapter->pages->first();
         $page2 = $chapter->pages[2];
 
-        $this->setEntityRestrictions($page, []);
+        $this->setRestrictionsForTestRoles($page, []);
 
         $this->actingAs($this->user)
             ->visit($page2->getUrl())
@@ -472,7 +472,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $chapter = Chapter::first();
         $page = $chapter->pages->first();
 
-        $this->setEntityRestrictions($page, []);
+        $this->setRestrictionsForTestRoles($page, []);
 
         $this->actingAs($this->user)
             ->visit($chapter->getUrl())
@@ -484,7 +484,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $chapter = Chapter::first();
         $page = $chapter->pages->first();
 
-        $this->setEntityRestrictions($page, []);
+        $this->setRestrictionsForTestRoles($page, []);
 
         $this->actingAs($this->user)
             ->visit($chapter->getUrl())
@@ -499,7 +499,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->see($chapter->pages->first()->name);
 
         foreach ($chapter->pages as $page) {
-            $this->setEntityRestrictions($page, []);
+            $this->setRestrictionsForTestRoles($page, []);
         }
 
         $this->actingAs($this->user)
@@ -515,12 +515,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($shelf->getUrl('/edit'))
             ->dontSee('Edit Book');
 
-        $this->setEntityRestrictions($shelf, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
 
         $this->forceVisit($shelf->getUrl('/edit'))
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($shelf, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
 
         $this->visit($shelf->getUrl('/edit'))
             ->seePageIs($shelf->getUrl('/edit'));
@@ -534,12 +534,12 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($shelf->getUrl('/delete'))
             ->dontSee('Delete Book');
 
-        $this->setEntityRestrictions($shelf, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
 
         $this->forceVisit($shelf->getUrl('/delete'))
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($shelf, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
 
         $this->visit($shelf->getUrl('/delete'))
             ->seePageIs($shelf->getUrl('/delete'))->see('Delete Book');
@@ -555,7 +555,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->dontSeeInElement('.actions', 'New Page')
             ->dontSeeInElement('.actions', 'New Chapter');
 
-        $this->setEntityRestrictions($book, ['view', 'delete', 'update']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
 
         $this->forceVisit($bookUrl . '/create-chapter')
             ->see('You do not have permission')->seePageIs('/');
@@ -564,7 +564,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->visit($bookUrl)->dontSeeInElement('.actions', 'New Page')
             ->dontSeeInElement('.actions', 'New Chapter');
 
-        $this->setEntityRestrictions($book, ['view', 'create']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'create']);
 
         $this->visit($bookUrl . '/create-chapter')
             ->type('test chapter', 'name')
@@ -591,7 +591,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($bookUrl . '/edit')
             ->dontSee('Edit Book');
 
-        $this->setEntityRestrictions($book, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
 
         $this->forceVisit($bookUrl . '/edit')
             ->see('You do not have permission')->seePageIs('/');
@@ -600,7 +600,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->forceVisit($bookChapter->getUrl() . '/edit')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($book, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'update']);
 
         $this->visit($bookUrl . '/edit')
             ->seePageIs($bookUrl . '/edit');
@@ -621,7 +621,7 @@ class EntityPermissionsTest extends BrowserKitTest
             ->visit($bookUrl . '/delete')
             ->dontSee('Delete Book');
 
-        $this->setEntityRestrictions($book, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'update']);
 
         $this->forceVisit($bookUrl . '/delete')
             ->see('You do not have permission')->seePageIs('/');
@@ -630,7 +630,7 @@ class EntityPermissionsTest extends BrowserKitTest
         $this->forceVisit($bookChapter->getUrl() . '/delete')
             ->see('You do not have permission')->seePageIs('/');
 
-        $this->setEntityRestrictions($book, ['view', 'delete']);
+        $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
 
         $this->visit($bookUrl . '/delete')
             ->seePageIs($bookUrl . '/delete')->see('Delete Book');
@@ -651,8 +651,8 @@ class EntityPermissionsTest extends BrowserKitTest
             $entity->save();
         }
 
-        $this->setEntityRestrictions($book, []);
-        $this->setEntityRestrictions($bookPage, ['view']);
+        $this->setRestrictionsForTestRoles($book, []);
+        $this->setRestrictionsForTestRoles($bookPage, ['view']);
 
         $this->actingAs($this->viewer);
         $this->get($bookPage->getUrl());
@@ -667,8 +667,8 @@ class EntityPermissionsTest extends BrowserKitTest
         $firstBook = Book::first();
         $secondBook = Book::find(2);
 
-        $this->setEntityRestrictions($firstBook, ['view', 'update']);
-        $this->setEntityRestrictions($secondBook, ['view']);
+        $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($secondBook, ['view']);
 
         // Test sort page visibility
         $this->actingAs($this->user)->visit($secondBook->getUrl() . '/sort')
@@ -683,8 +683,8 @@ class EntityPermissionsTest extends BrowserKitTest
         $firstBook = Book::first();
         $secondBook = Book::find(2);
 
-        $this->setEntityRestrictions($firstBook, ['view', 'update']);
-        $this->setEntityRestrictions($secondBook, ['view']);
+        $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
+        $this->setRestrictionsForTestRoles($secondBook, ['view']);
 
         $firstBookChapter = $this->newChapter(['name' => 'first book chapter'], $firstBook);
         $secondBookChapter = $this->newChapter(['name' => 'second book chapter'], $secondBook);
@@ -726,14 +726,14 @@ class EntityPermissionsTest extends BrowserKitTest
     public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
     {
         $book = Book::first();
-        $this->setEntityRestrictions($book, []);
+        $this->setRestrictionsForTestRoles($book, []);
         $bookChapter = $book->chapters->first();
-        $this->setEntityRestrictions($bookChapter, ['view']);
+        $this->setRestrictionsForTestRoles($bookChapter, ['view']);
 
         $this->actingAs($this->user)->visit($bookChapter->getUrl())
             ->dontSee('New Page');
 
-        $this->setEntityRestrictions($bookChapter, ['view', 'create']);
+        $this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
 
         $this->actingAs($this->user)->visit($bookChapter->getUrl())
             ->click('New Page')
index 1941901240b5288d628a8264c5bd83a1bef0b074..7dbf467bd838d8d82ece92e72b6b298ec0b88146 100644 (file)
@@ -8,72 +8,75 @@ use BookStack\Auth\User;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Models\Page;
+use Illuminate\Support\Facades\View;
 
-class PublicActionTest extends BrowserKitTest
+class PublicActionTest extends TestCase
 {
 
     public function test_app_not_public()
     {
         $this->setSettings(['app-public' => 'false']);
-        $book = Book::orderBy('name', 'asc')->first();
-        $this->visit('/books')->seePageIs('/login');
-        $this->visit($book->getUrl())->seePageIs('/login');
+        $book = Book::query()->first();
+        $this->get('/books')->assertRedirect('/login');
+        $this->get($book->getUrl())->assertRedirect('/login');
 
-        $page = Page::first();
-        $this->visit($page->getUrl())->seePageIs('/login');
+        $page = Page::query()->first();
+        $this->get($page->getUrl())->assertRedirect('/login');
     }
 
     public function test_login_link_visible()
     {
         $this->setSettings(['app-public' => 'true']);
-        $this->visit('/')->see(url('/login'));
+        $this->get('/')->assertElementExists('a[href="'.url('/login').'"]');
     }
 
     public function test_register_link_visible_when_enabled()
     {
         $this->setSettings(['app-public' => 'true']);
-
-        $this->visit('/')->see(url('/login'));
-        $this->visit('/')->dontSee(url('/register'));
+        $home = $this->get('/');
+        $home->assertSee(url('/login'));
+        $home->assertDontSee(url('/register'));
 
         $this->setSettings(['app-public' => 'true', 'registration-enabled' => 'true']);
-        $this->visit('/')->see(url('/login'));
-        $this->visit('/')->see(url('/register'));
+        $home = $this->get('/');
+        $home->assertSee(url('/login'));
+        $home->assertSee(url('/register'));
     }
 
     public function test_books_viewable()
     {
         $this->setSettings(['app-public' => 'true']);
-        $books = Book::orderBy('name', 'asc')->take(10)->get();
+        $books = Book::query()->orderBy('name', 'asc')->take(10)->get();
         $bookToVisit = $books[1];
 
         // Check books index page is showing
-        $this->visit('/books')
-            ->seeStatusCode(200)
-            ->see($books[0]->name)
-            // Check individual book page is showing and it's child contents are visible.
-            ->click($bookToVisit->name)
-            ->seePageIs($bookToVisit->getUrl())
-            ->see($bookToVisit->name)
-            ->see($bookToVisit->chapters()->first()->name);
+        $resp = $this->get('/books');
+        $resp->assertStatus(200);
+        $resp->assertSee($books[0]->name);
+
+        // Check individual book page is showing and it's child contents are visible.
+        $resp = $this->get($bookToVisit->getUrl());
+        $resp->assertSee($bookToVisit->name);
+        $resp->assertSee($bookToVisit->chapters()->first()->name);
     }
 
     public function test_chapters_viewable()
     {
         $this->setSettings(['app-public' => 'true']);
-        $chapterToVisit = Chapter::first();
+        /** @var Chapter $chapterToVisit */
+        $chapterToVisit = Chapter::query()->first();
         $pageToVisit = $chapterToVisit->pages()->first();
 
         // Check chapters index page is showing
-        $this->visit($chapterToVisit->getUrl())
-            ->seeStatusCode(200)
-            ->see($chapterToVisit->name)
-            // Check individual chapter page is showing and it's child contents are visible.
-            ->see($pageToVisit->name)
-            ->click($pageToVisit->name)
-            ->see($chapterToVisit->book->name)
-            ->see($chapterToVisit->name)
-            ->seePageIs($pageToVisit->getUrl());
+        $resp = $this->get($chapterToVisit->getUrl());
+        $resp->assertStatus(200);
+        $resp->assertSee($chapterToVisit->name);
+        // Check individual chapter page is showing and it's child contents are visible.
+        $resp->assertSee($pageToVisit->name);
+        $resp = $this->get($pageToVisit->getUrl());
+        $resp->assertStatus(200);
+        $resp->assertSee($chapterToVisit->book->name);
+        $resp->assertSee($chapterToVisit->name);
     }
 
     public function test_public_page_creation()
@@ -87,19 +90,22 @@ class PublicActionTest extends BrowserKitTest
         }
         $this->app[PermissionService::class]->buildJointPermissionForRole($publicRole);
 
-        $chapter = Chapter::first();
-        $this->visit($chapter->book->getUrl());
-        $this->visit($chapter->getUrl())
-            ->click('New Page')
-            ->see('New Page')
-            ->seePageIs($chapter->getUrl('/create-page'));
+        /** @var Chapter $chapter */
+        $chapter = Chapter::query()->first();
+        $resp = $this->get($chapter->getUrl());
+        $resp->assertSee('New Page');
+        $resp->assertElementExists('a[href="'.$chapter->getUrl('/create-page').'"]');
 
-        $this->submitForm('Continue', [
-            'name' => 'My guest page'
-        ])->seePageIs($chapter->book->getUrl('/page/my-guest-page/edit'));
+        $resp = $this->get($chapter->getUrl('/create-page'));
+        $resp->assertSee('Continue');
+        $resp->assertSee('Page Name');
+        $resp->assertElementExists('form[action="'.$chapter->getUrl('/create-guest-page').'"]');
+
+        $resp = $this->post($chapter->getUrl('/create-guest-page'), ['name' => 'My guest page']);
+        $resp->assertRedirect($chapter->book->getUrl('/page/my-guest-page/edit'));
 
         $user = User::getDefault();
-        $this->seeInDatabase('pages', [
+        $this->assertDatabaseHas('pages', [
             'name' => 'My guest page',
             'chapter_id' => $chapter->id,
             'created_by' => $user->id,
@@ -109,75 +115,73 @@ class PublicActionTest extends BrowserKitTest
 
     public function test_content_not_listed_on_404_for_public_users()
     {
-        $page = Page::first();
-        $this->asAdmin()->visit($page->getUrl());
+        $page = Page::query()->first();
+        $page->fill(['name' => 'my testing random unique page name'])->save();
+        $this->asAdmin()->get($page->getUrl()); // Fake visit to show on recents
+        $resp = $this->get('/cats/dogs/hippos');
+        $resp->assertStatus(404);
+        $resp->assertSee($page->name);
+        View::share('pageTitle', '');
+
         Auth::logout();
-        view()->share('pageTitle', '');
-        $this->forceVisit('/cats/dogs/hippos');
-        $this->dontSee($page->name);
+        $resp = $this->get('/cats/dogs/hippos');
+        $resp->assertStatus(404);
+        $resp->assertDontSee($page->name);
     }
 
     public function test_robots_effected_by_public_status()
     {
-        $this->visit('/robots.txt');
-        $this->seeText("User-agent: *\nDisallow: /");
+        $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
 
         $this->setSettings(['app-public' => 'true']);
-        $this->visit('/robots.txt');
 
-        $this->seeText("User-agent: *\nDisallow:");
-        $this->dontSeeText("Disallow: /");
+        $resp = $this->get('/robots.txt');
+        $resp->assertSee("User-agent: *\nDisallow:");
+        $resp->assertDontSee("Disallow: /");
     }
 
     public function test_robots_effected_by_setting()
     {
-        $this->visit('/robots.txt');
-        $this->seeText("User-agent: *\nDisallow: /");
+        $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
 
         config()->set('app.allow_robots', true);
-        $this->visit('/robots.txt');
 
-        $this->seeText("User-agent: *\nDisallow:");
-        $this->dontSeeText("Disallow: /");
+        $resp = $this->get('/robots.txt');
+        $resp->assertSee("User-agent: *\nDisallow:");
+        $resp->assertDontSee("Disallow: /");
 
         // Check config overrides app-public setting
         config()->set('app.allow_robots', false);
         $this->setSettings(['app-public' => 'true']);
-        $this->visit('/robots.txt');
-
-        $this->seeText("User-agent: *\nDisallow: /");
+        $this->get('/robots.txt')->assertSee("User-agent: *\nDisallow: /");
     }
 
     public function test_public_view_then_login_redirects_to_previous_content()
     {
         $this->setSettings(['app-public' => 'true']);
+        /** @var Book $book */
         $book = Book::query()->first();
-        $this->visit($book->getUrl())
-            ->see($book->name)
-            ->visit('/login')
-            ->type('[email protected]', '#email')
-            ->type('password', '#password')
-            ->press('Log In')
-            ->seePageUrlIs($book->getUrl());
+        $resp = $this->get($book->getUrl());
+        $resp->assertSee($book->name);
+
+        $this->get('/login');
+        $resp = $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
+        $resp->assertRedirect($book->getUrl());
     }
 
     public function test_access_hidden_content_then_login_redirects_to_intended_content()
     {
         $this->setSettings(['app-public' => 'true']);
+        /** @var Book $book */
         $book = Book::query()->first();
         $this->setEntityRestrictions($book);
 
-        try {
-            $this->visit($book->getUrl());
-        } catch (\Exception $exception) {}
-
-        $this->see('Book not found')
-            ->dontSee($book->name)
-            ->visit('/login')
-            ->type('[email protected]', '#email')
-            ->type('password', '#password')
-            ->press('Log In')
-            ->seePageUrlIs($book->getUrl())
-            ->see($book->name);
+        $resp = $this->get($book->getUrl());
+        $resp->assertSee('Book not found');
+
+        $this->get('/login');
+        $resp = $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
+        $resp->assertRedirect($book->getUrl());
+        $this->followRedirects($resp)->assertSee($book->name);
     }
 }
\ No newline at end of file
index 60f06cfc4769a9d457fd401eb0727b6d5405ba6e..55a9571de40acbdd1896cd7e5208012f8a28719d 100644 (file)
@@ -1,7 +1,10 @@
 <?php namespace Tests;
 
 use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Models\Deletion;
+use BookStack\Entities\Models\Entity;
 use BookStack\Entities\Models\Page;
 use DB;
 use Illuminate\Support\Carbon;
@@ -129,6 +132,21 @@ class RecycleBinTest extends TestCase
         $redirectReq->assertNotificationContains('Deleted '.$itemCount.' total items from the recycle bin');
     }
 
+    public function test_permanent_delete_for_each_type()
+    {
+        /** @var Entity $entity */
+        foreach ([new Bookshelf, new Book, new Chapter, new Page] as $entity) {
+            $entity = $entity->newQuery()->first();
+            $this->asEditor()->delete($entity->getUrl());
+            $deletion = Deletion::query()->orderBy('id', 'desc')->firstOrFail();
+
+            $deleteReq = $this->asAdmin()->delete("/settings/recycle-bin/{$deletion->id}");
+            $deleteReq->assertRedirect('/settings/recycle-bin');
+            $this->assertDatabaseMissing('deletions', ['id' => $deletion->id]);
+            $this->assertDatabaseMissing($entity->getTable(), ['id' => $entity->id]);
+        }
+    }
+
     public function test_permanent_entity_delete_updates_existing_activity_with_entity_name()
     {
         $page = Page::query()->firstOrFail();
index 02f7caae1cb859b4c14d105eab864a78eb4ec0fc..78c1f3b1825121a9c2a8ece027e29fe2fd087f3d 100644 (file)
@@ -15,13 +15,11 @@ use BookStack\Auth\Permissions\PermissionService;
 use BookStack\Entities\Repos\PageRepo;
 use BookStack\Settings\SettingService;
 use BookStack\Uploads\HttpFetcher;
-use Illuminate\Http\Response;
 use Illuminate\Support\Env;
 use Illuminate\Support\Facades\Log;
 use Mockery;
 use Monolog\Handler\TestHandler;
 use Monolog\Logger;
-use Throwable;
 use Illuminate\Foundation\Testing\Assert as PHPUnit;
 
 trait SharedTestHelpers
@@ -32,7 +30,6 @@ trait SharedTestHelpers
 
     /**
      * Set the current user context to be an admin.
-     * @return $this
      */
     public function asAdmin()
     {
@@ -41,19 +38,19 @@ trait SharedTestHelpers
 
     /**
      * Get the current admin user.
-     * @return mixed
      */
-    public function getAdmin() {
-        if($this->admin === null) {
+    public function getAdmin(): User
+    {
+        if (is_null($this->admin)) {
             $adminRole = Role::getSystemRole('admin');
             $this->admin = $adminRole->users->first();
         }
+
         return $this->admin;
     }
 
     /**
      * Set the current user context to be an editor.
-     * @return $this
      */
     public function asEditor()
     {
@@ -63,10 +60,10 @@ trait SharedTestHelpers
 
     /**
      * Get a editor user.
-     * @return mixed
      */
-    protected function getEditor() {
-        if($this->editor === null) {
+    protected function getEditor(): User
+    {
+        if ($this->editor === null) {
             $editorRole = Role::getRole('editor');
             $this->editor = $editorRole->users->first();
         }
@@ -87,10 +84,8 @@ trait SharedTestHelpers
 
     /**
      * Regenerate the permission for an entity.
-     * @param Entity $entity
-     * @throws Throwable
      */
-    protected function regenEntityPermissions(Entity $entity)
+    protected function regenEntityPermissions(Entity $entity): void
     {
         $entity->rebuildPermissions();
         $entity->load('jointPermissions');
@@ -98,40 +93,34 @@ trait SharedTestHelpers
 
     /**
      * Create and return a new bookshelf.
-     * @param array $input
-     * @return Bookshelf
      */
-    public function newShelf($input = ['name' => 'test shelf', 'description' => 'My new test shelf']) {
+    public function newShelf(array $input = ['name' => 'test shelf', 'description' => 'My new test shelf']): Bookshelf
+    {
         return app(BookshelfRepo::class)->create($input, []);
     }
 
     /**
      * Create and return a new book.
-     * @param array $input
-     * @return Book
      */
-    public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) {
+    public function newBook(array $input = ['name' => 'test book', 'description' => 'My new test book']): Book
+    {
         return app(BookRepo::class)->create($input);
     }
 
     /**
      * Create and return a new test chapter
-     * @param array $input
-     * @param Book $book
-     * @return Chapter
      */
-    public function newChapter($input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book) {
+    public function newChapter(array $input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book): Chapter
+    {
         return app(ChapterRepo::class)->create($input, $book);
     }
 
     /**
      * Create and return a new test page
-     * @param array $input
-     * @return Page
-     * @throws Throwable
      */
-    public function newPage($input = ['name' => 'test page', 'html' => 'My new test page']) {
-        $book = Book::first();
+    public function newPage(array $input = ['name' => 'test page', 'html' => 'My new test page']): Page
+    {
+        $book = Book::query()->first();
         $pageRepo = app(PageRepo::class);
         $draftPage = $pageRepo->getNewDraftPage($book);
         return $pageRepo->publishDraft($draftPage, $input);
@@ -139,9 +128,8 @@ trait SharedTestHelpers
 
     /**
      * Quickly sets an array of settings.
-     * @param $settingsArray
      */
-    protected function setSettings($settingsArray)
+    protected function setSettings(array $settingsArray): void
     {
         $settings = app(SettingService::class);
         foreach ($settingsArray as $key => $value) {
@@ -151,11 +139,8 @@ trait SharedTestHelpers
 
     /**
      * Manually set some permissions on an entity.
-     * @param Entity $entity
-     * @param array $actions
-     * @param array $roles
      */
-    protected function setEntityRestrictions(Entity $entity, $actions = [], $roles = [])
+    protected function setEntityRestrictions(Entity $entity, array $actions = [], array $roles = []): void
     {
         $entity->restricted = true;
         $entity->permissions()->delete();
@@ -180,7 +165,7 @@ trait SharedTestHelpers
     /**
      * Give the given user some permissions.
      */
-    protected function giveUserPermissions(User $user, array $permissions = [])
+    protected function giveUserPermissions(User $user, array $permissions = []): void
     {
         $newRole = $this->createNewRole($permissions);
         $user->attachRole($newRole);
@@ -190,10 +175,8 @@ trait SharedTestHelpers
 
     /**
      * Create a new basic role for testing purposes.
-     * @param array $permissions
-     * @return Role
      */
-    protected function createNewRole($permissions = [])
+    protected function createNewRole(array $permissions = []): Role
     {
         $permissionRepo = app(PermissionsRepo::class);
         $roleData = factory(Role::class)->make()->toArray();
@@ -203,8 +186,6 @@ trait SharedTestHelpers
 
     /**
      * Mock the HttpFetcher service and return the given data on fetch.
-     * @param $returnData
-     * @param int $times
      */
     protected function mockHttpFetch($returnData, int $times = 1)
     {
@@ -218,9 +199,6 @@ trait SharedTestHelpers
     /**
      * Run a set test with the given env variable.
      * Remembers the original and resets the value after test.
-     * @param string $name
-     * @param $value
-     * @param callable $callback
      */
     protected function runWithEnv(string $name, $value, callable $callback)
     {
@@ -246,11 +224,8 @@ trait SharedTestHelpers
     /**
      * Check the keys and properties in the given map to include
      * exist, albeit not exclusively, within the map to check.
-     * @param array $mapToInclude
-     * @param array $mapToCheck
-     * @param string $message
      */
-    protected function assertArrayMapIncludes(array $mapToInclude, array $mapToCheck, string $message = '') : void
+    protected function assertArrayMapIncludes(array $mapToInclude, array $mapToCheck, string $message = ''): void
     {
         $passed = true;
 
@@ -301,7 +276,7 @@ trait SharedTestHelpers
         $testHandler = new TestHandler();
         $monolog->pushHandler($testHandler);
 
-        Log::extend('testing', function() use ($monolog) {
+        Log::extend('testing', function () use ($monolog) {
             return $monolog;
         });
         Log::setDefaultDriver('testing');
index ca150d0d65aaf6409ca5a63b158b13abc13837fe..b4c35cf91759774060a48a715d7ea594d54419da 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use Illuminate\Cache\ArrayStore;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\Session;
@@ -31,7 +32,9 @@ class StatusTest extends TestCase
 
     public function test_returns_500_status_and_false_on_wrong_cache_return()
     {
-        Cache::partialMock()->shouldReceive('get')->andReturn('cat');
+        $mockStore = Mockery::mock(new ArrayStore())->makePartial();
+        Cache::swap($mockStore);
+        $mockStore->shouldReceive('get')->andReturn('cat');
 
         $resp = $this->get("/status");
         $resp->assertStatus(500);
index 9c6b78782b4c91bb8576bfb535666559a588d772..bf7ee0f69add042497b6862569354b1f1629427f 100644 (file)
@@ -60,13 +60,20 @@ class TestResponse extends BaseTestResponse {
 
     /**
      * Assert the response includes a specific element containing the given text.
+     * If an nth match is provided, only that will be checked otherwise all matching
+     * elements will be checked for the given text.
      * @return $this
      */
-    public function assertElementContains(string $selector, string $text)
+    public function assertElementContains(string $selector, string $text, ?int $nthMatch = null)
     {
         $elements = $this->crawler()->filter($selector);
         $matched = false;
         $pattern = $this->getEscapedPattern($text);
+
+        if (!is_null($nthMatch)) {
+            $elements = $elements->eq($nthMatch - 1);
+        }
+
         foreach ($elements as $element) {
             $element = new Crawler($element);
             if (preg_match("/$pattern/i", $element->html())) {
@@ -78,6 +85,7 @@ class TestResponse extends BaseTestResponse {
         PHPUnit::assertTrue(
             $matched,
             'Unable to find element of selector: '.PHP_EOL.PHP_EOL.
+            ($nthMatch ? ("at position {$nthMatch}".PHP_EOL.PHP_EOL) : '') .
             "[{$selector}]".PHP_EOL.PHP_EOL.
             'containing text'.PHP_EOL.PHP_EOL.
             "[{$text}]".PHP_EOL.PHP_EOL.
@@ -90,13 +98,20 @@ class TestResponse extends BaseTestResponse {
 
     /**
      * Assert the response does not include a specific element containing the given text.
+     * If an nth match is provided, only that will be checked otherwise all matching
+     * elements will be checked for the given text.
      * @return $this
      */
-    public function assertElementNotContains(string $selector, string $text)
+    public function assertElementNotContains(string $selector, string $text, ?int $nthMatch = null)
     {
         $elements = $this->crawler()->filter($selector);
         $matched = false;
         $pattern = $this->getEscapedPattern($text);
+
+        if (!is_null($nthMatch)) {
+            $elements = $elements->eq($nthMatch - 1);
+        }
+
         foreach ($elements as $element) {
             $element = new Crawler($element);
             if (preg_match("/$pattern/i", $element->html())) {
@@ -108,6 +123,7 @@ class TestResponse extends BaseTestResponse {
         PHPUnit::assertTrue(
             !$matched,
             'Found element of selector: '.PHP_EOL.PHP_EOL.
+            ($nthMatch ? ("at position {$nthMatch}".PHP_EOL.PHP_EOL) : '') .
             "[{$selector}]".PHP_EOL.PHP_EOL.
             'containing text'.PHP_EOL.PHP_EOL.
             "[{$text}]".PHP_EOL.PHP_EOL.
index 51fdfe70d56ebf8797feef5e0577a945b693a5e0..be3fc4ebdd1d4c5bdfb8eb3c78ce2afb297832be 100644 (file)
 <?php namespace Tests;
 
+use BookStack\Auth\User;
+use BookStack\Entities\Models\Page;
+use BookStack\Entities\Tools\PageContent;
+use BookStack\Facades\Theme;
+use BookStack\Theming\ThemeEvents;
 use File;
+use Illuminate\Http\Request;
+use Illuminate\Http\Response;
+use League\CommonMark\ConfigurableEnvironmentInterface;
 
 class ThemeTest extends TestCase
 {
     protected $themeFolderName;
     protected $themeFolderPath;
 
-    public function setUp(): void
+    public function test_translation_text_can_be_overridden_via_theme()
     {
-        parent::setUp();
+        $this->usingThemeFolder(function () {
+            $translationPath = theme_path('/lang/en');
+            File::makeDirectory($translationPath, 0777, true);
 
-        // Create a folder and configure a theme
-        $this->themeFolderName = 'testing_theme_' . rtrim(base64_encode(time()), "=");
-        config()->set('view.theme', $this->themeFolderName);
-        $this->themeFolderPath = theme_path('');
-        File::makeDirectory($this->themeFolderPath);
+            $customTranslations = '<?php
+            return [\'books\' => \'Sandwiches\'];
+        ';
+            file_put_contents($translationPath . '/entities.php', $customTranslations);
+
+            $homeRequest = $this->actingAs($this->getViewer())->get('/');
+            $homeRequest->assertElementContains('header nav', 'Sandwiches');
+        });
     }
 
-    public function tearDown(): void
+    public function test_theme_functions_file_used_and_app_boot_event_runs()
     {
-        // Cleanup the custom theme folder we created
-        File::deleteDirectory($this->themeFolderPath);
+        $this->usingThemeFolder(function ($themeFolder) {
+            $functionsFile = theme_path('functions.php');
+            app()->alias('cat', 'dog');
+            file_put_contents($functionsFile, "<?php\nTheme::listen(\BookStack\Theming\ThemeEvents::APP_BOOT, function(\$app) { \$app->alias('cat', 'dog');});");
+            $this->runWithEnv('APP_THEME', $themeFolder, function () {
+                $this->assertEquals('cat', $this->app->getAlias('dog'));
+            });
+        });
+    }
+
+    public function test_event_commonmark_environment_configure()
+    {
+        $callbackCalled = false;
+        $callback = function ($environment) use (&$callbackCalled) {
+            $this->assertInstanceOf(ConfigurableEnvironmentInterface::class, $environment);
+            $callbackCalled = true;
+            return $environment;
+        };
+        Theme::listen(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $callback);
 
-        parent::tearDown();
+        $page = Page::query()->first();
+        $content = new PageContent($page);
+        $content->setNewMarkdown('# test');
+
+        $this->assertTrue($callbackCalled);
     }
 
-    public function test_translation_text_can_be_overriden_via_theme()
+    public function test_event_web_middleware_before()
     {
-        $translationPath = theme_path('/lang/en');
-        File::makeDirectory($translationPath, 0777, true);
+        $callbackCalled = false;
+        $requestParam = null;
+        $callback = function ($request) use (&$callbackCalled, &$requestParam) {
+            $requestParam = $request;
+            $callbackCalled = true;
+        };
 
-        $customTranslations = '<?php
-            return [\'books\' => \'Sandwiches\'];
-        ';
-        file_put_contents($translationPath . '/entities.php', $customTranslations);
+        Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback);
+        $this->get('/login', ['Donkey' => 'cat']);
+
+        $this->assertTrue($callbackCalled);
+        $this->assertInstanceOf(Request::class, $requestParam);
+        $this->assertEquals('cat', $requestParam->header('donkey'));
+    }
+
+    public function test_event_web_middleware_before_return_val_used_as_response()
+    {
+        $callback = function (Request $request) {
+            return response('cat', 412);
+        };
+
+        Theme::listen(ThemeEvents::WEB_MIDDLEWARE_BEFORE, $callback);
+        $resp = $this->get('/login', ['Donkey' => 'cat']);
+        $resp->assertSee('cat');
+        $resp->assertStatus(412);
+    }
+
+    public function test_event_web_middleware_after()
+    {
+        $callbackCalled = false;
+        $requestParam = null;
+        $responseParam = null;
+        $callback = function ($request, Response $response) use (&$callbackCalled, &$requestParam, &$responseParam) {
+            $requestParam = $request;
+            $responseParam = $response;
+            $callbackCalled = true;
+            $response->header('donkey', 'cat123');
+        };
+
+        Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback);
+
+        $resp = $this->get('/login', ['Donkey' => 'cat']);
+        $this->assertTrue($callbackCalled);
+        $this->assertInstanceOf(Request::class, $requestParam);
+        $this->assertInstanceOf(Response::class, $responseParam);
+        $resp->assertHeader('donkey', 'cat123');
+    }
+
+    public function test_event_web_middleware_after_return_val_used_as_response()
+    {
+        $callback = function () {
+            return response('cat456', 443);
+        };
+
+        Theme::listen(ThemeEvents::WEB_MIDDLEWARE_AFTER, $callback);
+
+        $resp = $this->get('/login', ['Donkey' => 'cat']);
+        $resp->assertSee('cat456');
+        $resp->assertStatus(443);
+    }
+
+    public function test_event_auth_login_standard()
+    {
+        $args = [];
+        $callback = function (...$eventArgs) use (&$args) {
+            $args = $eventArgs;
+        };
 
-        $homeRequest = $this->actingAs($this->getViewer())->get('/');
-        $homeRequest->assertElementContains('header nav', 'Sandwiches');
+        Theme::listen(ThemeEvents::AUTH_LOGIN, $callback);
+        $this->post('/login', ['email' => '[email protected]', 'password' => 'password']);
+
+        $this->assertCount(2, $args);
+        $this->assertEquals('standard', $args[0]);
+        $this->assertInstanceOf(User::class, $args[1]);
+    }
+
+    public function test_event_auth_register_standard()
+    {
+        $args = [];
+        $callback = function (...$eventArgs) use (&$args) {
+            $args = $eventArgs;
+        };
+        Theme::listen(ThemeEvents::AUTH_REGISTER, $callback);
+        $this->setSettings(['registration-enabled' => 'true']);
+
+        $user = factory(User::class)->make();
+        $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
+
+        $this->assertCount(2, $args);
+        $this->assertEquals('standard', $args[0]);
+        $this->assertInstanceOf(User::class, $args[1]);
+    }
+
+    public function test_add_social_driver()
+    {
+        Theme::addSocialDriver('catnet', [
+            'client_id' => 'abc123',
+            'client_secret' => 'def456'
+        ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
+
+        $this->assertEquals('catnet', config('services.catnet.name'));
+        $this->assertEquals('abc123', config('services.catnet.client_id'));
+        $this->assertEquals(url('/login/service/catnet/callback'), config('services.catnet.redirect'));
+
+        $loginResp = $this->get('/login');
+        $loginResp->assertSee('login/service/catnet');
+    }
+
+    public function test_add_social_driver_uses_name_in_config_if_given()
+    {
+        Theme::addSocialDriver('catnet', [
+            'client_id' => 'abc123',
+            'client_secret' => 'def456',
+            'name' => 'Super Cat Name',
+        ], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
+
+        $this->assertEquals('Super Cat Name', config('services.catnet.name'));
+        $loginResp = $this->get('/login');
+        $loginResp->assertSee('Super Cat Name');
+    }
+
+
+    public function test_add_social_driver_allows_a_configure_for_redirect_callback_to_be_passed()
+    {
+        Theme::addSocialDriver(
+            'discord',
+            [
+                'client_id' => 'abc123',
+                'client_secret' => 'def456',
+                'name' => 'Super Cat Name',
+            ],
+            'SocialiteProviders\Discord\DiscordExtendSocialite@handle',
+            function ($driver) {
+                $driver->with(['donkey' => 'donut']);
+            }
+        );
+
+        $loginResp = $this->get('/login/service/discord');
+        $redirect = $loginResp->headers->get('location');
+        $this->assertStringContainsString('donkey=donut', $redirect);
+    }
+
+
+    protected function usingThemeFolder(callable $callback)
+    {
+        // Create a folder and configure a theme
+        $themeFolderName = 'testing_theme_' . rtrim(base64_encode(time()), "=");
+        config()->set('view.theme', $themeFolderName);
+        $themeFolderPath = theme_path('');
+        File::makeDirectory($themeFolderPath);
+
+        call_user_func($callback, $themeFolderName);
+
+        // Cleanup the custom theme folder we created
+        File::deleteDirectory($themeFolderPath);
     }
 
 }
\ No newline at end of file
index 1374b3aa9e288b3405c61fe2afe5834d3f40c481..0833ffbd8858c4a049ecd7354a58800d37d8b947 100644 (file)
@@ -59,16 +59,31 @@ class ConfigTest extends TestCase
         $this->assertStringNotContainsString('testing', $output);
     }
 
+    public function test_session_cookie_uses_sub_path_from_app_url()
+    {
+        $this->checkEnvConfigResult('APP_URL', 'https://example.com', 'session.path', '/');
+        $this->checkEnvConfigResult('APP_URL', 'https://a.com/b', 'session.path', '/b');
+        $this->checkEnvConfigResult('APP_URL', 'https://a.com/b/d/e', 'session.path', '/b/d/e');
+        $this->checkEnvConfigResult('APP_URL', '', 'session.path', '/');
+    }
+
+    public function test_saml2_idp_authn_context_string_parsed_as_space_separated_array()
+    {
+        $this->checkEnvConfigResult(
+            'SAML2_IDP_AUTHNCONTEXT',
+            'urn:federation:authentication:windows urn:federation:authentication:linux',
+            'saml2.onelogin.security.requestedAuthnContext',
+            ['urn:federation:authentication:windows', 'urn:federation:authentication:linux']
+        );
+    }
+
     /**
      * Set an environment variable of the given name and value
      * then check the given config key to see if it matches the given result.
      * Providing a null $envVal clears the variable.
-     * @param string $envName
-     * @param string|null $envVal
-     * @param string $configKey
-     * @param string $expectedResult
+     * @param mixed $expectedResult
      */
-    protected function checkEnvConfigResult(string $envName, $envVal, string $configKey, string $expectedResult)
+    protected function checkEnvConfigResult(string $envName, ?string $envVal, string $configKey, $expectedResult)
     {
         $this->runWithEnv($envName, $envVal, function() use ($configKey, $expectedResult) {
             $this->assertEquals($expectedResult, config($configKey));
index 1c736d672d977b8727c8ee5cf08b9f9ee5ba1538..95332565e5761673cdafa5489aecced690a7faaf 100644 (file)
@@ -14,7 +14,7 @@ class ImageTest extends TestCase
 
     public function test_image_upload()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $admin = $this->getAdmin();
         $this->actingAs($admin);
 
@@ -38,7 +38,7 @@ class ImageTest extends TestCase
 
     public function test_image_display_thumbnail_generation_does_not_increase_image_size()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $admin = $this->getAdmin();
         $this->actingAs($admin);
 
@@ -108,7 +108,7 @@ class ImageTest extends TestCase
 
     public function test_image_usage()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $editor = $this->getEditor();
         $this->actingAs($editor);
 
@@ -128,7 +128,7 @@ class ImageTest extends TestCase
 
     public function test_php_files_cannot_be_uploaded()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $admin = $this->getAdmin();
         $this->actingAs($admin);
 
@@ -136,7 +136,7 @@ class ImageTest extends TestCase
         $relPath = $this->getTestImagePath('gallery', $fileName);
         $this->deleteImage($relPath);
 
-        $file = $this->getTestImage($fileName);
+        $file = $this->newTestImageFromBase64('bad-php.base64', $fileName);
         $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
         $upload->assertStatus(302);
 
@@ -150,7 +150,7 @@ class ImageTest extends TestCase
 
     public function test_php_like_files_cannot_be_uploaded()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $admin = $this->getAdmin();
         $this->actingAs($admin);
 
@@ -158,28 +158,36 @@ class ImageTest extends TestCase
         $relPath = $this->getTestImagePath('gallery', $fileName);
         $this->deleteImage($relPath);
 
-        $file = $this->getTestImage($fileName);
+        $file = $this->newTestImageFromBase64('bad-phtml.base64', $fileName);
         $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
         $upload->assertStatus(302);
 
         $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
     }
 
-    public function test_files_with_double_extensions_cannot_be_uploaded()
+    public function test_files_with_double_extensions_will_get_sanitized()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $admin = $this->getAdmin();
         $this->actingAs($admin);
 
         $fileName = 'bad.phtml.png';
         $relPath = $this->getTestImagePath('gallery', $fileName);
-        $this->deleteImage($relPath);
+        $expectedRelPath = dirname($relPath) . '/bad-phtml.png';
+        $this->deleteImage($expectedRelPath);
 
-        $file = $this->getTestImage($fileName);
+        $file = $this->newTestImageFromBase64('bad-phtml-png.base64', $fileName);
         $upload = $this->withHeader('Content-Type', 'image/png')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
-        $upload->assertStatus(302);
+        $upload->assertStatus(200);
+
+        $lastImage = Image::query()->latest('id')->first();
+
+        $this->assertEquals('bad.phtml.png', $lastImage->name);
+        $this->assertEquals('bad-phtml.png', basename($lastImage->path));
+        $this->assertFileDoesNotExist(public_path($relPath), 'Uploaded image file name was not stripped of dots');
+        $this->assertFileExists(public_path($expectedRelPath));
 
-        $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded double extension file was uploaded but should have been stopped');
+        $this->deleteImage($lastImage->path);
     }
 
     public function test_url_entities_removed_from_filenames()
@@ -194,7 +202,7 @@ class ImageTest extends TestCase
         ];
         foreach ($badNames as $name) {
             $galleryFile = $this->getTestImage($name);
-            $page = Page::first();
+            $page = Page::query()->first();
             $badPath = $this->getTestImagePath('gallery', $name);
             $this->deleteImage($badPath);
 
@@ -219,7 +227,7 @@ class ImageTest extends TestCase
         config()->set('filesystems.images', 'local_secure');
         $this->asEditor();
         $galleryFile = $this->getTestImage('my-secure-test-upload.png');
-        $page = Page::first();
+        $page = Page::query()->first();
         $expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m') . '/my-secure-test-upload.png');
 
         $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
@@ -237,7 +245,7 @@ class ImageTest extends TestCase
         config()->set('filesystems.images', 'local_secure');
         $this->asEditor();
         $galleryFile = $this->getTestImage('my-secure-test-upload.png');
-        $page = Page::first();
+        $page = Page::query()->first();
         $expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m') . '/my-secure-test-upload.png');
 
         $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
@@ -274,7 +282,7 @@ class ImageTest extends TestCase
 
     public function test_image_delete()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $this->asAdmin();
         $imageName = 'first-image.png';
         $relPath = $this->getTestImagePath('gallery', $imageName);
@@ -296,7 +304,7 @@ class ImageTest extends TestCase
 
     public function test_image_delete_does_not_delete_similar_images()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $this->asAdmin();
         $imageName = 'first-image.png';
 
@@ -375,7 +383,7 @@ class ImageTest extends TestCase
 
     public function test_deleted_unused_images()
     {
-        $page = Page::first();
+        $page = Page::query()->first();
         $admin = $this->getAdmin();
         $this->actingAs($admin);
 
@@ -428,4 +436,4 @@ class ImageTest extends TestCase
         $this->deleteImage($relPath);
     }
 
-}
\ No newline at end of file
+}
index 64f26dea8a9be7c847909d0d192a780a2ed25f02..24c253802f23dd72ccc2d4252ba79334b2a105c2 100644 (file)
@@ -6,10 +6,9 @@ use Illuminate\Http\UploadedFile;
 trait UsesImages
 {
     /**
-     * Get the path to our basic test image.
-     * @return string
+     * Get the path to a file in the test-data-directory.
      */
-    protected function getTestImageFilePath(?string $fileName = null)
+    protected function getTestImageFilePath(?string $fileName = null): string
     {
         if (is_null($fileName)) {
             $fileName = 'test-image.png';
@@ -18,14 +17,27 @@ trait UsesImages
         return base_path('tests/test-data/' . $fileName);
     }
 
+    /**
+     * Creates a new temporary image file using the given name,
+     * with the content decoded from the given bas64 file name.
+     * Is generally used for testing sketchy files that could trip AV.
+     */
+    protected function newTestImageFromBase64(string $base64FileName, $imageFileName): UploadedFile
+    {
+        $imagePath = implode(DIRECTORY_SEPARATOR, [sys_get_temp_dir(), $imageFileName]);
+        $base64FilePath = $this->getTestImageFilePath($base64FileName);
+        $data = file_get_contents($base64FilePath);
+        $decoded = base64_decode($data);
+        file_put_contents($imagePath, $decoded);
+        return new UploadedFile($imagePath, $imageFileName, 'image/png', null, true);
+    }
+
     /**
      * Get a test image that can be uploaded
-     * @param $fileName
-     * @return UploadedFile
      */
-    protected function getTestImage($fileName, ?string $testDataFileName = null)
+    protected function getTestImage(string $fileName, ?string $testDataFileName = null): UploadedFile
     {
-        return new UploadedFile($this->getTestImageFilePath($testDataFileName), $fileName, 'image/png', 5238, null, true);
+        return new UploadedFile($this->getTestImageFilePath($testDataFileName), $fileName, 'image/png', null, true);
     }
 
     /**
@@ -88,9 +100,8 @@ trait UsesImages
 
     /**
      * Delete an uploaded image.
-     * @param $relPath
      */
-    protected function deleteImage($relPath)
+    protected function deleteImage(string $relPath)
     {
         $path = public_path($relPath);
         if (file_exists($path)) {
index 7ffc8f9db7085958b9ef5e5a80bc4bfed22a3022..49c49188b2451f0b74cf017210f0b5baafd23f80 100644 (file)
@@ -92,4 +92,17 @@ class UserPreferencesTest extends TestCase
         $home->assertDontSee('Dark Mode');
         $home->assertSee('Light Mode');
     }
+
+    public function test_dark_mode_defaults_to_config_option()
+    {
+        config()->set('setting-defaults.user.dark-mode-enabled', false);
+        $this->assertEquals(false, setting()->getForCurrentUser('dark-mode-enabled'));
+        $home = $this->get('/login');
+        $home->assertElementNotExists('.dark-mode');
+
+        config()->set('setting-defaults.user.dark-mode-enabled', true);
+        $this->assertEquals(true, setting()->getForCurrentUser('dark-mode-enabled'));
+        $home = $this->get('/login');
+        $home->assertElementExists('.dark-mode');
+    }
 }
\ No newline at end of file
index 27d97381e54393a8f469fcce6f47038cd3690dae..a5db83c48e78816ed4af15402f8c5185209272b5 100644 (file)
@@ -19,7 +19,7 @@ class UserProfileTest extends BrowserKitTest
     public function test_profile_page_shows_name()
     {
         $this->asAdmin()
-            ->visit('/user/' . $this->user->id)
+            ->visit('/user/' . $this->user->slug)
             ->see($this->user->name);
     }
 
@@ -28,7 +28,7 @@ class UserProfileTest extends BrowserKitTest
         $content = $this->createEntityChainBelongingToUser($this->user, $this->user);
 
         $this->asAdmin()
-            ->visit('/user/' . $this->user->id)
+            ->visit('/user/' . $this->user->slug)
             // Check the recently created page is shown
             ->see($content['page']->name)
             // Check the recently created chapter is shown
@@ -41,7 +41,7 @@ class UserProfileTest extends BrowserKitTest
     {
         $newUser = $this->getNewBlankUser();
 
-        $this->asAdmin()->visit('/user/' . $newUser->id)
+        $this->asAdmin()->visit('/user/' . $newUser->slug)
             ->see($newUser->name)
             ->seeInElement('#content-counts', '0 Books')
             ->seeInElement('#content-counts', '0 Chapters')
@@ -49,7 +49,7 @@ class UserProfileTest extends BrowserKitTest
 
         $this->createEntityChainBelongingToUser($newUser, $newUser);
 
-        $this->asAdmin()->visit('/user/' . $newUser->id)
+        $this->asAdmin()->visit('/user/' . $newUser->slug)
             ->see($newUser->name)
             ->seeInElement('#content-counts', '1 Book')
             ->seeInElement('#content-counts', '1 Chapter')
@@ -64,7 +64,7 @@ class UserProfileTest extends BrowserKitTest
         Activity::addForEntity($entities['book'], ActivityType::BOOK_UPDATE);
         Activity::addForEntity($entities['page'], ActivityType::PAGE_CREATE);
 
-        $this->asAdmin()->visit('/user/' . $newUser->id)
+        $this->asAdmin()->visit('/user/' . $newUser->slug)
             ->seeInElement('#recent-user-activity', 'updated book')
             ->seeInElement('#recent-user-activity', 'created page')
             ->seeInElement('#recent-user-activity', $entities['page']->name);
@@ -79,7 +79,7 @@ class UserProfileTest extends BrowserKitTest
         Activity::addForEntity($entities['page'], ActivityType::PAGE_CREATE);
 
         $this->asAdmin()->visit('/')->clickInElement('#recent-activity', $newUser->name)
-            ->seePageIs('/user/' . $newUser->id)
+            ->seePageIs('/user/' . $newUser->slug)
             ->see($newUser->name);
     }
 
diff --git a/tests/test-data/bad-php.base64 b/tests/test-data/bad-php.base64
new file mode 100644 (file)
index 0000000..550ce17
--- /dev/null
@@ -0,0 +1,10 @@
+/9j/4AAQSkZJRgABAQEBLAEsAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAEBAQEBAQEBAQEB
+AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBD
+AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+AQEBAQEBAQH/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACv/EABQBAQAA
+AAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAT/n/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgB
+AQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAA
+AAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAA
+AAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABAf/8QAFBEBAAAAAAAAAAAAAAAA
+AAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAA
+AAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k8P3BocCBlY2hvICdiYWRwaHAnOwo=
diff --git a/tests/test-data/bad-phtml-png.base64 b/tests/test-data/bad-phtml-png.base64
new file mode 100644 (file)
index 0000000..7fd9d8f
--- /dev/null
@@ -0,0 +1,3 @@
+iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA
+B3RJTUUH4gEcDCo5iYNs+gAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH
+AAAAFElEQVQI12O0jN/KgASYGFABqXwAZtoBV6Sl3hIAAAAASUVORK5CYII=
diff --git a/tests/test-data/bad-phtml.base64 b/tests/test-data/bad-phtml.base64
new file mode 100644 (file)
index 0000000..550ce17
--- /dev/null
@@ -0,0 +1,10 @@
+/9j/4AAQSkZJRgABAQEBLAEsAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAEBAQEBAQEBAQEB
+AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBD
+AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+AQEBAQEBAQH/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACv/EABQBAQAA
+AAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAT/n/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgB
+AQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAA
+AAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAA
+AAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABAf/8QAFBEBAAAAAAAAAAAAAAAA
+AAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAA
+AAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k8P3BocCBlY2hvICdiYWRwaHAnOwo=
diff --git a/tests/test-data/bad.php b/tests/test-data/bad.php
deleted file mode 100644 (file)
index 3b7c0f3..0000000
Binary files a/tests/test-data/bad.php and /dev/null differ
diff --git a/tests/test-data/bad.phtml b/tests/test-data/bad.phtml
deleted file mode 100644 (file)
index 3b7c0f3..0000000
Binary files a/tests/test-data/bad.phtml and /dev/null differ
diff --git a/tests/test-data/bad.phtml.png b/tests/test-data/bad.phtml.png
deleted file mode 100644 (file)
index dd15f6e..0000000
Binary files a/tests/test-data/bad.phtml.png and /dev/null differ