From 6c14c09880bc085752a0479c512a47fe08b76aeb Mon Sep 17 00:00:00 2001 From: Christopher Tran Date: Sat, 27 Oct 2018 16:14:19 -0400 Subject: [PATCH 01/43] Add ability to disable LDAP certificate validation --- .env.example | 2 ++ app/Auth/Access/LdapService.php | 6 ++++++ config/services.php | 1 + 3 files changed, 9 insertions(+) diff --git a/.env.example b/.env.example index eda20ea26..3ca612f64 100644 --- a/.env.example +++ b/.env.example @@ -76,6 +76,8 @@ LDAP_GROUP_ATTRIBUTE="memberOf" # Would you like to remove users from roles on BookStack if they do not match on LDAP # If false, the ldap groups-roles sync will only add users to roles LDAP_REMOVE_FROM_GROUPS=false +# Set this option to disable LDAPS Certificate Verification +LDAP_TLS_INSECURE=false # Mail settings MAIL_DRIVER=smtp diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index d3a177f8e..04af5b370 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -169,8 +169,14 @@ class LdapService } $hostName = $ldapServer[0] . ($hasProtocol?':':'') . $ldapServer[1]; $defaultPort = $ldapServer[0] === 'ldaps' ? 636 : 389; + $ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort); + // Check if TLS_INSECURE is set + if($this->config['tls_insecure']) { + $this->ldap->setOption($ldapConnection, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); + } + if ($ldapConnection === false) { throw new LdapException(trans('errors.ldap_cannot_connect')); } diff --git a/config/services.php b/config/services.php index 711040386..47fd2f2ff 100644 --- a/config/services.php +++ b/config/services.php @@ -148,6 +148,7 @@ return [ 'user_to_groups' => env('LDAP_USER_TO_GROUPS',false), 'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'), 'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false), + 'tls_insecure' => env('LDAP_TLS_INSECURE', false), ] ]; From 8e7f703af713c7c81bd1399d2f6e2518bee0d328 Mon Sep 17 00:00:00 2001 From: Christopher Tran Date: Sat, 27 Oct 2018 16:58:10 -0400 Subject: [PATCH 02/43] fix how the option is set, change handle to NULL --- app/Auth/Access/LdapService.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index 04af5b370..9e626bbac 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -170,13 +170,17 @@ class LdapService $hostName = $ldapServer[0] . ($hasProtocol?':':'') . $ldapServer[1]; $defaultPort = $ldapServer[0] === 'ldaps' ? 636 : 389; - $ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort); - - // Check if TLS_INSECURE is set + /* + * 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. + */ if($this->config['tls_insecure']) { - $this->ldap->setOption($ldapConnection, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); + $this->ldap->setOption(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); } + $ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort); + if ($ldapConnection === false) { throw new LdapException(trans('errors.ldap_cannot_connect')); } From 730cb78b455b75d1503008cbf39904898af3ffa1 Mon Sep 17 00:00:00 2001 From: Christopher Tran Date: Sat, 27 Oct 2018 17:05:46 -0400 Subject: [PATCH 03/43] switch spaces to tabs --- config/services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/services.php b/config/services.php index 47fd2f2ff..98b1fce8e 100644 --- a/config/services.php +++ b/config/services.php @@ -148,7 +148,7 @@ return [ 'user_to_groups' => env('LDAP_USER_TO_GROUPS',false), 'group_attribute' => env('LDAP_GROUP_ATTRIBUTE', 'memberOf'), 'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS',false), - 'tls_insecure' => env('LDAP_TLS_INSECURE', false), + 'tls_insecure' => env('LDAP_TLS_INSECURE', false), ] ]; From 5e6c039b08f3c0c90329201b4eb083e637405992 Mon Sep 17 00:00:00 2001 From: Vinrobot Date: Thu, 8 Nov 2018 09:14:11 +0100 Subject: [PATCH 04/43] Added config to change Gravatar URL --- .env.example | 5 +++++ app/Auth/UserRepo.php | 2 +- app/Uploads/ImageService.php | 14 ++++++++++---- config/services.php | 1 + 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index eda20ea26..06394f54f 100644 --- a/.env.example +++ b/.env.example @@ -61,6 +61,11 @@ DISCORD_APP_SECRET=false # External services such as Gravatar and Draw.IO DISABLE_EXTERNAL_SERVICES=false +# Default GRAVATAR_URL set to Gravatar service +GRAVATAR_URL=false +# To use a different service to get user's avatar like libravatar +# Possible placeholders: %{hash} %{size} %{email} +#GRAVATAR_URL=https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon # LDAP Settings LDAP_SERVER=false diff --git a/app/Auth/UserRepo.php b/app/Auth/UserRepo.php index 7c88badb8..abff7c641 100644 --- a/app/Auth/UserRepo.php +++ b/app/Auth/UserRepo.php @@ -251,7 +251,7 @@ class UserRepo } try { - $avatar = Images::saveUserGravatar($user); + $avatar = Images::saveUserGravatar($user, config('services.gravatar_url')); $user->avatar()->associate($avatar); $user->save(); return true; diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index f109db600..b65a476f4 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -281,16 +281,22 @@ class ImageService extends UploadService /** * Save a gravatar image and set a the profile image for a user. * @param \BookStack\Auth\User $user + * @param null|string $gravatarUrl * @param int $size * @return mixed * @throws Exception */ - public function saveUserGravatar(User $user, $size = 500) + public function saveUserGravatar(User $user, $gravatarUrl, $size = 500) { - $emailHash = md5(strtolower(trim($user->email))); - $url = 'https://www.gravatar.com/avatar/' . $emailHash . '?s=' . $size . '&d=identicon'; + if (!is_string($gravatarUrl) || empty($gravatarUrl)) { + $gravatarUrl = 'https://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'; + } + $email = strtolower(trim($user->email)); + $gravatarUrl = str_replace('%{hash}', md5($email), $gravatarUrl); + $gravatarUrl = str_replace('%{size}', $size, $gravatarUrl); + $gravatarUrl = str_replace('%{email}', urlencode($email), $gravatarUrl); $imageName = str_replace(' ', '-', $user->name . '-gravatar.png'); - $image = $this->saveNewFromUrl($url, 'user', $imageName); + $image = $this->saveNewFromUrl($gravatarUrl, 'user', $imageName); $image->created_by = $user->id; $image->updated_by = $user->id; $image->save(); diff --git a/config/services.php b/config/services.php index 711040386..b8f152124 100644 --- a/config/services.php +++ b/config/services.php @@ -19,6 +19,7 @@ return [ 'gravatar' => env('GRAVATAR', !env('DISABLE_EXTERNAL_SERVICES', false)), 'drawio' => env('DRAWIO', !env('DISABLE_EXTERNAL_SERVICES', false)), + 'gravatar_url' => env('GRAVATAR_URL', false), 'callback_url' => env('APP_URL', false), From 54b36cd30581d49382a4a58e752357a28955919c Mon Sep 17 00:00:00 2001 From: Christopher Wilkinson Date: Tue, 13 Nov 2018 13:43:20 +0000 Subject: [PATCH 05/43] Show users link in top nav if user is signed in and only manages users --- resources/views/base.blade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php index e6d0b7761..535548b3b 100644 --- a/resources/views/base.blade.php +++ b/resources/views/base.blade.php @@ -55,6 +55,9 @@ @if(signedInUser() && userCan('settings-manage')) @icon('settings'){{ trans('settings.settings') }} @endif + @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage')) + @icon('users'){{ trans('settings.users') }} + @endif @if(!signedInUser()) @icon('login') {{ trans('auth.log_in') }} @endif From d3d3e2ad3e2ce2cfff2672df9109ca15bfcf14e4 Mon Sep 17 00:00:00 2001 From: Abijeet Date: Sun, 18 Nov 2018 00:22:08 +0530 Subject: [PATCH 06/43] Added a default timeout of 60 seconds to dropzone. Fixes #876 --- resources/assets/js/vues/components/dropzone.js | 9 +++++++-- resources/lang/ar/errors.php | 1 + resources/lang/de/errors.php | 2 ++ resources/lang/en/errors.php | 1 + resources/lang/es/errors.php | 1 + resources/lang/es_AR/errors.php | 1 + resources/lang/fr/errors.php | 1 + resources/lang/it/errors.php | 1 + resources/lang/ja/errors.php | 1 + resources/lang/kr/errors.php | 1 + resources/lang/nl/errors.php | 1 + resources/lang/pl/errors.php | 1 + resources/lang/pt_BR/errors.php | 1 + resources/lang/ru/errors.php | 1 + resources/lang/sk/errors.php | 1 + resources/lang/sv/errors.php | 1 + resources/lang/zh_CN/errors.php | 3 ++- resources/lang/zh_TW/errors.php | 1 + 18 files changed, 26 insertions(+), 3 deletions(-) diff --git a/resources/assets/js/vues/components/dropzone.js b/resources/assets/js/vues/components/dropzone.js index 587ec2030..6cfcecb40 100644 --- a/resources/assets/js/vues/components/dropzone.js +++ b/resources/assets/js/vues/components/dropzone.js @@ -13,8 +13,9 @@ function mounted() { let container = this.$el; let _this = this; this._dz = new DropZone(container, { - addRemoveLinks: true, - dictRemoveFile: trans('components.image_upload_remove'), + addRemoveLinks: true, + dictRemoveFile: trans('components.image_upload_remove'), + timeout: +window.dropZoneTimeout || 60000, url: function() { return _this.uploadUrl; }, @@ -26,6 +27,10 @@ function mounted() { data.append('_token', token); let uploadedTo = typeof _this.uploadedTo === 'undefined' ? 0 : _this.uploadedTo; data.append('uploaded_to', uploadedTo); + + xhr.ontimeout = function (e) { + _this.$events.emit('error', trans('errors.file_upload_timeout')); + } }); dz.on('success', function (file, data) { diff --git a/resources/lang/ar/errors.php b/resources/lang/ar/errors.php index 019b1ac87..6be77a1b4 100644 --- a/resources/lang/ar/errors.php +++ b/resources/lang/ar/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => 'الخادم لا يسمح برفع ملفات بهذا الحجم. الرجاء محاولة الرفع بحجم أصغر.', 'image_upload_error' => 'حدث خطأ خلال رفع الصورة', 'image_upload_type_error' => 'صيغة الصورة المرفوعة غير صالحة', + 'file_upload_timeout' => 'انتهت عملية تحميل الملف.', // Attachments 'attachment_page_mismatch' => 'Page mismatch during attachment update', // جار البحث عن الترجمة الأنسب diff --git a/resources/lang/de/errors.php b/resources/lang/de/errors.php index df941acd0..dc95d1d2b 100644 --- a/resources/lang/de/errors.php +++ b/resources/lang/de/errors.php @@ -32,6 +32,8 @@ return [ 'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuchen Sie es mit einer kleineren Datei.', 'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.', 'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.', + 'file_upload_timeout' => 'Der Upload der Datei ist abgelaufen.', + // Attachments 'attachment_page_mismatch' => 'Die Seite stimmte nach dem Hochladen des Anhangs nicht überein.', 'attachment_not_found' => 'Anhang konnte nicht gefunden werden.', diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php index fb09841cf..f2ed4c17f 100644 --- a/resources/lang/en/errors.php +++ b/resources/lang/en/errors.php @@ -38,6 +38,7 @@ return [ '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.', // Attachments 'attachment_page_mismatch' => 'Page mismatch during attachment update', diff --git a/resources/lang/es/errors.php b/resources/lang/es/errors.php index b98f3aea8..9b2df45ff 100644 --- a/resources/lang/es/errors.php +++ b/resources/lang/es/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => 'El servidor no permite la subida de ficheros de este tamaño. Intente subir un fichero de menor tamaño.', 'image_upload_error' => 'Ha ocurrido un error al subir la imagen', 'image_upload_type_error' => 'El tipo de imagen que se quiere subir no es válido', + 'file_upload_timeout' => 'La carga del archivo ha caducado.', // Attachments 'attachment_page_mismatch' => 'Página no coincidente durante la subida del adjunto ', diff --git a/resources/lang/es_AR/errors.php b/resources/lang/es_AR/errors.php index 46f69c6f0..2876274f0 100644 --- a/resources/lang/es_AR/errors.php +++ b/resources/lang/es_AR/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => 'El servidor no permite subir archivos de este tamaño. Por favor intente un tamaño menor.', 'image_upload_error' => 'Ha ocurrido un error al subir la imagen', 'image_upload_error' => 'Ha ocurrido un error al subir la imagen', 'image_upload_type_error' => 'El tipo de imagen subida es inválido.', + 'file_upload_timeout' => 'La carga del archivo ha caducado.', // Attachments 'attachment_page_mismatch' => 'Página no coincidente durante la subida del adjunto ', diff --git a/resources/lang/fr/errors.php b/resources/lang/fr/errors.php index b05289070..e758f6e5e 100644 --- a/resources/lang/fr/errors.php +++ b/resources/lang/fr/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => 'Le serveur n\'autorise pas l\'envoi d\'un fichier de cette taille. Veuillez essayer avec une taille de fichier réduite.', 'image_upload_error' => 'Une erreur est survenue pendant l\'envoi de l\'image', 'image_upload_type_error' => 'LE format de l\'image envoyée n\'est pas valide', + 'file_upload_timeout' => 'Le téléchargement du fichier a expiré.', // Attachments 'attachment_page_mismatch' => 'Page incorrecte durant la mise à jour du fichier joint', diff --git a/resources/lang/it/errors.php b/resources/lang/it/errors.php index e81489ff4..17be482c7 100755 --- a/resources/lang/it/errors.php +++ b/resources/lang/it/errors.php @@ -37,6 +37,7 @@ return [ 'server_upload_limit' => 'Il server non permette un upload di questa grandezza. Prova con un file più piccolo.', 'image_upload_error' => 'C\'è stato un errore caricando l\'immagine', 'image_upload_type_error' => 'Il tipo di immagine in upload non è valido', + 'file_upload_timeout' => 'Il caricamento del file è scaduto.', // Attachments 'attachment_page_mismatch' => 'Page mismatch during attachment update', diff --git a/resources/lang/ja/errors.php b/resources/lang/ja/errors.php index 5905237fd..8c13d4474 100644 --- a/resources/lang/ja/errors.php +++ b/resources/lang/ja/errors.php @@ -35,6 +35,7 @@ return [ 'cannot_create_thumbs' => 'このサーバはサムネイルを作成できません。GD PHP extensionがインストールされていることを確認してください。', 'server_upload_limit' => 'このサイズの画像をアップロードすることは許可されていません。ファイルサイズを小さくし、再試行してください。', 'image_upload_error' => '画像アップロード時にエラーが発生しました。', + 'file_upload_timeout' => 'ファイルのアップロードがタイムアウトしました。', // Attachments 'attachment_page_mismatch' => '添付を更新するページが一致しません', diff --git a/resources/lang/kr/errors.php b/resources/lang/kr/errors.php index 0a7689b9d..59dbfda2b 100644 --- a/resources/lang/kr/errors.php +++ b/resources/lang/kr/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => '해당 크기의 파일을 업로드하는것이 서버에서 제한됩니다. 파일 사이즈를 작게 줄이거나 서버 설정을 변경하세요.', 'image_upload_error' => '이미지를 업로드하는 중에 오류가 발생했습니다.', 'image_upload_type_error' => '업로드중인 이미지 유형이 잘못되었습니다.', + 'file_upload_timeout' => '파일 업로드가 시간 초과되었습니다.', // Attachments 'attachment_page_mismatch' => '첨부 파일 업데이트 중 페이지 불일치하였습니다.', diff --git a/resources/lang/nl/errors.php b/resources/lang/nl/errors.php index 7c0b74663..19dbbd8d1 100644 --- a/resources/lang/nl/errors.php +++ b/resources/lang/nl/errors.php @@ -35,6 +35,7 @@ return [ 'cannot_create_thumbs' => 'De server kon geen thumbnails maken. Controleer of je de GD PHP extensie geïnstalleerd hebt.', 'server_upload_limit' => 'Het afbeeldingsformaat is te groot. Probeer een kleinere bestandsgrootte.', 'image_upload_error' => 'Er ging iets fout bij het uploaden van de afbeelding', + 'file_upload_timeout' => 'Het uploaden van het bestand is verlopen.', // Attachments 'attachment_page_mismatch' => 'Bij het bijwerken van de bijlage bleek de pagina onjuist', diff --git a/resources/lang/pl/errors.php b/resources/lang/pl/errors.php index 633bf7a2d..cb70c75cb 100644 --- a/resources/lang/pl/errors.php +++ b/resources/lang/pl/errors.php @@ -35,6 +35,7 @@ return [ 'cannot_create_thumbs' => 'Serwer nie może utworzyć miniaturek. Upewnij się że rozszerzenie GD PHP zostało zainstalowane.', 'server_upload_limit' => 'Serwer nie pozwala na przyjęcie pliku o tym rozmiarze. Spróbuj udostępnić coś o mniejszym rozmiarze.', 'image_upload_error' => 'Wystąpił błąd podczas udostępniania obrazka', + 'file_upload_timeout' => 'Przesyłanie pliku przekroczyło limit czasu.', // Attachments 'attachment_page_mismatch' => 'Niezgodność stron podczas aktualizacji załącznika', diff --git a/resources/lang/pt_BR/errors.php b/resources/lang/pt_BR/errors.php index 0c00a66a0..023254182 100644 --- a/resources/lang/pt_BR/errors.php +++ b/resources/lang/pt_BR/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => 'O servidor não permite o upload de arquivos com esse tamanho. Por favor, tente fazer o upload de arquivos de menor tamanho.', 'image_upload_error' => 'Um erro aconteceu enquanto o servidor tentava efetuar o upload da imagem', 'image_upload_type_error' => 'O tipo de imagem que está sendo feito upload é inválido', + 'file_upload_timeout' => 'O upload do arquivo expirou.', // Attachments 'attachment_page_mismatch' => 'Erro de \'Page mismatch\' durante a atualização do anexo', diff --git a/resources/lang/ru/errors.php b/resources/lang/ru/errors.php index 05d6d7921..6286425a9 100644 --- a/resources/lang/ru/errors.php +++ b/resources/lang/ru/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => 'Сервер не позволяет загружать файлы такого размера. Пожалуйста, попробуйте файл меньше.', 'image_upload_error' => 'Произошла ошибка при загрузке изображения.', 'image_upload_type_error' => 'Неправильный тип загружаемого изображения', + 'file_upload_timeout' => 'Выгрузка файла закончилась.', // Attachments 'attachment_page_mismatch' => 'Несоответствие страницы во время обновления вложения', diff --git a/resources/lang/sk/errors.php b/resources/lang/sk/errors.php index d4c7b7a3a..05277014b 100644 --- a/resources/lang/sk/errors.php +++ b/resources/lang/sk/errors.php @@ -35,6 +35,7 @@ return [ 'cannot_create_thumbs' => 'Server nedokáže vytvoriť náhľady. Skontrolujte prosím, či máte nainštalované GD rozšírenie PHP.', 'server_upload_limit' => 'Server nedovoľuje nahrávanie súborov s takouto veľkosťou. Skúste prosím menší súbor.', 'image_upload_error' => 'Pri nahrávaní obrázka nastala chyba', + 'file_upload_timeout' => 'Nahrávanie súboru vypršalo.', // Attachments 'attachment_page_mismatch' => 'Page mismatch during attachment update', diff --git a/resources/lang/sv/errors.php b/resources/lang/sv/errors.php index 37526802e..3c0659e83 100644 --- a/resources/lang/sv/errors.php +++ b/resources/lang/sv/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => 'Servern tillåter inte så här stora filer. Prova en mindre fil.', 'image_upload_error' => 'Ett fel inträffade vid uppladdningen', 'image_upload_type_error' => 'Filtypen du försöker ladda upp är ogiltig', + 'file_upload_timeout' => 'Filuppladdningen har tagits ut.', // Attachments 'attachment_page_mismatch' => 'Fel i sidmatchning vid uppdatering av bilaga', diff --git a/resources/lang/zh_CN/errors.php b/resources/lang/zh_CN/errors.php index ebc26d8a0..aa1c648d4 100644 --- a/resources/lang/zh_CN/errors.php +++ b/resources/lang/zh_CN/errors.php @@ -37,7 +37,8 @@ return [ 'server_upload_limit' => '服务器不允许上传此大小的文件。 请尝试较小的文件。', 'uploaded' => 'The server does not allow uploads of this size. Please try a smaller file size.', 'image_upload_error' => '上传图片时发生错误', - 'image_upload_type_error' => '上传的图像类型无效', + 'image_upload_type_error' => '上传的图像类型无效', + 'file_upload_timeout' => '文件上传已超时。', // Attachments 'attachment_page_mismatch' => '附件更新期间的页面不匹配', diff --git a/resources/lang/zh_TW/errors.php b/resources/lang/zh_TW/errors.php index de3d8c2c0..2e8050cde 100644 --- a/resources/lang/zh_TW/errors.php +++ b/resources/lang/zh_TW/errors.php @@ -38,6 +38,7 @@ return [ 'uploaded' => '上傳的檔案大小超過伺服器允許上限。請嘗試較小的檔案。', 'image_upload_error' => '上傳圖片時發生錯誤', 'image_upload_type_error' => '上傳圖片類型錯誤', + 'file_upload_timeout' => '文件上傳已超時。', // Attachments 'attachment_page_mismatch' => '附件更新期間的頁面不符合', From b7915cc7b003dc5cc44ce4e0cc835e41327955c9 Mon Sep 17 00:00:00 2001 From: qianmengnet Date: Mon, 26 Nov 2018 08:47:49 +0800 Subject: [PATCH 07/43] Add anchor link to "Created Content" on the "View Profile" Add 3 anchor link to "Created Content" on the "View Profile" page and click to jump to the page section --- resources/views/users/profile.blade.php | 30 +++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index bd63ce938..c4a8f6323 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -37,21 +37,27 @@
{{ trans('entities.profile_created_content') }}
-
- @icon('book') {{ trans_choice('entities.x_books', $assetCounts['books']) }} -
-
- @icon('chapter') {{ trans_choice('entities.x_chapters', $assetCounts['chapters']) }} -
-
- @icon('page') {{ trans_choice('entities.x_pages', $assetCounts['pages']) }} -
+ +
+ @icon('book') {{ trans_choice('entities.x_books', $assetCounts['books']) }} +
+
+ +
+ @icon('chapter') {{ trans_choice('entities.x_chapters', $assetCounts['chapters']) }} +
+
+ +
+ @icon('page') {{ trans_choice('entities.x_pages', $assetCounts['pages']) }} +
+

- +

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

@if (count($recentlyCreated['pages']) > 0) @include('partials/entity-list', ['entities' => $recentlyCreated['pages']]) @@ -60,7 +66,7 @@ @endif
- +

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

@if (count($recentlyCreated['chapters']) > 0) @include('partials/entity-list', ['entities' => $recentlyCreated['chapters']]) @@ -69,7 +75,7 @@ @endif
- +

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

@if (count($recentlyCreated['books']) > 0) @include('partials/entity-list', ['entities' => $recentlyCreated['books']]) From 3c796b1ae78df74cf5412fa1d6eea718c932704b Mon Sep 17 00:00:00 2001 From: qianmengnet Date: Mon, 26 Nov 2018 09:05:38 +0800 Subject: [PATCH 08/43] Add "register" to nav. Add "register" to nav.You need to click "login" to find register, which is not convenient for people who are not familiar with the app. --- resources/views/base.blade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php index e6d0b7761..cd8063e12 100644 --- a/resources/views/base.blade.php +++ b/resources/views/base.blade.php @@ -57,6 +57,9 @@ @endif @if(!signedInUser()) @icon('login') {{ trans('auth.log_in') }} + @if(setting('registration-enabled', false)) + @icon('new-user') {{ trans('auth.sign_up') }} + @endif @endif @if(signedInUser()) From e9e3e8b6b1cabe620046cbab1b10e0b7d32a3490 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 11 Dec 2018 14:54:34 +0000 Subject: [PATCH 09/43] Added npm install details Closes #1174 --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 245460f9a..08da9c539 100644 --- a/readme.md +++ b/readme.md @@ -25,11 +25,14 @@ In regards to development philosophy, BookStack has a relaxed, open & positive a All development on BookStack is currently done on the master branch. When it's time for a release the master branch is merged into release with built & minified CSS & JS then tagged at its version. Here are the current development requirements: -* [Node.js](https://nodejs.org/en/) v6.9+ +* [Node.js](https://nodejs.org/en/) v10.0+ SASS is used to help the CSS development and the JavaScript is run through babel to allow for writing ES6 code. This is done using webpack. To run the build task you can use the following commands: ``` bash +# Install NPM Dependencies +npm install + # Build assets for development npm run build From a2acd063f3883a667e60c3cde78426f5f767d50c Mon Sep 17 00:00:00 2001 From: ezzra Date: Tue, 11 Dec 2018 19:39:16 +0100 Subject: [PATCH 10/43] add german informal language --- config/app.php | 2 +- resources/lang/de_informal/activities.php | 23 ++++++++ resources/lang/de_informal/auth.php | 45 ++++++++++++++++ resources/lang/de_informal/common.php | 32 +++++++++++ resources/lang/de_informal/components.php | 15 ++++++ resources/lang/de_informal/entities.php | 66 +++++++++++++++++++++++ resources/lang/de_informal/errors.php | 36 +++++++++++++ resources/lang/de_informal/pagination.php | 20 +++++++ resources/lang/de_informal/passwords.php | 19 +++++++ resources/lang/de_informal/settings.php | 41 ++++++++++++++ resources/lang/de_informal/validation.php | 41 ++++++++++++++ resources/lang/en/settings.php | 3 +- 12 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 resources/lang/de_informal/activities.php create mode 100644 resources/lang/de_informal/auth.php create mode 100644 resources/lang/de_informal/common.php create mode 100644 resources/lang/de_informal/components.php create mode 100644 resources/lang/de_informal/entities.php create mode 100644 resources/lang/de_informal/errors.php create mode 100644 resources/lang/de_informal/pagination.php create mode 100644 resources/lang/de_informal/passwords.php create mode 100644 resources/lang/de_informal/settings.php create mode 100644 resources/lang/de_informal/validation.php diff --git a/config/app.php b/config/app.php index b514263d1..5209b5372 100755 --- a/config/app.php +++ b/config/app.php @@ -84,7 +84,7 @@ return [ */ 'locale' => env('APP_LANG', 'en'), - 'locales' => ['en', 'ar', 'de', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'], + 'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'], /* |-------------------------------------------------------------------------- diff --git a/resources/lang/de_informal/activities.php b/resources/lang/de_informal/activities.php new file mode 100644 index 000000000..4f6dc30e6 --- /dev/null +++ b/resources/lang/de_informal/activities.php @@ -0,0 +1,23 @@ + 'Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.', + /** + * Login & Register + */ + 'ldap_email_hint' => 'Bitte gib eine E-Mail-Adresse ein, um diese mit dem Account zu nutzen.', + 'register_confirm' => 'Bitte prüfe Deinen Posteingang und bestätig die Registrierung.', + 'registration_email_domain_invalid' => 'Du kannst dich mit dieser E-Mail nicht registrieren.', + 'register_success' => 'Vielen Dank für Deine Registrierung! Die Daten sind gespeichert und Du bist angemeldet.', + /** + * Password Reset + */ + 'reset_password_send_instructions' => 'Bitte gib Deine E-Mail-Adresse ein. Danach erhältst Du eine E-Mail mit einem Link zum Zurücksetzen Deines Passwortes.', + 'reset_password_sent_success' => 'Eine E-Mail mit dem Link zum Zurücksetzen Deines Passwortes wurde an :email gesendet.', + 'reset_password_success' => 'Dein Passwort wurde erfolgreich zurückgesetzt.', + 'email_reset_text' => 'Du erhältsts diese E-Mail, weil jemand versucht hat, Dein Passwort zurückzusetzen.', + 'email_reset_not_requested' => 'Wenn Du das nicht warst, brauchst Du nichts weiter zu tun.', + /** + * Email Confirmation + */ + 'email_confirm_subject' => 'Bestätige Deine E-Mail-Adresse für :appName', + 'email_confirm_greeting' => 'Danke, dass Du dich für :appName registrierst hast!', + 'email_confirm_text' => 'Bitte bestätige Deine E-Mail-Adresse, indem Du auf die Schaltfläche klickst:', + 'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung Deine E-Mail-Adresse nicht versandt werden. Bitte kontaktiere den Systemadministrator!', + 'email_confirm_success' => 'Deine E-Mail-Adresse wurde bestätigt!', + 'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfe Deinen Posteingang.', + 'email_not_confirmed_text' => 'Deine E-Mail-Adresse ist bisher nicht bestätigt.', + 'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die Du nach der Registrierung erhalten hast.', + 'email_not_confirmed_resend' => 'Wenn Du die E-Mail nicht erhalten hast, kannst Du die Nachricht erneut anfordern. Fülle hierzu bitte das folgende Formular aus:', +]; + +return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/common.php b/resources/lang/de_informal/common.php new file mode 100644 index 000000000..933b3e30b --- /dev/null +++ b/resources/lang/de_informal/common.php @@ -0,0 +1,32 @@ + 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffne die folgende URL in Deinem Browser:', +]; + +return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/components.php b/resources/lang/de_informal/components.php new file mode 100644 index 000000000..651974c88 --- /dev/null +++ b/resources/lang/de_informal/components.php @@ -0,0 +1,15 @@ + 'Bitte klicke erneut auf löschen, wenn Du dieses Bild wirklich entfernen möchtest.', + 'image_dropzone' => 'Ziehe Bilder hierher oder klicke hier, um ein Bild auszuwählen', + /** + * Code editor + */ +]; + +return array_replace($de_formal, $de_informal); \ No newline at end of file diff --git a/resources/lang/de_informal/entities.php b/resources/lang/de_informal/entities.php new file mode 100644 index 000000000..232ab51ec --- /dev/null +++ b/resources/lang/de_informal/entities.php @@ -0,0 +1,66 @@ + 'Du hast bisher keine Seiten angesehen.', + 'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.', + 'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.', + /** + * Permissions and restrictions + */ + + /** + * Search + */ + + /** + * Books + */ + 'books_delete_confirmation' => 'Bist Du sicher, dass Du dieses Buch löschen möchtest?', + /** + * Chapters + */ + 'chapters_delete_confirm' => 'Bist Du sicher, dass Du dieses Kapitel löschen möchtest?', + /** + * Pages + */ + 'pages_delete_confirm' => 'Bist Du sicher, dass Du diese Seite löschen möchtest?', + 'pages_delete_draft_confirm' => 'Bist Du sicher, dass Du diesen Seitenentwurf löschen möchtest?', + 'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung Deiner Änderungen ein', + 'pages_editing_draft_notification' => 'Du bearbeitest momenten einen Entwurf, der zuletzt :timeDiff gespeichert wurde.', + 'pages_draft_edit_active' => [ + 'start_a' => ':count Benutzer bearbeiten derzeit diese Seite.', + 'start_b' => ':userName bearbeitet jetzt diese Seite.', + 'time_a' => 'seit die Seiten zuletzt aktualisiert wurden.', + 'time_b' => 'in den letzten :minCount Minuten', + 'message' => ':start :time. Achte darauf, keine Änderungen von anderen Benutzern zu überschreiben!', + ], + /** + * Editor sidebar + */ + 'tags_explain' => "Füge Schlagwörter hinzu, um ihren Inhalt zu kategorisieren.\nDu kannst einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.", + 'attachments_explain' => 'Du kannst auf Deiner Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.', + 'attachments_delete_confirm' => 'Klicke erneut auf löschen, um diesen Anhang zu entfernen.', + 'attachments_dropzone' => 'Ziehe Dateien hierher oder klicke hier, um eine Datei auszuwählen', + 'attachments_explain_link' => 'Wenn Du keine Datei hochladen möchtest, kannst Du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.', + 'attachments_edit_drop_upload' => 'Ziehe Dateien hierher, um diese hochzuladen und zu überschreiben', + /** + * Profile View + */ + + /** + * Comments + */ + 'comment_placeholder' => 'Gib hier Deine Kommentare ein (Markdown unterstützt)', + 'comment_delete_confirm' => 'Möchtst Du diesen Kommentar wirklich löschen?', + + /** + * Revision + */ + 'revision_delete_confirm' => 'Bist Du sicher, dass Du diese Revision löschen möchtest?', +]; + +return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/errors.php b/resources/lang/de_informal/errors.php new file mode 100644 index 000000000..8f8d24ea8 --- /dev/null +++ b/resources/lang/de_informal/errors.php @@ -0,0 +1,36 @@ + 'Du hast keine Berechtigung, auf diese Seite zuzugreifen.', + 'permissionJson' => 'Du hast keine Berechtigung, die angeforderte Aktion auszuführen.', + // Auth + 'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melde dich an.', + 'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich erneut.', + 'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melde dich mit dem :socialAccount-Konto an.', + 'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.', + 'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.', + 'social_account_register_instructions' => 'Wenn Du bisher kein Social-Media Konto besitzt, kannst Du ein solches Konto mit der :socialAccount Option anlegen.', + // System + 'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stelle sicher, dass dieser Ordner auf dem Server beschreibbar ist.', + 'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.', + 'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.', + // Attachments + // Pages + 'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.', + 'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.', + // Entities + // Users + 'users_cannot_delete_only_admin' => 'Du kannst den einzigen Administrator nicht löschen.', + 'users_cannot_delete_guest' => 'Du kannst den Gast-Benutzer nicht löschen', + // Roles + // Comments + // Error pages + 'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.', +]; + +return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/pagination.php b/resources/lang/de_informal/pagination.php new file mode 100644 index 000000000..0feccda8e --- /dev/null +++ b/resources/lang/de_informal/pagination.php @@ -0,0 +1,20 @@ + 'Wähle den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.', + 'app_primary_color_desc' => "Dies sollte ein HEX Wert sein.\nWenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.", + '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_books' => 'Oder wähle die Buch-Übersicht als Startseite. Das wird die Seiten-Auswahl überschreiben.', + /** + * Registration settings + */ + + /** + * Maintenance settings + */ + '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_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?', + + /** + * Role settings + */ + 'role_delete_confirm' => 'Du möchtest die Rolle ":roleName" löschen.', + 'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die Du diesen Benutzern zuordnen möchtest.', + 'role_delete_sure' => 'Bist Du sicher, dass Du diese Rolle löschen möchtest?', + /** + * Users + */ + 'users_password_warning' => 'Fülle die folgenden Felder nur aus, wenn Du Dein Passwort ändern möchtest:', + 'users_delete_confirm' => 'Bist Du sicher, dass Du diesen Benutzer löschen möchtest?', + 'users_social_accounts_info' => 'Hier kannst Du andere Social-Media-Konten für eine schnellere und einfachere Anmeldung verknüpfen. Wenn Du ein Social-Media Konto löschst, bleibt der Zugriff erhalten. Entferne in diesem Falle die Berechtigung in Deinen Profil-Einstellungen des verknüpften Social-Media-Kontos.', +]; + +return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/validation.php b/resources/lang/de_informal/validation.php new file mode 100644 index 000000000..e2078ed61 --- /dev/null +++ b/resources/lang/de_informal/validation.php @@ -0,0 +1,41 @@ + [ 'en' => 'English', 'ar' => 'العربية', - 'de' => 'Deutsch', + 'de' => 'Deutsch (Sie)', + 'de_informal' => 'Deutsch (Du)', 'es' => 'Español', 'es_AR' => 'Español Argentina', 'fr' => 'Français', From 1b01d65965a74f433c7deef0cfa3e7216c1740e8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 11 Dec 2018 23:26:43 +0000 Subject: [PATCH 11/43] Updated readme with phpunit version, removed old translations line --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 08da9c539..e6fd59e98 100644 --- a/readme.md +++ b/readme.md @@ -43,7 +43,7 @@ npm run production npm run dev ``` -BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. To use you will need PHPUnit installed and accessible via command line. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the following database name, user name and password defined as `bookstack-test`. You will have to create that database and credentials before testing. +BookStack has many integration tests that use Laravel's built-in testing capabilities which makes use of PHPUnit. To use you will need PHPUnit 6 installed and accessible via command line, Directly running the composer-installed version will not work. There is a `mysql_testing` database defined within the app config which is what is used by PHPUnit. This database is set with the following database name, user name and password defined as `bookstack-test`. You will have to create that database and credentials before testing. The testing database will also need migrating and seeding beforehand. This can be done with the following commands: @@ -56,7 +56,7 @@ Once done you can run `phpunit` in the application root directory to run all tes ## Translations -As part of BookStack v0.14 support for translations has been built in. All text strings can be found in the `resources/lang` folder where each language option has its own folder. To add a new language you should copy the `en` folder to an new folder (eg. `fr` for french) then go through and translate all text strings in those files, leaving the keys and file-names intact. If a language string is missing then the `en` translation will be used. To show the language option in the user preferences language drop-down you will need to add your language to the options found at the bottom of the `resources/lang/en/settings.php` file. A system-wide language can also be set in the `.env` file like so: `APP_LANG=en`. +All text strings can be found in the `resources/lang` folder where each language option has its own folder. To add a new language you should copy the `en` folder to an new folder (eg. `fr` for french) then go through and translate all text strings in those files, leaving the keys and file-names intact. If a language string is missing then the `en` translation will be used. To show the language option in the user preferences language drop-down you will need to add your language to the options found at the bottom of the `resources/lang/en/settings.php` file. A system-wide language can also be set in the `.env` file like so: `APP_LANG=en`. You will also need to add the language to the `locales` array in the `config/app.php` file. @@ -73,7 +73,7 @@ php resources/lang/check.php pt_BR Some strings have colon-prefixed variables in such as `:userName`. Leave these values as they are as they will be replaced at run-time. -## Contributing & Maintenence +## Contributing & Maintenance Feel free to create issues to request new features or to report bugs and problems. Just please follow the template given when creating the issue. From f943f0d401e7e4e9e3325a2f9c4e090aca70a307 Mon Sep 17 00:00:00 2001 From: ezzra Date: Wed, 12 Dec 2018 10:37:24 +0100 Subject: [PATCH 12/43] de_informal - remove comments from unused lines --- resources/lang/de_informal/activities.php | 15 ----------- resources/lang/de_informal/common.php | 21 --------------- resources/lang/de_informal/components.php | 3 --- resources/lang/de_informal/entities.php | 13 --------- resources/lang/de_informal/errors.php | 7 ----- resources/lang/de_informal/pagination.php | 12 --------- resources/lang/de_informal/passwords.php | 11 -------- resources/lang/de_informal/settings.php | 5 ---- resources/lang/de_informal/validation.php | 33 ----------------------- 9 files changed, 120 deletions(-) diff --git a/resources/lang/de_informal/activities.php b/resources/lang/de_informal/activities.php index 4f6dc30e6..79595ac4b 100644 --- a/resources/lang/de_informal/activities.php +++ b/resources/lang/de_informal/activities.php @@ -2,21 +2,6 @@ $de_formal = (include resource_path() . '/lang/de/' . basename(__FILE__)); $de_informal = [ - - /** - * Activity text strings. - * Is used for all the text within activity logs & notifications. - */ - - // Pages - - // Chapters - - // Books - - // Bookshelves - - // Other ]; diff --git a/resources/lang/de_informal/common.php b/resources/lang/de_informal/common.php index 933b3e30b..9121b18ca 100644 --- a/resources/lang/de_informal/common.php +++ b/resources/lang/de_informal/common.php @@ -2,27 +2,6 @@ $de_formal = (include resource_path() . '/lang/de/' . basename(__FILE__)); $de_informal = [ - - /** - * Buttons - */ - - /** - * Form Labels - */ - - /** - * Actions - */ - - /** - * Misc - */ - - /** - * Header - */ - /** * Email Content */ diff --git a/resources/lang/de_informal/components.php b/resources/lang/de_informal/components.php index 651974c88..4027da46e 100644 --- a/resources/lang/de_informal/components.php +++ b/resources/lang/de_informal/components.php @@ -7,9 +7,6 @@ $de_informal = [ */ 'image_delete_confirm' => 'Bitte klicke erneut auf löschen, wenn Du dieses Bild wirklich entfernen möchtest.', 'image_dropzone' => 'Ziehe Bilder hierher oder klicke hier, um ein Bild auszuwählen', - /** - * Code editor - */ ]; return array_replace($de_formal, $de_informal); \ No newline at end of file diff --git a/resources/lang/de_informal/entities.php b/resources/lang/de_informal/entities.php index 232ab51ec..d50b0029d 100644 --- a/resources/lang/de_informal/entities.php +++ b/resources/lang/de_informal/entities.php @@ -8,14 +8,6 @@ $de_informal = [ 'no_pages_viewed' => 'Du hast bisher keine Seiten angesehen.', 'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.', 'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.', - /** - * Permissions and restrictions - */ - - /** - * Search - */ - /** * Books */ @@ -47,16 +39,11 @@ $de_informal = [ 'attachments_dropzone' => 'Ziehe Dateien hierher oder klicke hier, um eine Datei auszuwählen', 'attachments_explain_link' => 'Wenn Du keine Datei hochladen möchtest, kannst Du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.', 'attachments_edit_drop_upload' => 'Ziehe Dateien hierher, um diese hochzuladen und zu überschreiben', - /** - * Profile View - */ - /** * Comments */ 'comment_placeholder' => 'Gib hier Deine Kommentare ein (Markdown unterstützt)', 'comment_delete_confirm' => 'Möchtst Du diesen Kommentar wirklich löschen?', - /** * Revision */ diff --git a/resources/lang/de_informal/errors.php b/resources/lang/de_informal/errors.php index 8f8d24ea8..9cc123c3d 100644 --- a/resources/lang/de_informal/errors.php +++ b/resources/lang/de_informal/errors.php @@ -2,9 +2,6 @@ $de_formal = (include resource_path() . '/lang/de/' . basename(__FILE__)); $de_informal = [ - /** - * Error text strings. - */ // Pages 'permission' => 'Du hast keine Berechtigung, auf diese Seite zuzugreifen.', 'permissionJson' => 'Du hast keine Berechtigung, die angeforderte Aktion auszuführen.', @@ -19,16 +16,12 @@ $de_informal = [ 'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stelle sicher, dass dieser Ordner auf dem Server beschreibbar ist.', 'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.', 'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.', - // Attachments // Pages 'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.', 'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.', - // Entities // Users 'users_cannot_delete_only_admin' => 'Du kannst den einzigen Administrator nicht löschen.', 'users_cannot_delete_guest' => 'Du kannst den Gast-Benutzer nicht löschen', - // Roles - // Comments // Error pages 'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.', ]; diff --git a/resources/lang/de_informal/pagination.php b/resources/lang/de_informal/pagination.php index 0feccda8e..efab3e48f 100644 --- a/resources/lang/de_informal/pagination.php +++ b/resources/lang/de_informal/pagination.php @@ -3,18 +3,6 @@ $de_formal = (include resource_path() . '/lang/de/' . basename(__FILE__)); $de_informal = [ - /* - |-------------------------------------------------------------------------- - | Pagination Language Lines - |-------------------------------------------------------------------------- - | - | The following language lines are used by the paginator library to build - | the simple pagination links. You are free to change them to anything - | you want to customize your views to better match your application. - | - */ - - ]; return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/passwords.php b/resources/lang/de_informal/passwords.php index f25119c9d..efab3e48f 100644 --- a/resources/lang/de_informal/passwords.php +++ b/resources/lang/de_informal/passwords.php @@ -3,17 +3,6 @@ $de_formal = (include resource_path() . '/lang/de/' . basename(__FILE__)); $de_informal = [ - /* - |-------------------------------------------------------------------------- - | 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, such as for an invalid token or invalid new password. - | - */ - ]; return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/settings.php b/resources/lang/de_informal/settings.php index 1360fc151..67bd32c13 100644 --- a/resources/lang/de_informal/settings.php +++ b/resources/lang/de_informal/settings.php @@ -14,16 +14,11 @@ $de_informal = [ 'app_primary_color_desc' => "Dies sollte ein HEX Wert sein.\nWenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.", '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_books' => 'Oder wähle die Buch-Übersicht als Startseite. Das wird die Seiten-Auswahl überschreiben.', - /** - * Registration settings - */ - /** * Maintenance settings */ '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_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?', - /** * Role settings */ diff --git a/resources/lang/de_informal/validation.php b/resources/lang/de_informal/validation.php index e2078ed61..efab3e48f 100644 --- a/resources/lang/de_informal/validation.php +++ b/resources/lang/de_informal/validation.php @@ -3,39 +3,6 @@ $de_formal = (include resource_path() . '/lang/de/' . basename(__FILE__)); $de_informal = [ - /* - |-------------------------------------------------------------------------- - | Validation Language Lines - |-------------------------------------------------------------------------- - | - | following language lines contain default error messages used by - | validator class. Some of these rules have multiple versions such - | as size rules. Feel free to tweak each of these messages here. - | - */ - - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - ]; return array_replace($de_formal, $de_informal); From 323bff7d6d3974e5752bfec9581d733b91e933f5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 12 Dec 2018 20:46:27 +0000 Subject: [PATCH 13/43] Extended translations system for arrays & extension Extended the base Laravel translation system to allow a locale to be based upon another. Also adds functionality to take base & fallback locales into account when fetching an array of translations. Related to work done in #1159 --- app/Http/Controllers/HomeController.php | 10 +-- app/Providers/TranslationServiceProvider.php | 32 +++++++++ app/Translation/Translator.php | 74 ++++++++++++++++++++ composer.json | 1 + config/app.php | 2 +- resources/lang/de_informal/activities.php | 10 ++- resources/lang/de_informal/auth.php | 12 ++-- resources/lang/de_informal/common.php | 8 +-- resources/lang/de_informal/components.php | 8 +-- resources/lang/de_informal/entities.php | 12 ++-- resources/lang/de_informal/errors.php | 11 +-- resources/lang/de_informal/pagination.php | 10 ++- resources/lang/de_informal/passwords.php | 10 ++- resources/lang/de_informal/settings.php | 10 +-- resources/lang/de_informal/validation.php | 10 ++- tests/LanguageTest.php | 26 +++++++ 16 files changed, 185 insertions(+), 61 deletions(-) create mode 100644 app/Providers/TranslationServiceProvider.php create mode 100644 app/Translation/Translator.php diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 2bf029b51..5a5f34e4a 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -79,6 +79,7 @@ class HomeController extends Controller { $locale = app()->getLocale(); $cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale; + if (cache()->has($cacheKey) && config('app.env') !== 'development') { $resp = cache($cacheKey); } else { @@ -89,15 +90,6 @@ class HomeController extends Controller 'entities' => trans('entities'), 'errors' => trans('errors') ]; - if ($locale !== 'en') { - $enTrans = [ - 'common' => trans('common', [], 'en'), - 'components' => trans('components', [], 'en'), - 'entities' => trans('entities', [], 'en'), - 'errors' => trans('errors', [], 'en') - ]; - $translations = array_replace_recursive($enTrans, $translations); - } $resp = 'window.translations = ' . json_encode($translations); cache()->put($cacheKey, $resp, 120); } diff --git a/app/Providers/TranslationServiceProvider.php b/app/Providers/TranslationServiceProvider.php new file mode 100644 index 000000000..0e628c7da --- /dev/null +++ b/app/Providers/TranslationServiceProvider.php @@ -0,0 +1,32 @@ +registerLoader(); + + $this->app->singleton('translator', function ($app) { + $loader = $app['translation.loader']; + + // When registering the translator component, we'll need to set the default + // locale as well as the fallback locale. So, we'll grab the application + // configuration so we can easily get both of these values from there. + $locale = $app['config']['app.locale']; + + $trans = new Translator($loader, $locale); + + $trans->setFallback($app['config']['app.fallback_locale']); + + return $trans; + }); + } +} \ No newline at end of file diff --git a/app/Translation/Translator.php b/app/Translation/Translator.php new file mode 100644 index 000000000..2edfecf73 --- /dev/null +++ b/app/Translation/Translator.php @@ -0,0 +1,74 @@ + 'de', + ]; + + /** + * Get the translation for a given key. + * + * @param string $key + * @param array $replace + * @param string $locale + * @return string|array|null + */ + public function trans($key, array $replace = [], $locale = null) + { + $translation = $this->get($key, $replace, $locale); + + if (is_array($translation)) { + $translation = $this->mergeBackupTranslations($translation, $key, $locale); + } + + return $translation; + } + + /** + * Merge the fallback translations, and base translations if existing, + * into the provided core key => value array of translations content. + * @param array $translationArray + * @param string $key + * @param null $locale + * @return array + */ + protected function mergeBackupTranslations(array $translationArray, string $key, $locale = null) + { + $fallback = $this->get($key, [], $this->fallback); + $baseLocale = $this->getBaseLocale($locale ?? $this->locale); + $baseTranslations = $baseLocale ? $this->get($key, [], $baseLocale) : []; + + return array_replace_recursive($fallback, $baseTranslations, $translationArray); + } + + /** + * Get the array of locales to be checked. + * + * @param string|null $locale + * @return array + */ + protected function localeArray($locale) + { + $primaryLocale = $locale ?: $this->locale; + return array_filter([$primaryLocale, $this->getBaseLocale($primaryLocale), $this->fallback]); + } + + /** + * Get the locale to extend for the given locale. + * + * @param string $locale + * @return string|null + */ + protected function getBaseLocale($locale) + { + return $this->baseLocaleMap[$locale] ?? null; + } + +} \ No newline at end of file diff --git a/composer.json b/composer.json index a34c65091..a9b38bff4 100644 --- a/composer.json +++ b/composer.json @@ -6,6 +6,7 @@ "type": "project", "require": { "php": ">=7.0.0", + "ext-json": "*", "ext-tidy": "*", "ext-dom": "*", "laravel/framework": "~5.5.44", diff --git a/config/app.php b/config/app.php index 5209b5372..3040a36c6 100755 --- a/config/app.php +++ b/config/app.php @@ -187,7 +187,6 @@ return [ Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, @@ -205,6 +204,7 @@ return [ * Application Service Providers... */ BookStack\Providers\PaginationServiceProvider::class, + BookStack\Providers\TranslationServiceProvider::class, BookStack\Providers\AuthServiceProvider::class, BookStack\Providers\AppServiceProvider::class, diff --git a/resources/lang/de_informal/activities.php b/resources/lang/de_informal/activities.php index 79595ac4b..c82c9e0c4 100644 --- a/resources/lang/de_informal/activities.php +++ b/resources/lang/de_informal/activities.php @@ -1,8 +1,6 @@ 'Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.', + /** * Login & Register */ @@ -20,6 +22,7 @@ $de_informal = [ 'register_confirm' => 'Bitte prüfe Deinen Posteingang und bestätig die Registrierung.', 'registration_email_domain_invalid' => 'Du kannst dich mit dieser E-Mail nicht registrieren.', 'register_success' => 'Vielen Dank für Deine Registrierung! Die Daten sind gespeichert und Du bist angemeldet.', + /** * Password Reset */ @@ -28,6 +31,7 @@ $de_informal = [ 'reset_password_success' => 'Dein Passwort wurde erfolgreich zurückgesetzt.', 'email_reset_text' => 'Du erhältsts diese E-Mail, weil jemand versucht hat, Dein Passwort zurückzusetzen.', 'email_reset_not_requested' => 'Wenn Du das nicht warst, brauchst Du nichts weiter zu tun.', + /** * Email Confirmation */ @@ -40,6 +44,4 @@ $de_informal = [ 'email_not_confirmed_text' => 'Deine E-Mail-Adresse ist bisher nicht bestätigt.', 'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die Du nach der Registrierung erhalten hast.', 'email_not_confirmed_resend' => 'Wenn Du die E-Mail nicht erhalten hast, kannst Du die Nachricht erneut anfordern. Fülle hierzu bitte das folgende Formular aus:', -]; - -return array_replace($de_formal, $de_informal); +]; \ No newline at end of file diff --git a/resources/lang/de_informal/common.php b/resources/lang/de_informal/common.php index 9121b18ca..d80fa9dcf 100644 --- a/resources/lang/de_informal/common.php +++ b/resources/lang/de_informal/common.php @@ -1,11 +1,9 @@ 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffne die folgende URL in Deinem Browser:', -]; - -return array_replace($de_formal, $de_informal); +]; \ No newline at end of file diff --git a/resources/lang/de_informal/components.php b/resources/lang/de_informal/components.php index 4027da46e..31cc9ca1b 100644 --- a/resources/lang/de_informal/components.php +++ b/resources/lang/de_informal/components.php @@ -1,12 +1,10 @@ 'Bitte klicke erneut auf löschen, wenn Du dieses Bild wirklich entfernen möchtest.', 'image_dropzone' => 'Ziehe Bilder hierher oder klicke hier, um ein Bild auszuwählen', -]; - -return array_replace($de_formal, $de_informal); \ No newline at end of file +]; \ No newline at end of file diff --git a/resources/lang/de_informal/entities.php b/resources/lang/de_informal/entities.php index d50b0029d..21fdbb13d 100644 --- a/resources/lang/de_informal/entities.php +++ b/resources/lang/de_informal/entities.php @@ -1,21 +1,24 @@ 'Du hast bisher keine Seiten angesehen.', 'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.', 'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.', + /** * Books */ 'books_delete_confirmation' => 'Bist Du sicher, dass Du dieses Buch löschen möchtest?', + /** * Chapters */ 'chapters_delete_confirm' => 'Bist Du sicher, dass Du dieses Kapitel löschen möchtest?', + /** * Pages */ @@ -30,6 +33,7 @@ $de_informal = [ 'time_b' => 'in den letzten :minCount Minuten', 'message' => ':start :time. Achte darauf, keine Änderungen von anderen Benutzern zu überschreiben!', ], + /** * Editor sidebar */ @@ -39,15 +43,15 @@ $de_informal = [ 'attachments_dropzone' => 'Ziehe Dateien hierher oder klicke hier, um eine Datei auszuwählen', 'attachments_explain_link' => 'Wenn Du keine Datei hochladen möchtest, kannst Du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.', 'attachments_edit_drop_upload' => 'Ziehe Dateien hierher, um diese hochzuladen und zu überschreiben', + /** * Comments */ 'comment_placeholder' => 'Gib hier Deine Kommentare ein (Markdown unterstützt)', 'comment_delete_confirm' => 'Möchtst Du diesen Kommentar wirklich löschen?', + /** * Revision */ 'revision_delete_confirm' => 'Bist Du sicher, dass Du diese Revision löschen möchtest?', ]; - -return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/errors.php b/resources/lang/de_informal/errors.php index 9cc123c3d..924deee0d 100644 --- a/resources/lang/de_informal/errors.php +++ b/resources/lang/de_informal/errors.php @@ -1,10 +1,11 @@ 'Du hast keine Berechtigung, auf diese Seite zuzugreifen.', 'permissionJson' => 'Du hast keine Berechtigung, die angeforderte Aktion auszuführen.', + // Auth 'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melde dich an.', 'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich erneut.', @@ -12,18 +13,20 @@ $de_informal = [ 'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.', 'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.', 'social_account_register_instructions' => 'Wenn Du bisher kein Social-Media Konto besitzt, kannst Du ein solches Konto mit der :socialAccount Option anlegen.', + // System 'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stelle sicher, dass dieser Ordner auf dem Server beschreibbar ist.', 'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.', 'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.', + // Pages 'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.', 'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.', + // Users 'users_cannot_delete_only_admin' => 'Du kannst den einzigen Administrator nicht löschen.', 'users_cannot_delete_guest' => 'Du kannst den Gast-Benutzer nicht löschen', + // Error pages 'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.', ]; - -return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/pagination.php b/resources/lang/de_informal/pagination.php index efab3e48f..c82c9e0c4 100644 --- a/resources/lang/de_informal/pagination.php +++ b/resources/lang/de_informal/pagination.php @@ -1,8 +1,6 @@ "Dies sollte ein HEX Wert sein.\nWenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.", '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_books' => 'Oder wähle die Buch-Übersicht als Startseite. Das wird die Seiten-Auswahl überschreiben.', + /** * Maintenance settings */ '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_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?', + /** * Role settings */ 'role_delete_confirm' => 'Du möchtest die Rolle ":roleName" löschen.', 'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die Du diesen Benutzern zuordnen möchtest.', 'role_delete_sure' => 'Bist Du sicher, dass Du diese Rolle löschen möchtest?', + /** * Users */ @@ -32,5 +36,3 @@ $de_informal = [ 'users_delete_confirm' => 'Bist Du sicher, dass Du diesen Benutzer löschen möchtest?', 'users_social_accounts_info' => 'Hier kannst Du andere Social-Media-Konten für eine schnellere und einfachere Anmeldung verknüpfen. Wenn Du ein Social-Media Konto löschst, bleibt der Zugriff erhalten. Entferne in diesem Falle die Berechtigung in Deinen Profil-Einstellungen des verknüpften Social-Media-Kontos.', ]; - -return array_replace($de_formal, $de_informal); diff --git a/resources/lang/de_informal/validation.php b/resources/lang/de_informal/validation.php index efab3e48f..c82c9e0c4 100644 --- a/resources/lang/de_informal/validation.php +++ b/resources/lang/de_informal/validation.php @@ -1,8 +1,6 @@ assertTrue(config('app.rtl'), "App RTL config should have been set to true by middleware"); } + public function test_de_informal_falls_base_to_de() + { + // Base de back value + $deBack = trans()->get('common.cancel', [], 'de', false); + $this->assertEquals('Abbrechen', $deBack); + // Ensure de_informal has no value set + $this->assertEquals('common.cancel', trans()->get('common.cancel', [], 'de_informal', false)); + // Ensure standard trans falls back to de + $this->assertEquals($deBack, trans('common.cancel', [], 'de_informal')); + // Ensure de_informal gets its own values where set + $deEmailActionHelp = trans()->get('common.email_action_help', [], 'de', false); + $enEmailActionHelp = trans()->get('common.email_action_help', [], 'en', false); + $deInformalEmailActionHelp = trans()->get('common.email_action_help', [], 'de_informal', false); + $this->assertNotEquals($deEmailActionHelp, $deInformalEmailActionHelp); + $this->assertNotEquals($enEmailActionHelp, $deInformalEmailActionHelp); + } + + public function test_de_informal_falls_base_to_de_in_js_endpoint() + { + $this->asEditor(); + setting()->putUser($this->getEditor(), 'language', 'de_informal'); + + $transResp = $this->get('/translations'); + $transResp->assertSee('"cancel":"Abbrechen"'); + } + } \ No newline at end of file From 1b1ddb67946ea7bffce0a3e381bbb1f616b628c5 Mon Sep 17 00:00:00 2001 From: Jurij Vasiliev Date: Thu, 13 Dec 2018 13:21:02 +0100 Subject: [PATCH 14/43] =?UTF-8?q?Major=20updates=20on=20polish=20language?= =?UTF-8?q?=201.=20Changed=20Book=20translation=20from=20ksi=C4=99ga=20=3D?= =?UTF-8?q?>=20podr=C4=99cznik=20(ksi=C4=99ga=20is=20very=20old=20word,=20?= =?UTF-8?q?and=20thus=20not=20fit=20to=20the=20app.=20Podr=C4=99cznik=20is?= =?UTF-8?q?=20word=20for=20book=20used=20in=20school=20and=20fits=20much?= =?UTF-8?q?=20more=20to=20the=20documentation=20site)=202.=20Changed=20Ent?= =?UTF-8?q?ity=20transaltion=20from=20encja=20=3D>=20obiekt=20(encja=20is?= =?UTF-8?q?=20word=20used=20in=20IT=20world,=20common=20people=20doesn't?= =?UTF-8?q?=20know=20what=20it=20is.=20Obiekt=20(object)=20fits=20better?= =?UTF-8?q?=20for=20no=20IT=20geeks=20and=20explains=20them=20more=20than?= =?UTF-8?q?=20word=20encja)=203.=20Added=20Shelf/Bookshelf=20transaltion.?= =?UTF-8?q?=20Now=20they=20are=20named=20P=C3=B3=C5=82ka/P=C3=B3=C5=82ki?= =?UTF-8?q?=204.=20Changed=20Draft=20translation=20from=20szkic=20=3D>=20w?= =?UTF-8?q?ersja=20robocza=20(in=20every=20system=20like=20wordpress/wiki?= =?UTF-8?q?=20etc.=20the=20word=20for=20draft=20is=20wersja=20robocza.=20S?= =?UTF-8?q?zkic=20is=20word=20for=20draft=20of=20an=20image)=205.=20Fixed?= =?UTF-8?q?=20typos=206.=20Fixed=20unfit=20plural=20words=20when=20they=20?= =?UTF-8?q?were=20not=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/lang/pl/activities.php | 28 +++-- resources/lang/pl/auth.php | 26 ++--- resources/lang/pl/common.php | 10 +- resources/lang/pl/components.php | 13 ++- resources/lang/pl/entities.php | 193 +++++++++++++++++++------------ resources/lang/pl/errors.php | 57 +++++---- resources/lang/pl/passwords.php | 2 +- resources/lang/pl/settings.php | 54 ++++++--- 8 files changed, 236 insertions(+), 147 deletions(-) diff --git a/resources/lang/pl/activities.php b/resources/lang/pl/activities.php index 5ef5acab0..7013be566 100644 --- a/resources/lang/pl/activities.php +++ b/resources/lang/pl/activities.php @@ -15,7 +15,7 @@ return [ 'page_delete' => 'usunięto stronę', 'page_delete_notification' => 'Strona usunięta pomyślnie', 'page_restore' => 'przywrócono stronę', - 'page_restore_notification' => 'Stronga przywrócona pomyślnie', + 'page_restore_notification' => 'Strona przywrócona pomyślnie', 'page_move' => 'przeniesiono stronę', // Chapters @@ -28,13 +28,23 @@ return [ 'chapter_move' => 'przeniesiono rozdział', // Books - 'book_create' => 'utworzono księgę', - 'book_create_notification' => 'Księga utworzona pomyślnie', - 'book_update' => 'zaktualizowano księgę', - 'book_update_notification' => 'Księga zaktualizowana pomyślnie', - 'book_delete' => 'usunięto księgę', - 'book_delete_notification' => 'Księga usunięta pomyślnie', - 'book_sort' => 'posortowano księgę', - 'book_sort_notification' => 'Księga posortowana pomyślnie', + 'book_create' => 'utworzono podręcznik', + 'book_create_notification' => 'Podręcznik utworzony pomyślnie', + 'book_update' => 'zaktualizowano podręcznik', + 'book_update_notification' => 'Podręcznik zaktualizowany pomyślnie', + 'book_delete' => 'usunięto podręcznik', + 'book_delete_notification' => 'Podręcznik usunięty pomyślnie', + 'book_sort' => 'posortowano podręcznik', + 'book_sort_notification' => 'Podręcznik posortowany pomyślnie', + // Bookshelves + 'bookshelf_create' => 'utworzono półkę', + 'bookshelf_create_notification' => 'Półka utworzona pomyślnie', + 'bookshelf_update' => 'zaktualizowano półkę', + 'bookshelf_update_notification' => 'Półka zaktualizowana pomyślnie', + 'bookshelf_delete' => 'usunięto półkę', + 'bookshelf_delete_notification' => 'Półka usunięta pomyślnie', + + // Other + 'commented_on' => 'skomentował', ]; diff --git a/resources/lang/pl/auth.php b/resources/lang/pl/auth.php index 740e067ca..5cec651a9 100644 --- a/resources/lang/pl/auth.php +++ b/resources/lang/pl/auth.php @@ -10,8 +10,8 @@ return [ | these language lines according to your application's requirements. | */ - 'failed' => 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'failed' => 'Wprowadzone poświadczenia są nieprawidłowe.', + 'throttle' => 'Zbyt wiele prób logowania. Spróbuj ponownie za :seconds s.', /** * Login & Register @@ -24,13 +24,13 @@ return [ 'name' => 'Imię', 'username' => 'Nazwa użytkownika', - 'email' => 'Email', + 'email' => 'E-mail', 'password' => 'Hasło', 'password_confirm' => 'Potwierdzenie hasła', 'password_hint' => 'Musi mieć więcej niż 5 znaków', - 'forgot_password' => 'Przypomnij hasło', + 'forgot_password' => 'Zapomniałem hasła', 'remember_me' => 'Zapamiętaj mnie', - 'ldap_email_hint' => 'Wprowadź adres email dla tego konta.', + 'ldap_email_hint' => 'Wprowadź adres e-mail dla tego konta.', 'create_account' => 'Utwórz konto', 'social_login' => 'Logowanie za pomocą konta społecznościowego', 'social_registration' => 'Rejestracja za pomocą konta społecznościowego', @@ -40,7 +40,7 @@ return [ 'register_confirm' => 'Sprawdź podany adres e-mail i kliknij w link, by uzyskać dostęp do :appName.', 'registrations_disabled' => 'Rejestracja jest obecnie zablokowana.', 'registration_email_domain_invalid' => 'Adresy e-mail z tej domeny nie mają dostępu do tej aplikacji', - 'register_success' => 'Dziękujemy za rejestrację! Zalogowano Cię automatycznie.', + 'register_success' => 'Dziękujemy za rejestrację! Zostałeś zalogowany automatycznie.', /** @@ -60,17 +60,17 @@ return [ /** * Email Confirmation */ - 'email_confirm_subject' => 'Potwierdź swój adres email w :appName', + 'email_confirm_subject' => 'Potwierdź swój adres e-mail w :appName', 'email_confirm_greeting' => 'Dziękujemy za dołączenie do :appName!', 'email_confirm_text' => 'Prosimy byś potwierdził swoje hasło klikając przycisk poniżej:', - 'email_confirm_action' => 'Potwierdź email', + 'email_confirm_action' => 'Potwierdź e-mail', 'email_confirm_send_error' => 'Wymagane jest potwierdzenie hasła, lecz wiadomość nie mogła zostać wysłana. Skontaktuj się z administratorem w celu upewnienia się, że skrzynka została skonfigurowana prawidłowo.', - 'email_confirm_success' => 'Adres email został potwierdzony!', - 'email_confirm_resent' => 'Wiadomość potwierdzająca została wysłana, sprawdź swoją skrzynkę.', + 'email_confirm_success' => 'Adres e-mail został potwierdzony!', + 'email_confirm_resent' => 'E-mail z potwierdzeniem został wysłany ponownie, sprawdź swoją skrzynkę odbiorczą.', - 'email_not_confirmed' => 'Adres email niepotwierdzony', - 'email_not_confirmed_text' => 'Twój adres email nie został jeszcze potwierdzony.', + 'email_not_confirmed' => 'Adres e-mail nie został potwierdzony', + 'email_not_confirmed_text' => 'Twój adres e-mail nie został jeszcze potwierdzony.', 'email_not_confirmed_click_link' => 'Aby potwierdzić swoje konto kliknij w link wysłany w wiadomości po rejestracji.', 'email_not_confirmed_resend' => 'Jeśli wiadomość do Ciebie nie dotarła możesz wysłać ją ponownie wypełniając formularz poniżej.', 'email_not_confirmed_resend_button' => 'Wyślij ponownie wiadomość z potwierdzeniem', -]; \ No newline at end of file +]; diff --git a/resources/lang/pl/common.php b/resources/lang/pl/common.php index 84c9b0d67..a03cd61fe 100644 --- a/resources/lang/pl/common.php +++ b/resources/lang/pl/common.php @@ -19,7 +19,7 @@ return [ 'description' => 'Opis', 'role' => 'Rola', 'cover_image' => 'Zdjęcie z okładki', - 'cover_image_description' => 'Ten obraz powinien wynosić około 300 x 170 piksli.', + 'cover_image_description' => 'Ten obraz powinien posiadać wymiary około 440x250px.', /** * Actions @@ -31,6 +31,8 @@ return [ 'edit' => 'Edytuj', 'sort' => 'Sortuj', 'move' => 'Przenieś', + 'copy' => 'Copy', + 'reply' => 'Reply', 'delete' => 'Usuń', 'search' => 'Szukaj', 'search_clear' => 'Wyczyść wyszukiwanie', @@ -38,12 +40,11 @@ return [ 'remove' => 'Usuń', 'add' => 'Dodaj', - /** * Misc */ 'deleted_user' => 'Użytkownik usunięty', - 'no_activity' => 'Brak aktywności do pokazania', + 'no_activity' => 'Brak aktywności do wyświetlenia', 'no_items' => 'Brak elementów do wyświetlenia', 'back_to_top' => 'Powrót na górę', 'toggle_details' => 'Włącz/wyłącz szczegóły', @@ -51,6 +52,7 @@ return [ 'details' => 'Szczegóły', 'grid_view' => 'Widok kafelkowy', 'list_view' => 'Widok listy', + 'default' => 'Domyślny', /** * Header @@ -63,4 +65,4 @@ return [ */ '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', -]; \ No newline at end of file +]; diff --git a/resources/lang/pl/components.php b/resources/lang/pl/components.php index 177fdba5f..8c5429088 100644 --- a/resources/lang/pl/components.php +++ b/resources/lang/pl/components.php @@ -7,21 +7,22 @@ return [ 'image_select' => 'Wybór obrazka', 'image_all' => 'Wszystkie', 'image_all_title' => 'Zobacz wszystkie obrazki', - 'image_book_title' => 'Zobacz obrazki zapisane w tej księdze', + 'image_book_title' => 'Zobacz obrazki zapisane w tym podręczniku', 'image_page_title' => 'Zobacz obrazki zapisane na tej stronie', 'image_search_hint' => 'Szukaj po nazwie obrazka', - 'image_uploaded' => 'Udostępniono :uploadedDate', + 'image_uploaded' => 'Przesłano :uploadedDate', 'image_load_more' => 'Wczytaj więcej', 'image_image_name' => 'Nazwa obrazka', - 'image_delete_used' => 'Ten obrazek jest używany na stronach poniżej.', + 'image_delete_used' => 'Ten obrazek jest używany na stronach wyświetlonych poniżej.', 'image_delete_confirm' => 'Kliknij ponownie Usuń by potwierdzić usunięcie obrazka.', 'image_select_image' => 'Wybierz obrazek', - 'image_dropzone' => 'Upuść obrazki tutaj lub kliknij by wybrać obrazki do udostępnienia', + 'image_dropzone' => 'Upuść obrazki tutaj lub kliknij by wybrać obrazki do przesłania', 'images_deleted' => 'Usunięte obrazki', 'image_preview' => 'Podgląd obrazka', - 'image_upload_success' => 'Obrazek wysłany pomyślnie', + 'image_upload_success' => 'Obrazek przesłany pomyślnie', 'image_update_success' => 'Szczegóły obrazka zaktualizowane pomyślnie', 'image_delete_success' => 'Obrazek usunięty pomyślnie', + 'image_upload_remove' => 'Usuń', /** * Code editor @@ -30,4 +31,4 @@ return [ 'code_language' => 'Język kodu', 'code_content' => 'Zawartość kodu', 'code_save' => 'Zapisz kod', -]; \ No newline at end of file +]; diff --git a/resources/lang/pl/entities.php b/resources/lang/pl/entities.php index 8b53591f6..3dad5e2e3 100644 --- a/resources/lang/pl/entities.php +++ b/resources/lang/pl/entities.php @@ -8,23 +8,23 @@ return [ 'recently_created_pages' => 'Ostatnio utworzone strony', 'recently_updated_pages' => 'Ostatnio zaktualizowane strony', 'recently_created_chapters' => 'Ostatnio utworzone rozdziały', - 'recently_created_books' => 'Ostatnio utworzone księgi', + 'recently_created_books' => 'Ostatnio utworzone podręczniki', 'recently_update' => 'Ostatnio zaktualizowane', 'recently_viewed' => 'Ostatnio wyświetlane', 'recent_activity' => 'Ostatnia aktywność', 'create_now' => 'Utwórz teraz', - 'revisions' => 'Rewizje', - 'meta_revision' => 'Rewizja #:revisionCount', + 'revisions' => 'Wersje', + 'meta_revision' => 'Wersja #:revisionCount', 'meta_created' => 'Utworzono :timeLength', 'meta_created_name' => 'Utworzono :timeLength przez :user', 'meta_updated' => 'Zaktualizowano :timeLength', 'meta_updated_name' => 'Zaktualizowano :timeLength przez :user', 'x_pages' => ':count stron', - 'entity_select' => 'Wybór encji', + 'entity_select' => 'Wybór obiektu', 'images' => 'Obrazki', - 'my_recent_drafts' => 'Moje ostatnie szkice', + 'my_recent_drafts' => 'Moje ostatnie wersje robocze', 'my_recently_viewed' => 'Moje ostatnio wyświetlane', - 'no_pages_viewed' => 'Nie wyświetlano żadnych stron', + 'no_pages_viewed' => 'Nie przeglądałeś jeszcze żadnych stron', 'no_pages_recently_created' => 'Nie utworzono ostatnio żadnych stron', 'no_pages_recently_updated' => 'Nie zaktualizowano ostatnio żadnych stron', 'export' => 'Eksportuj', @@ -36,8 +36,8 @@ return [ * Permissions and restrictions */ 'permissions' => 'Uprawnienia', - 'permissions_intro' => 'Jeśli odblokowane, te uprawnienia będą miały priorytet względem pozostałych ustawionych uprawnień ról.', - 'permissions_enable' => 'Odblokuj własne uprawnienia', + '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', /** @@ -50,54 +50,90 @@ return [ 'search_for_term' => 'Szukaj :term', 'search_more' => 'Więcej wyników', 'search_filters' => 'Filtry wyszukiwania', - 'search_content_type' => 'Rodziaj treści', + 'search_content_type' => 'Rodzaj treści', 'search_exact_matches' => 'Dokładne frazy', 'search_tags' => 'Tagi wyszukiwania', + 'search_options' => 'Opcje', 'search_viewed_by_me' => 'Wyświetlone przeze mnie', 'search_not_viewed_by_me' => 'Niewyświetlone przeze mnie', 'search_permissions_set' => 'Zbiór uprawnień', 'search_created_by_me' => 'Utworzone przeze mnie', 'search_updated_by_me' => 'Zaktualizowane przeze mnie', + 'search_date_options' => 'Opcje dat', 'search_updated_before' => 'Zaktualizowane przed', 'search_updated_after' => 'Zaktualizowane po', 'search_created_before' => 'Utworzone przed', 'search_created_after' => 'Utworzone po', 'search_set_date' => 'Ustaw datę', 'search_update' => 'Zaktualizuj wyszukiwanie', - + + /** + * Shelves + */ + 'shelf' => 'Półka', + 'shelves' => 'Półki', + 'shelves_long' => 'Półki', + 'shelves_empty' => 'Brak utworzonych półek', + 'shelves_create' => 'Utwórz półkę', + 'shelves_popular' => 'Popularne półki', + 'shelves_new' => 'Nowe półki', + 'shelves_popular_empty' => 'Najpopularniejsze półki pojawią się w tym miejscu.', + 'shelves_new_empty' => 'Tutaj pojawią się ostatnio utworzone półki.', + 'shelves_save' => 'Zapisz półkę', + 'shelves_books' => 'Podręczniki na tej półce', + 'shelves_add_books' => 'Dodaj podręczniki do tej półki', + 'shelves_drag_books' => 'Przeciągnij podręczniki tutaj aby dodać je do półki', + 'shelves_empty_contents' => 'Ta półka nie ma przypisanych żadnych podręczników', + 'shelves_edit_and_assign' => 'Edytuj półkę aby przypisać podręczniki', + 'shelves_edit_named' => 'Edytuj półkę :name', + 'shelves_edit' => 'Edytuj półkę', + 'shelves_delete' => 'Usuń półkę', + 'shelves_delete_named' => 'Usuń półkę :name', + 'shelves_delete_explain' => "Ta operacja usunie półkę o nazwie ':name'. Podręczniki z tej półki nie zostaną usunięte.", + 'shelves_delete_confirmation' => 'Czy jesteś pewien, że chcesz usunąć tę półkę?', + 'shelves_permissions' => 'Uprawnienia półki', + 'shelves_permissions_updated' => 'Uprawnienia półki zostały zaktualizowane', + 'shelves_permissions_active' => 'Uprawnienia półki są aktywne', + 'shelves_copy_permissions_to_books' => 'Skopiuj uprawnienia do podręczników', + 'shelves_copy_permissions' => 'Skopiuj uprawnienia', + 'shelves_copy_permissions_explain' => 'To spowoduje zastosowanie obecnych ustawień uprawnień dla tej półki do wszystkich podręczników w niej zawartych. Przed aktywacją upewnij się, że wszelkie zmiany w uprawnieniach do tej półki zostały zapisane.', + 'shelves_copy_permission_success' => 'Uprawnienia półki zostały skopiowane do :count podręczników', + /** * Books */ - 'book' => 'Księga', - 'books' => 'Księgi', - 'x_books' => ':count Księga|:count Księgi', - 'books_empty' => 'Brak utworzonych ksiąg', - 'books_popular' => 'Popularne księgi', - 'books_recent' => 'Ostatnie księgi', - 'books_popular_empty' => 'Najbardziej popularne księgi zostaną wyświetlone w tym miejscu.', - 'books_create' => 'Utwórz księgę', - 'books_delete' => 'Usuń księgę', - 'books_delete_named' => 'Usuń księgę :bookName', - 'books_delete_explain' => 'To spowoduje usunięcie księgi \':bookName\', Wszystkie strony i rozdziały zostaną usunięte.', - 'books_delete_confirmation' => 'Czy na pewno chcesz usunąc tę księgę?', - 'books_edit' => 'Edytuj księgę', - 'books_edit_named' => 'Edytuj księgę :bookName', - 'books_form_book_name' => 'Nazwa księgi', - 'books_save' => 'Zapisz księgę', - 'books_permissions' => 'Uprawnienia księgi', - 'books_permissions_updated' => 'Zaktualizowano uprawnienia księgi', - 'books_empty_contents' => 'Brak stron lub rozdziałów w tej księdze.', + 'book' => 'Podręcznik', + 'books' => 'Podręczniki', + 'x_books' => ':count Podręcznik|:count Podręczniki', + 'books_empty' => 'Brak utworzonych podręczników', + 'books_popular' => 'Popularne podręczniki', + 'books_recent' => 'Ostatnie podręczniki', + 'books_new' => 'Nowe podręczniki', + 'books_popular_empty' => 'Najpopularniejsze podręczniki pojawią się w tym miejscu.', + 'books_new_empty' => 'Tutaj pojawią się ostatnio utworzone podręczniki.', + 'books_create' => 'Utwórz podręcznik', + 'books_delete' => 'Usuń podręcznik', + 'books_delete_named' => 'Usuń podręcznik :bookName', + 'books_delete_explain' => 'To spowoduje usunięcie podręcznika \':bookName\', Wszystkie strony i rozdziały zostaną usunięte.', + 'books_delete_confirmation' => 'Czy na pewno chcesz usunąc ten podręcznik?', + 'books_edit' => 'Edytuj podręcznik', + 'books_edit_named' => 'Edytuj podręcznik :bookName', + 'books_form_book_name' => 'Nazwa podręcznika', + 'books_save' => 'Zapisz podręcznik', + 'books_permissions' => 'Uprawnienia podręcznika', + 'books_permissions_updated' => 'Zaktualizowano uprawnienia podręcznika', + 'books_empty_contents' => 'Brak stron lub rozdziałów w tym podręczniku.', 'books_empty_create_page' => 'Utwórz nową stronę', 'books_empty_or' => 'lub', - 'books_empty_sort_current_book' => 'posortuj bieżącą księgę', + 'books_empty_sort_current_book' => 'posortuj bieżący podręcznik', 'books_empty_add_chapter' => 'Dodaj rozdział', - 'books_permissions_active' => 'Uprawnienia księgi aktywne', - 'books_search_this' => 'Wyszukaj w tej księdze', - 'books_navigation' => 'Nawigacja po księdze', - 'books_sort' => 'Sortuj zawartość Księgi', - 'books_sort_named' => 'Sortuj księgę :bookName', - 'books_sort_show_other' => 'Pokaż inne księgi', - 'books_sort_save' => 'Zapisz nowy porządek', + 'books_permissions_active' => 'Uprawnienia podręcznika są aktywne', + 'books_search_this' => 'Wyszukaj w tym podręczniku', + 'books_navigation' => 'Nawigacja po podręczniku', + 'books_sort' => 'Sortuj zawartość podręcznika', + 'books_sort_named' => 'Sortuj podręcznik :bookName', + 'books_sort_show_other' => 'Pokaż inne podręczniki', + 'books_sort_save' => 'Zapisz nową kolejność', /** * Chapters @@ -111,7 +147,7 @@ return [ 'chapters_delete' => 'Usuń rozdział', 'chapters_delete_named' => 'Usuń rozdział :chapterName', 'chapters_delete_explain' => 'To spowoduje usunięcie rozdziału \':chapterName\', Wszystkie strony zostaną usunięte - i dodane bezpośrednio do księgi macierzystej.', + i dodane bezpośrednio do podręcznika nadrzędnego.', 'chapters_delete_confirm' => 'Czy na pewno chcesz usunąć ten rozdział?', 'chapters_edit' => 'Edytuj rozdział', 'chapters_edit_named' => 'Edytuj rozdział :chapterName', @@ -121,7 +157,7 @@ return [ 'chapter_move_success' => 'Rozdział przeniesiony do :bookName', 'chapters_permissions' => 'Uprawienia rozdziału', 'chapters_empty' => 'Brak stron w tym rozdziale.', - 'chapters_permissions_active' => 'Uprawnienia rozdziału aktywne', + 'chapters_permissions_active' => 'Uprawnienia rozdziału są aktywne', 'chapters_permissions_success' => 'Zaktualizowano uprawnienia rozdziału', 'chapters_search_this' => 'Przeszukaj ten rozdział', @@ -136,84 +172,93 @@ return [ 'pages_navigation' => 'Nawigacja po stronie', 'pages_delete' => 'Usuń stronę', 'pages_delete_named' => 'Usuń stronę :pageName', - 'pages_delete_draft_named' => 'Usuń szkic strony :pageName', - 'pages_delete_draft' => 'Usuń szkic strony', + 'pages_delete_draft_named' => 'Usuń wersje robocze dla strony :pageName', + 'pages_delete_draft' => 'Usuń wersje roboczą', 'pages_delete_success' => 'Strona usunięta pomyślnie', - 'pages_delete_draft_success' => 'Szkic strony usunięty pomyślnie', - 'pages_delete_confirm' => 'Czy na pewno chcesz usunąć tę stron?', - 'pages_delete_draft_confirm' => 'Czy na pewno chcesz usunąć szkic strony?', + 'pages_delete_draft_success' => 'Werjsa robocza usunięta pomyślnie', + 'pages_delete_confirm' => 'Czy na pewno chcesz usunąć tę stronę?', + 'pages_delete_draft_confirm' => 'Czy na pewno chcesz usunąć wersje roboczą strony?', 'pages_editing_named' => 'Edytowanie strony :pageName', 'pages_edit_toggle_header' => 'Włącz/wyłącz nagłówek', - 'pages_edit_save_draft' => 'Zapisz szkic', - 'pages_edit_draft' => 'Edytuj szkic strony', - 'pages_editing_draft' => 'Edytowanie szkicu strony', + 'pages_edit_save_draft' => 'Zapisano wersje roboczą o ', + 'pages_edit_draft' => 'Edytuj wersje roboczą', + 'pages_editing_draft' => 'Edytowanie wersji roboczej', 'pages_editing_page' => 'Edytowanie strony', - 'pages_edit_draft_save_at' => 'Szkic zapisany ', - 'pages_edit_delete_draft' => 'Usuń szkic', - 'pages_edit_discard_draft' => 'Porzuć szkic', - 'pages_edit_set_changelog' => 'Ustaw log zmian', + 'pages_edit_draft_save_at' => 'Wersja robocza zapisana ', + 'pages_edit_delete_draft' => 'Usuń wersje roboczą', + 'pages_edit_discard_draft' => 'Porzuć wersje roboczą', + 'pages_edit_set_changelog' => 'Ustaw dziennik zmian', 'pages_edit_enter_changelog_desc' => 'Opisz zmiany, które zostały wprowadzone', - 'pages_edit_enter_changelog' => 'Wyświetl log zmian', + 'pages_edit_enter_changelog' => 'Wyświetl dziennik zmian', 'pages_save' => 'Zapisz stronę', 'pages_title' => 'Tytuł strony', 'pages_name' => 'Nazwa strony', 'pages_md_editor' => 'Edytor', 'pages_md_preview' => 'Podgląd', 'pages_md_insert_image' => 'Wstaw obrazek', - 'pages_md_insert_link' => 'Wstaw łącze do encji', + 'pages_md_insert_link' => 'Wstaw łącze do obiektu', + 'pages_md_insert_drawing' => 'Wstaw rysunek', 'pages_not_in_chapter' => 'Strona nie została umieszczona w rozdziale', 'pages_move' => 'Przenieś stronę', 'pages_move_success' => 'Strona przeniesiona do ":parentName"', + 'pages_copy' => 'Skopiuj stronę', + 'pages_copy_desination' => 'Skopiuj do', + 'pages_copy_success' => 'Strona została pomyślnie skopiowana', 'pages_permissions' => 'Uprawnienia strony', 'pages_permissions_success' => 'Zaktualizowano uprawnienia strony', - 'pages_revisions' => 'Rewizje strony', - 'pages_revisions_named' => 'Rewizje strony :pageName', - 'pages_revision_named' => 'Rewizja stroony :pageName', + 'pages_revision' => 'Wersja', + 'pages_revisions' => 'Wersje strony', + 'pages_revisions_named' => 'Wersje strony :pageName', + 'pages_revision_named' => 'Wersja strony :pageName', 'pages_revisions_created_by' => 'Utworzona przez', - 'pages_revisions_date' => 'Data rewizji', + 'pages_revisions_date' => 'Data wersji', 'pages_revisions_number' => '#', - 'pages_revisions_changelog' => 'Log zmian', + 'pages_revisions_changelog' => 'Dziennik zmian', 'pages_revisions_changes' => 'Zmiany', 'pages_revisions_current' => 'Obecna wersja', 'pages_revisions_preview' => 'Podgląd', 'pages_revisions_restore' => 'Przywróć', - 'pages_revisions_none' => 'Ta strona nie posiada żadnych rewizji', + 'pages_revisions_none' => 'Ta strona nie posiada żadnych wersji', 'pages_copy_link' => 'Kopiuj link', 'pages_edit_content_link' => 'Edytuj zawartość', - 'pages_permissions_active' => 'Uprawnienia strony aktywne', - 'pages_initial_revision' => 'Wydanie pierwotne', + 'pages_permissions_active' => 'Uprawnienia strony są aktywne', + 'pages_initial_revision' => 'Pierwsze wydanie', 'pages_initial_name' => 'Nowa strona', - 'pages_editing_draft_notification' => 'Edytujesz obecnie szkic, który był ostatnio zapisany :timeDiff.', - 'pages_draft_edited_notification' => 'Od tego czasu ta strona była zmieniana. Zalecane jest odrzucenie tego szkicu.', + 'pages_editing_draft_notification' => 'Edytujesz obecnie wersje roboczą, która była ostatnio zapisana :timeDiff.', + 'pages_draft_edited_notification' => 'Od tego czasu ta strona była zmieniana. Zalecane jest odrzucenie tej wersji roboczej.', 'pages_draft_edit_active' => [ 'start_a' => ':count użytkowników rozpoczęło edytowanie tej strony', 'start_b' => ':userName edytuje stronę', - 'time_a' => ' od czasu ostatniej edycji', + 'time_a' => 'od czasu ostatniej edycji', 'time_b' => 'w ciągu ostatnich :minCount minut', 'message' => ':start :time. Pamiętaj by nie nadpisywać czyichś zmian!', ], - 'pages_draft_discarded' => 'Szkic odrzucony, edytor został uzupełniony najnowszą wersją strony', + 'pages_draft_discarded' => 'Wersja robocza odrzucona, edytor został uzupełniony najnowszą wersją strony', + 'pages_specific' => 'Określona strona', /** * Editor sidebar */ 'page_tags' => 'Tagi strony', + 'chapter_tags' => 'Tagi rozdziału', + 'book_tags' => 'Tagi podręcznika', + 'shelf_tags' => 'Tagi półki', 'tag' => 'Tag', - 'tags' => '', + 'tags' => 'Tagi', 'tag_value' => 'Wartość tagu (opcjonalnie)', 'tags_explain' => "Dodaj tagi by skategoryzować zawartość. \n W celu dokładniejszej organizacji zawartości możesz dodać wartości do tagów.", 'tags_add' => 'Dodaj kolejny tag', 'attachments' => 'Załączniki', - 'attachments_explain' => 'Udostępnij kilka plików lub załącz link. Będą one widoczne na marginesie strony.', + 'attachments_explain' => 'Prześlij kilka plików lub załącz linki. Będą one widoczne na pasku bocznym strony.', 'attachments_explain_instant_save' => 'Zmiany są zapisywane natychmiastowo.', 'attachments_items' => 'Załączniki', 'attachments_upload' => 'Dodaj plik', 'attachments_link' => 'Dodaj link', 'attachments_set_link' => 'Ustaw link', 'attachments_delete_confirm' => 'Kliknij ponownie Usuń by potwierdzić usunięcie załącznika.', - 'attachments_dropzone' => 'Upuść pliki lub kliknij tutaj by udostępnić pliki', - 'attachments_no_files' => 'Nie udostępniono plików', - 'attachments_explain_link' => 'Możesz załączyć link jeśli nie chcesz udostępniać pliku. Może być to link do innej strony lub link do pliku w chmurze.', + 'attachments_dropzone' => 'Upuść pliki lub kliknij tutaj by przesłać pliki', + 'attachments_no_files' => 'Nie przesłano żadnych plików', + 'attachments_explain_link' => 'Możesz załączyć link jeśli nie chcesz przesyłać pliku. Może być to link do innej strony lub link do pliku w chmurze.', 'attachments_link_name' => 'Nazwa linku', 'attachment_link' => 'Link do załącznika', 'attachments_link_url' => 'Link do pliku', @@ -221,10 +266,10 @@ return [ 'attach' => 'Załącz', 'attachments_edit_file' => 'Edytuj plik', 'attachments_edit_file_name' => 'Nazwa pliku', - 'attachments_edit_drop_upload' => 'Upuść pliki lub kliknij tutaj by udostępnić pliki i nadpisać istniejące', + 'attachments_edit_drop_upload' => 'Upuść pliki lub kliknij tutaj by przesłać pliki i nadpisać istniejące', 'attachments_order_updated' => 'Kolejność załączników zaktualizowana', 'attachments_updated_success' => 'Szczegóły załączników zaktualizowane', - 'attachments_deleted' => 'Załączniki usunięte', + 'attachments_deleted' => 'Załącznik usunięty', 'attachments_file_uploaded' => 'Plik załączony pomyślnie', 'attachments_file_updated' => 'Plik zaktualizowany pomyślnie', 'attachments_link_attached' => 'Link pomyślnie dodany do strony', @@ -236,7 +281,7 @@ return [ 'profile_created_content' => 'Utworzona zawartość', 'profile_not_created_pages' => ':userName nie utworzył żadnych stron', 'profile_not_created_chapters' => ':userName nie utworzył żadnych rozdziałów', - 'profile_not_created_books' => ':userName nie utworzył żadnych ksiąg', + 'profile_not_created_books' => ':userName nie utworzył żadnych podręczników', /** * Comments @@ -264,4 +309,4 @@ return [ 'revision_delete_confirm' => 'Czy na pewno chcesz usunąć tę wersję?', 'revision_delete_success' => 'Usunięto wersję', 'revision_cannot_delete_latest' => 'Nie można usunąć najnowszej wersji.' -]; \ No newline at end of file +]; diff --git a/resources/lang/pl/errors.php b/resources/lang/pl/errors.php index 633bf7a2d..ec26e0763 100644 --- a/resources/lang/pl/errors.php +++ b/resources/lang/pl/errors.php @@ -11,17 +11,18 @@ return [ 'permissionJson' => 'Nie masz uprawnień do wykonania tej akcji.', // Auth - 'error_user_exists_different_creds' => 'Użytkownik o adresie :email już istnieje.', - 'email_already_confirmed' => 'Email został potwierdzony, spróbuj się zalogować.', + 'error_user_exists_different_creds' => 'Użytkownik o adresie :email już istnieje, ale używa innych poświadczeń.', + 'email_already_confirmed' => 'E-mail został potwierdzony, spróbuj się zalogować.', 'email_confirmation_invalid' => 'Ten token jest nieprawidłowy lub został już wykorzystany. Spróbuj zarejestrować się ponownie.', 'email_confirmation_expired' => 'Ten token potwierdzający wygasł. Wysłaliśmy Ci kolejny.', 'ldap_fail_anonymous' => 'Dostęp LDAP przy użyciu anonimowego powiązania nie powiódł się', - 'ldap_fail_authed' => 'Dostęp LDAP przy użyciu tego dn i hasła nie powiódł się', + 'ldap_fail_authed' => 'Dostęp LDAP przy użyciu tego DN i hasła nie powiódł się', 'ldap_extension_not_installed' => 'Rozszerzenie LDAP PHP nie zostało zainstalowane', 'ldap_cannot_connect' => 'Nie można połączyć z serwerem LDAP, połączenie nie zostało ustanowione', 'social_no_action_defined' => 'Brak zdefiniowanej akcji', - 'social_account_in_use' => 'To konto :socialAccount jest już w użyciu, spróbuj zalogować się za pomocą opcji :socialAccount.', - 'social_account_email_in_use' => 'Email :email jest już w użyciu. Jeśli masz już konto, połącz konto :socialAccount z poziomu ustawień profilu.', + 'social_login_bad_response' => "Podczas próby logowania :socialAccount wystąpił błąd: \n:error", + 'social_account_in_use' => 'To konto :socialAccount jest już w użyciu. Spróbuj zalogować się za pomocą opcji :socialAccount.', + 'social_account_email_in_use' => 'E-mail :email jest już w użyciu. Jeśli masz już konto, połącz konto :socialAccount z poziomu ustawień profilu.', 'social_account_existing' => 'Konto :socialAccount jest już połączone z Twoim profilem', 'social_account_already_used_existing' => 'Konto :socialAccount jest już używane przez innego użytkownika.', 'social_account_not_used' => 'To konto :socialAccount nie jest połączone z żadnym użytkownikiem. Połącz je ze swoim kontem w ustawieniach profilu. ', @@ -30,26 +31,31 @@ return [ 'social_driver_not_configured' => 'Ustawienia konta :socialAccount nie są poprawne.', // System - 'path_not_writable' => 'Zapis do ścieżki :filePath jest niemożliwy. Upewnij się że aplikacja ma prawa do zapisu w niej.', + 'path_not_writable' => 'Zapis do ścieżki :filePath jest niemożliwy. Upewnij się że aplikacja ma prawa do zapisu plików na serwerze.', 'cannot_get_image_from_url' => 'Nie można pobrać obrazka z :url', 'cannot_create_thumbs' => 'Serwer nie może utworzyć miniaturek. Upewnij się że rozszerzenie GD PHP zostało zainstalowane.', - 'server_upload_limit' => 'Serwer nie pozwala na przyjęcie pliku o tym rozmiarze. Spróbuj udostępnić coś o mniejszym rozmiarze.', - 'image_upload_error' => 'Wystąpił błąd podczas udostępniania obrazka', + 'server_upload_limit' => 'Serwer nie pozwala na przyjęcie pliku o tym rozmiarze. Spróbuj przesłać plik o mniejszym rozmiarze.', + 'uploaded' => 'Serwer nie pozwala na przyjęcie pliku o tym rozmiarze. Spróbuj przesłać plik o mniejszym rozmiarze.', + 'image_upload_error' => 'Wystąpił błąd podczas przesyłania obrazka', + 'image_upload_type_error' => 'Typ przesłanego obrazka jest nieprwidłowy.', // Attachments - 'attachment_page_mismatch' => 'Niezgodność stron podczas aktualizacji załącznika', + 'attachment_page_mismatch' => 'Niezgodność strony podczas aktualizacji załącznika', + 'attachment_not_found' => 'Nie znaleziono załącznika', // Pages - 'page_draft_autosave_fail' => 'Zapis szkicu nie powiódł się. Upewnij się że posiadasz połączenie z internetem.', + 'page_draft_autosave_fail' => 'Zapis wersji roboczej nie powiódł się. Upewnij się, że posiadasz połączenie z internetem.', + 'page_custom_home_deletion' => 'Nie można usunąć strony, jeśli jest ona ustawiona jako strona główna', // Entities - 'entity_not_found' => 'Encja nie została odnaleziona', - 'book_not_found' => 'Księga nie została odnaleziona', - 'page_not_found' => 'Strona nie została odnaleziona', - 'chapter_not_found' => 'Rozdział nie został odnaleziony', - 'selected_book_not_found' => 'Wybrana księga nie została odnaleziona', - 'selected_book_chapter_not_found' => 'Wybrana księga lub rozdział nie zostały odnalezione', - 'guests_cannot_save_drafts' => 'Goście nie mogą zapisywać szkiców', + 'entity_not_found' => 'Nie znaleziono obiektu', + 'bookshelf_not_found' => 'Nie znaleziono półki', + 'book_not_found' => 'Nie znaleziono podręcznika', + 'page_not_found' => 'Nie znaleziono strony', + 'chapter_not_found' => 'Nie znaleziono rozdziału', + 'selected_book_not_found' => 'Wybrany podręcznik nie został znaleziony', + 'selected_book_chapter_not_found' => 'Wybrany podręcznik lub rozdział nie został znaleziony', + 'guests_cannot_save_drafts' => 'Goście nie mogą zapisywać wersji roboczych', // Users 'users_cannot_delete_only_admin' => 'Nie możesz usunąć jedynego administratora', @@ -58,13 +64,20 @@ return [ // Roles 'role_cannot_be_edited' => 'Ta rola nie może być edytowana', 'role_system_cannot_be_deleted' => 'Ta rola jest rolą systemową i nie może zostać usunięta', - 'role_registration_default_cannot_delete' => 'Ta rola nie może zostać usunięta jeśli jest ustawiona jako domyślna rola użytkownika', - + 'role_registration_default_cannot_delete' => 'Ta rola nie może zostać usunięta, dopóki jest ustawiona jako domyślna rola użytkownika', + + // Comments + 'comment_list' => 'Wystąpił błąd podczas pobierania komentarzy.', + 'cannot_add_comment_to_draft' => 'Nie możesz dodawać komentarzy do wersji roboczej.', + 'comment_add' => 'Wystąpił błąd podczas dodwania / aktualizaowania komentarza.', + 'comment_delete' => 'Wystąpił błąd podczas usuwania komentarza.', + 'empty_comment' => 'Nie można dodać pustego komentarza.', + // Error pages - '404_page_not_found' => 'Strona nie została odnaleziona', - 'sorry_page_not_found' => 'Przepraszamy, ale strona której szukasz nie została odnaleziona.', + '404_page_not_found' => 'Strona nie została znaleziona', + 'sorry_page_not_found' => 'Przepraszamy, ale strona której szukasz nie została znaleziona.', 'return_home' => 'Powrót do strony głównej', 'error_occurred' => 'Wystąpił błąd', 'app_down' => ':appName jest aktualnie wyłączona', 'back_soon' => 'Niedługo zostanie uruchomiona ponownie.', -]; \ No newline at end of file +]; diff --git a/resources/lang/pl/passwords.php b/resources/lang/pl/passwords.php index a9e669f4d..a9103d593 100644 --- a/resources/lang/pl/passwords.php +++ b/resources/lang/pl/passwords.php @@ -14,7 +14,7 @@ return [ */ 'password' => 'Hasło musi zawierać co najmniej 6 znaków i być zgodne z powtórzeniem.', - 'user' => "Nie znaleziono użytkownika o takim adresie email.", + 'user' => "Nie znaleziono użytkownika o takim adresie e-mail.", 'token' => 'Ten token resetowania hasła jest nieprawidłowy.', 'sent' => 'Wysłaliśmy Ci link do resetowania hasła!', 'reset' => 'Twoje hasło zostało zresetowane!', diff --git a/resources/lang/pl/settings.php b/resources/lang/pl/settings.php index d6f3cca1b..e1c8c7b8d 100644 --- a/resources/lang/pl/settings.php +++ b/resources/lang/pl/settings.php @@ -18,21 +18,24 @@ return [ 'app_settings' => 'Ustawienia aplikacji', 'app_name' => 'Nazwa aplikacji', - 'app_name_desc' => 'Ta nazwa jest wyświetlana w nagłówku i emailach.', + 'app_name_desc' => 'Ta nazwa jest wyświetlana w nagłówku i e-mailach.', 'app_name_header' => 'Pokazać nazwę aplikacji w nagłówku?', 'app_public_viewing' => 'Zezwolić na publiczne przeglądanie?', - 'app_secure_images' => 'Odblokować wyższe bezpieczeństwo obrazków?', - 'app_secure_images_desc' => 'Ze względów wydajnościowych wszystkie obrazki są publiczne. Ta opcja dodaje dodatkowy, trudny do zgadnienia losowy ciąg na początku nazwy obrazka. Upewnij się że indeksowanie ścieżek jest zablokowane, by uniknąć problemów z dostępem do obrazka.', + 'app_secure_images' => 'Włączyć przesyłanie obrazów o wyższym poziomie bezpieczeństwa?', + 'app_secure_images_desc' => 'Ze względów wydajnościowych wszystkie obrazki są publiczne. Ta opcja dodaje dodatkowy, trudny do odgadnięcia losowy ciąg na początku nazwy obrazka. Upewnij się że indeksowanie katalogów jest zablokowane, aby uniemożliwić łatwy dostęp do obrazków.', 'app_editor' => 'Edytor strony', 'app_editor_desc' => 'Wybierz edytor używany przez użytkowników do edycji zawartości.', - 'app_custom_html' => 'Własna zawartość tagu ', - 'app_custom_html_desc' => 'Zawartość dodana tutaj zostanie dołączona do sekcji każdej strony. Przydatne przy nadpisywaniu styli lub dodawaniu analityki.', + 'app_custom_html' => 'Własna zawartość w tagu ', + 'app_custom_html_desc' => 'Zawartość dodana tutaj zostanie dołączona na dole sekcji każdej strony. Przydatne przy nadpisywaniu styli lub dodawaniu analityki.', 'app_logo' => 'Logo aplikacji', - 'app_logo_desc' => 'Ten obrazek powinien mieć nie więcej niż 43px w pionie.
Większe obrazki będą skalowane w dół.', + 'app_logo_desc' => 'Ten obrazek powinien mieć nie więcej niż 43px wysokosci.
Większe obrazki zostaną zmniejszone.', 'app_primary_color' => 'Podstawowy kolor aplikacji', 'app_primary_color_desc' => 'To powinna być wartość HEX.
Zostaw to pole puste, by powrócić do podstawowego koloru.', + '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_disable_comments' => 'Wyłącz komentarze', - 'app_disable_comments_desc' => 'Wyłącz komentarze na wszystkich stronach w aplikacji. Istniejące komentarze nie są pokazywane.', + 'app_disable_comments_desc' => 'Wyłącz komentarze na wszystkich stronach w aplikacji. Istniejące komentarze nie będą pokazywane.', /** * Registration settings @@ -41,18 +44,31 @@ return [ 'reg_settings' => 'Ustawienia rejestracji', 'reg_allow' => 'Zezwolić na rejestrację?', 'reg_default_role' => 'Domyślna rola użytkownika po rejestracji', - 'reg_confirm_email' => 'Wymagać potwierdzenia adresu email?', - 'reg_confirm_email_desc' => 'Jeśli restrykcje domenowe zostały uzupełnione potwierdzenie adresu stanie się konieczne, a poniższa wartośc zostanie zignorowana.', - 'reg_confirm_restrict_domain' => 'Restrykcje domenowe dot. adresu email', - 'reg_confirm_restrict_domain_desc' => 'Wprowadź listę domen adresów email rozdzieloną przecinkami, którym chciałbyś zezwolić na rejestrację. Wymusi to konieczność potwierdzenia adresu email przez użytkownika przed uzyskaniem dostępu do aplikacji.
Pamiętaj, że użytkownicy będą mogli zmienić adres email po rejestracji.', + 'reg_confirm_email' => 'Wymagać potwierdzenia adresu e-mail?', + 'reg_confirm_email_desc' => 'Jeśli restrykcje domenowe zostały ustawione, potwierdzenie adresu stanie się konieczne, a poniższa wartośc zostanie zignorowana.', + 'reg_confirm_restrict_domain' => 'Restrykcje domenowe dot. adresu e-mail', + 'reg_confirm_restrict_domain_desc' => 'Wprowadź listę domen adresów e-mail, rozdzieloną przecinkami, którym chciałbyś zezwolić na rejestrację. Wymusi to konieczność potwierdzenia adresu e-mail przez użytkownika przed uzyskaniem dostępu do aplikacji.
Pamiętaj, że użytkownicy będą mogli zmienić adres e-mail po rejestracji.', 'reg_confirm_restrict_domain_placeholder' => 'Brak restrykcji', + /** + * Maintenance settings + */ + + '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_image_cleanup_ignore_revisions' => 'Ignoruje obrazki w poprzednich wersjach', + '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!', + 'maint_image_cleanup_nothing_found' => 'Nie znaleziono żadnych nieużywanych obrazków. Nic nie zostało usunięte!', + /** * Role settings */ 'roles' => 'Role', - 'role_user_roles' => 'Role użytkownika', + 'role_user_roles' => 'Role użytkowników', 'role_create' => 'Utwórz nową rolę', 'role_create_success' => 'Rola utworzona pomyślnie', 'role_delete' => 'Usuń rolę', @@ -65,14 +81,16 @@ return [ 'role_details' => 'Szczegóły roli', 'role_name' => 'Nazwa roli', 'role_desc' => 'Krótki opis roli', + 'role_external_auth_id' => 'Zewnętrzne identyfikatory uwierzytelniania', 'role_system' => 'Uprawnienia systemowe', 'role_manage_users' => 'Zarządzanie użytkownikami', 'role_manage_roles' => 'Zarządzanie rolami i uprawnieniami ról', - 'role_manage_entity_permissions' => 'Zarządzanie uprawnieniami ksiąg, rozdziałów i stron', - 'role_manage_own_entity_permissions' => 'Zarządzanie uprawnieniami własnych ksiąg, rozdziałów i stron', + 'role_manage_entity_permissions' => 'Zarządzanie uprawnieniami podręczników, rozdziałów i stron', + 'role_manage_own_entity_permissions' => 'Zarządzanie uprawnieniami własnych podręczników, rozdziałów i stron', 'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji', 'role_asset' => 'Zarządzanie zasobami', - 'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia ksiąg, rozdziałów i stron nadpisują te ustawienia.', + 'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia podręczników, 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', 'role_own' => 'Własne', 'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione', @@ -90,7 +108,7 @@ return [ 'users_add_new' => 'Dodaj użytkownika', 'users_search' => 'Wyszukaj użytkownika', 'users_role' => 'Role użytkownika', - 'users_external_auth_id' => 'Zewnętrzne ID autentykacji', + 'users_external_auth_id' => 'Zewnętrzne identyfikatory autentykacji', 'users_password_warning' => 'Wypełnij poniżej tylko jeśli chcesz zmienić swoje hasło:', 'users_system_public' => 'Ten użytkownik reprezentuje każdego gościa odwiedzającego tę aplikację. Nie można się na niego zalogować, lecz jest przyznawany automatycznie.', 'users_delete' => 'Usuń użytkownika', @@ -102,10 +120,10 @@ return [ 'users_edit_profile' => 'Edytuj profil', 'users_edit_success' => 'Użytkownik zaktualizowany pomyśłnie', 'users_avatar' => 'Avatar użytkownika', - 'users_avatar_desc' => 'Ten obrazek powinien mieć 25px x 256px.', + 'users_avatar_desc' => 'Ten obrazek powinien posiadać wymiary 256x256px.', 'users_preferred_language' => 'Preferowany język', 'users_social_accounts' => 'Konta społecznościowe', - 'users_social_accounts_info' => 'Tutaj możesz połączyć kilka kont społecznościowych w celu łatwiejszego i szybszego logowania.', + 'users_social_accounts_info' => 'Tutaj możesz połączyć kilka kont społecznościowych w celu łatwiejszego i szybszego logowania. Odłączenie konta tutaj nie autoryzowało dostępu. Odwołaj dostęp z ustawień profilu na podłączonym koncie społecznościowym.', 'users_social_connect' => 'Podłącz konto', 'users_social_disconnect' => 'Odłącz konto', 'users_social_connected' => ':socialAccount zostało dodane do Twojego profilu.', From d6dd96e7fcebf5e11b3fca918fe24c3e34b885f4 Mon Sep 17 00:00:00 2001 From: Jurij Vasiliev Date: Thu, 13 Dec 2018 13:58:08 +0100 Subject: [PATCH 15/43] 1. Fixed translation for Copy and Reply --- resources/lang/pl/common.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/pl/common.php b/resources/lang/pl/common.php index a03cd61fe..b97925222 100644 --- a/resources/lang/pl/common.php +++ b/resources/lang/pl/common.php @@ -31,8 +31,8 @@ return [ 'edit' => 'Edytuj', 'sort' => 'Sortuj', 'move' => 'Przenieś', - 'copy' => 'Copy', - 'reply' => 'Reply', + 'copy' => 'Skopiuj', + 'reply' => 'Odpowiedz', 'delete' => 'Usuń', 'search' => 'Szukaj', 'search_clear' => 'Wyczyść wyszukiwanie', From 86a00a59d4b3143acd1282706cf49e224918bf1a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 14 Dec 2018 21:23:05 +0000 Subject: [PATCH 16/43] Created sketchy translation formatter script Compares a translation file to a EN version to place translations on matching line numbers and matches up comments. --- resources/lang/en/activities.php | 10 +- resources/lang/en/validation.php | 44 ++----- resources/lang/format.php | 201 +++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 41 deletions(-) create mode 100755 resources/lang/format.php diff --git a/resources/lang/en/activities.php b/resources/lang/en/activities.php index 153ae33f0..4cac54b2a 100644 --- a/resources/lang/en/activities.php +++ b/resources/lang/en/activities.php @@ -1,12 +1,10 @@ 'created page', 'page_create_notification' => 'Page Successfully Created', diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index b75af7485..0b6a1c170 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -1,18 +1,13 @@ 'The :attribute must be accepted.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', @@ -75,34 +70,13 @@ return [ 'unique' => 'The :attribute has already been taken.', 'url' => 'The :attribute format is invalid.', - /* - |-------------------------------------------------------------------------- - | Custom Validation Language Lines - |-------------------------------------------------------------------------- - | - | Here you may specify custom validation messages for attributes using the - | convention "attribute.rule" to name the lines. This makes it quick to - | specify a specific custom language line for a given attribute rule. - | - */ - + // Custom validation lines 'custom' => [ 'password-confirm' => [ 'required_with' => 'Password confirmation required', ], ], - /* - |-------------------------------------------------------------------------- - | Custom Validation Attributes - |-------------------------------------------------------------------------- - | - | The following language lines are used to swap attribute place-holders - | with something more reader friendly such as E-Mail Address instead - | of "email". This simply helps us make messages a little cleaner. - | - */ - + // Custom validation attributes 'attributes' => [], - ]; diff --git a/resources/lang/format.php b/resources/lang/format.php new file mode 100755 index 000000000..8698d1bb7 --- /dev/null +++ b/resources/lang/format.php @@ -0,0 +1,201 @@ +#!/usr/bin/env php + $line) { + $trimLine = trim($line); + if ($mode === 'header') { + $formatted[$index] = $line; + if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body'; + } + + if ($mode === 'body') { + $matches = []; + + // Comment + if (strpos($trimLine, '//') === 0) { + $formatted[$index] = "\t" . $trimLine; + continue; + } + + // Arrays + $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches); + $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine); + $indent = count($arrayKeys) + 1; + if ($arrayStartMatch === 1) { + $arrayKeys[] = $matches[1]; + $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> ["; + if ($arrayEndMatch !== 1) continue; + } + if ($arrayEndMatch === 1) { + unsetArrayByKeys($langContent, $arrayKeys); + $key = array_pop($arrayKeys); + if (isset($formatted[$index])) { + $formatted[$index] .= '],'; + } else { + $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],"; + } + continue; + } + + // Translation + $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\'(.*)?\'.+?$/', $trimLine, $matches); + if ($translationMatch === 1) { + $key = $matches[1]; + $keys = array_merge($arrayKeys, [$key]); + $langVal = getTranslationByKeys($langContent, $keys); + if (empty($langVal)) continue; + + $keyPad = $longestKeyLength; + if (count($arrayKeys) === 0) { + unset($langContent[$key]); + } else { + $keyPad = longestKey(getTranslationByKeys($enContent, $arrayKeys)); + } + + $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad); + continue; + } + } + +} + +// Fill missing lines +$arraySize = max(array_keys($formatted)); +$formatted = array_replace(array_fill(0, $arraySize, ''), $formatted); + +// Add remaining translations +$langContent = array_filter($langContent, function($item) { + return !is_null($item) && !empty($item); +}); +if (count($langContent) > 0) { + $formatted[] = ''; + $formatted[] = "\t// Unmatched"; +} +foreach ($langContent as $key => $value) { + if (is_array($value)) { + $formatted[] = formatTranslationArray($key, $value); + } else { + $formatted[] = formatTranslationLine($key, $value); + } +} + +// Add end line +$formatted[] = '];'; +$formatted = implode("\n", $formatted); + +writeLangFile($lang, $fileName, $formatted); + +function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) { + $escapedValue = str_replace("'", "\\'", $value); + return str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ') ."=> '{$escapedValue}',"; +} + +function longestKey(array $array) { + $top = 0; + foreach ($array as $key => $value) { + $keyLen = strlen($key); + $top = max($top, $keyLen); + } + return $top + 3; +} + +function formatTranslationArray(string $key, array $array) { + $arrayPHP = var_export($array, true); + return " '{$key}' => {$arrayPHP},"; +} + +function getTranslationByKeys(array $translations, array $keys) { + $val = $translations; + foreach ($keys as $key) { + $val = $val[$key] ?? ''; + if ($val === '') return ''; + } + return $val; +} + +function unsetArrayByKeys(array &$input, array $keys) { + $val = &$input; + $lastIndex = count($keys) - 1; + foreach ($keys as $index => &$key) { + if ($index === $lastIndex && is_array($val)) { + unset($val[$key]); + } + if (!is_array($val)) return; + $val = &$val[$key] ?? []; + } +} + +function writeLangFile(string $lang, string $fileName, string $content) { + $path = __DIR__ . "/{$lang}/{$fileName}.php"; + if (!file_exists($path)) { + errorOut("Expected translation file '{$path}' does not exist"); + } + file_put_contents($path, $content); +} + +function loadLangFileLines(string $lang, string $fileName) { + $path = __DIR__ . "/{$lang}/{$fileName}.php"; + if (!file_exists($path)) { + errorOut("Expected translation file '{$path}' does not exist"); + } + $lines = explode("\n", file_get_contents($path)); + return array_map(function($line) { + return trim($line, "\r"); + }, $lines); +} + +function loadLang(string $lang, string $fileName) { + $path = __DIR__ . "/{$lang}/{$fileName}.php"; + if (!file_exists($path)) { + errorOut("Expected translation file '{$path}' does not exist"); + } + + $fileData = include($path); + return $fileData; +} + +function formatLang($lang) { + $langParts = explode('_', strtoupper($lang)); + $langParts[0] = strtolower($langParts[0]); + return implode('_', $langParts); +} + +function dd($content) { + print_r($content); + exit(1); +} + +function info($text) { + echo "\e[34m" . $text . "\e[0m\n"; +} + +function errorOut($text) { + echo "\e[31m" . $text . "\e[0m\n"; + exit(1); +} \ No newline at end of file From 2753629dbe3dcfdc0d2d9393384ba7a35a8a4c8a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 16 Dec 2018 13:12:13 +0000 Subject: [PATCH 17/43] Cleaned up script and formatted remaining EN files --- resources/lang/en/auth.php | 29 +--- resources/lang/en/common.php | 27 +-- resources/lang/en/components.php | 11 +- resources/lang/en/entities.php | 48 ++---- resources/lang/en/errors.php | 9 +- resources/lang/en/pagination.php | 17 +- resources/lang/en/passwords.php | 17 +- resources/lang/en/settings.php | 38 ++-- resources/lang/format.php | 286 +++++++++++++++++++++---------- 9 files changed, 259 insertions(+), 223 deletions(-) diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php index a1232efc6..436734816 100644 --- a/resources/lang/en/auth.php +++ b/resources/lang/en/auth.php @@ -1,21 +1,15 @@ 'These credentials do not match our records.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', - /** - * Login & Register - */ + // Login & Register 'sign_up' => 'Sign up', 'log_in' => 'Log in', 'log_in_with' => 'Login with :socialDriver', @@ -43,23 +37,18 @@ return [ 'register_success' => 'Thanks for signing up! You are now registered and signed in.', - /** - * Password Reset - */ + // 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_success' => 'A password reset link has been sent to :email.', '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.', - /** - * Email Confirmation - */ + // 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:', diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index 8e86129e2..ac2edc621 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -1,9 +1,10 @@ 'Cancel', 'confirm' => 'Confirm', 'back' => 'Back', @@ -12,18 +13,14 @@ return [ 'select' => 'Select', 'more' => 'More', - /** - * Form Labels - */ + // Form Labels 'name' => 'Name', 'description' => 'Description', 'role' => 'Role', 'cover_image' => 'Cover image', 'cover_image_description' => 'This image should be approx 440x250px.', - /** - * Actions - */ + // Actions 'actions' => 'Actions', 'view' => 'View', 'create' => 'Create', @@ -40,9 +37,7 @@ return [ 'remove' => 'Remove', 'add' => 'Add', - /** - * Misc - */ + // Misc 'deleted_user' => 'Deleted User', 'no_activity' => 'No activity to show', 'no_items' => 'No items available', @@ -54,15 +49,11 @@ return [ 'list_view' => 'List View', 'default' => 'Default', - /** - * Header - */ + // Header 'view_profile' => 'View Profile', 'edit_profile' => 'Edit Profile', - /** - * Email 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', ]; \ No newline at end of file diff --git a/resources/lang/en/components.php b/resources/lang/en/components.php index c093f7316..d8e8981fb 100644 --- a/resources/lang/en/components.php +++ b/resources/lang/en/components.php @@ -1,9 +1,10 @@ 'Image Select', 'image_all' => 'All', 'image_all_title' => 'View all images', @@ -24,9 +25,7 @@ return [ 'image_delete_success' => 'Image successfully deleted', 'image_upload_remove' => 'Remove', - /** - * Code editor - */ + // Code Editor 'code_editor' => 'Edit Code', 'code_language' => 'Code Language', 'code_content' => 'Code Content', diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index 810b25f45..2a64f57a3 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -1,9 +1,11 @@ 'Recently Created', 'recently_created_pages' => 'Recently Created Pages', 'recently_updated_pages' => 'Recently Updated Pages', @@ -31,17 +33,13 @@ return [ 'export_pdf' => 'PDF File', 'export_text' => 'Plain Text File', - /** - * Permissions and restrictions - */ + // 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', - /** - * Search - */ + // Search 'search_results' => 'Search Results', 'search_total_results_found' => ':count result found|:count total results found', 'search_clear' => 'Clear Search', @@ -66,9 +64,7 @@ return [ 'search_set_date' => 'Set Date', 'search_update' => 'Update Search', - /** - * Shelves - */ + // Shelves 'shelf' => 'Shelf', 'shelves' => 'Shelves', 'shelves_long' => 'Bookshelves', @@ -98,9 +94,7 @@ return [ '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 - */ + // Books 'book' => 'Book', 'books' => 'Books', 'x_books' => ':count Book|:count Books', @@ -134,9 +128,7 @@ return [ 'books_sort_show_other' => 'Show Other Books', 'books_sort_save' => 'Save New Order', - /** - * Chapters - */ + // Chapters 'chapter' => 'Chapter', 'chapters' => 'Chapters', 'x_chapters' => ':count Chapter|:count Chapters', @@ -159,9 +151,7 @@ return [ 'chapters_permissions_success' => 'Chapter Permissions Updated', 'chapters_search_this' => 'Search this chapter', - /** - * Pages - */ + // Pages 'page' => 'Page', 'pages' => 'Pages', 'x_pages' => ':count Page|:count Pages', @@ -235,9 +225,7 @@ return [ 'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content', 'pages_specific' => 'Specific Page', - /** - * Editor sidebar - */ + // Editor Sidebar 'page_tags' => 'Page Tags', 'chapter_tags' => 'Chapter Tags', 'book_tags' => 'Book Tags', @@ -273,18 +261,14 @@ return [ 'attachments_file_updated' => 'File successfully updated', 'attachments_link_attached' => 'Link successfully attached to page', - /** - * Profile View - */ + // 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', - /** - * Comments - */ + // Comments 'comment' => 'Comment', 'comments' => 'Comments', 'comment_add' => 'Add Comment', @@ -302,9 +286,7 @@ return [ 'comment_delete_confirm' => 'Are you sure you want to delete this comment?', 'comment_in_reply_to' => 'In reply to :commentId', - /** - * Revision - */ + // Revision 'revision_delete_confirm' => 'Are you sure you want to delete this revision?', 'revision_delete_success' => 'Revision deleted', 'revision_cannot_delete_latest' => 'Cannot delete the latest revision.' diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php index fb09841cf..eb73b880b 100644 --- a/resources/lang/en/errors.php +++ b/resources/lang/en/errors.php @@ -1,11 +1,9 @@ 'You do not have permission to access the requested page.', 'permissionJson' => 'You do not have permission to perform the requested action.', @@ -80,4 +78,5 @@ return [ 'error_occurred' => 'An Error Occurred', 'app_down' => ':appName is down right now', 'back_soon' => 'It will be back up soon.', + ]; diff --git a/resources/lang/en/pagination.php b/resources/lang/en/pagination.php index fcab34b25..85bd12fc3 100644 --- a/resources/lang/en/pagination.php +++ b/resources/lang/en/pagination.php @@ -1,18 +1,11 @@ '« Previous', 'next' => 'Next »', diff --git a/resources/lang/en/passwords.php b/resources/lang/en/passwords.php index 7c10cba1a..9f7d9e3cb 100644 --- a/resources/lang/en/passwords.php +++ b/resources/lang/en/passwords.php @@ -1,18 +1,11 @@ 'Passwords must be at least six characters and match the confirmation.', 'user' => "We can't find a user with that e-mail address.", 'token' => 'This password reset token is invalid.', diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index f5bd8e27d..6e48c517d 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -1,21 +1,17 @@ 'Settings', 'settings_save' => 'Save Settings', 'settings_save_success' => 'Settings saved', - /** - * App settings - */ - + // App Settings 'app_settings' => 'App Settings', 'app_name' => 'Application name', 'app_name_desc' => 'This name is shown in the header and any emails.', @@ -37,10 +33,7 @@ return [ 'app_disable_comments' => 'Disable comments', 'app_disable_comments_desc' => 'Disable comments across all pages in the application. Existing comments are not shown.', - /** - * Registration settings - */ - + // Registration Settings 'reg_settings' => 'Registration Settings', 'reg_allow' => 'Allow registration?', 'reg_default_role' => 'Default user role after registration', @@ -50,10 +43,7 @@ return [ 'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application.
Note that users will be able to change their email addresses after successful registration.', 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', - /** - * Maintenance settings - */ - + // 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.", @@ -63,10 +53,7 @@ return [ 'maint_image_cleanup_success' => ':count potentially unused images found and deleted!', 'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!', - /** - * Role settings - */ - + // Role Settings 'roles' => 'Roles', 'role_user_roles' => 'User Roles', 'role_create' => 'Create New Role', @@ -99,10 +86,7 @@ return [ 'role_users' => 'Users in this role', 'role_users_none' => 'No users are currently assigned to this role', - /** - * Users - */ - + // Users 'users' => 'Users', 'user_profile' => 'User Profile', 'users_add_new' => 'Add New User', diff --git a/resources/lang/format.php b/resources/lang/format.php index 8698d1bb7..6815268c6 100755 --- a/resources/lang/format.php +++ b/resources/lang/format.php @@ -4,119 +4,150 @@ /** * Format a language file in the same way as the EN equivalent. * Matches the line numbers of translated content. + * Potentially destructive, Ensure you have a backup of your translation content before running. */ $args = array_slice($argv, 1); if (count($args) < 2) { - errorOut("Please provide a language code as the first argument and a translation file name as the second (./format.php fr activities)"); + errorOut("Please provide a language code as the first argument and a translation file name, or '--all', as the second (./format.php fr activities)"); } -$lang = formatLang($args[0]); +$lang = formatLocale($args[0]); $fileName = explode('.', $args[1])[0]; +$filesNames = [$fileName]; +if ($fileName === '--all') { + $fileNames = getTranslationFileNames(); +} -$enLines = loadLangFileLines('en', $fileName); -$langContent = loadLang($lang, $fileName); -$enContent = loadLang('en', $fileName); +foreach ($fileNames as $fileName) { + $formatted = formatFileContents($lang, $fileName); + writeLangFile($lang, $fileName, $formatted); +} -// Calculate the longest top-level key length -$longestKeyLength = longestKey($enContent); -// Start formatted content -$formatted = []; -$mode = 'header'; -$arrayKeys = []; +/** + * Format the contents of a single translation file in the given language. + * @param string $lang + * @param string $fileName + * @return string + */ +function formatFileContents(string $lang, string $fileName) : string { + $enLines = loadLangFileLines('en', $fileName); + $langContent = loadLang($lang, $fileName); + $enContent = loadLang('en', $fileName); -foreach($enLines as $index => $line) { - $trimLine = trim($line); - if ($mode === 'header') { - $formatted[$index] = $line; - if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body'; - } + // Calculate the longest top-level key length + $longestKeyLength = calculateKeyPadding($enContent); - if ($mode === 'body') { - $matches = []; + // Start formatted content + $formatted = []; + $mode = 'header'; + $arrayKeys = []; - // Comment - if (strpos($trimLine, '//') === 0) { - $formatted[$index] = "\t" . $trimLine; - continue; + foreach($enLines as $index => $line) { + $trimLine = trim($line); + if ($mode === 'header') { + $formatted[$index] = $line; + if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body'; } - // Arrays - $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches); - $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine); - $indent = count($arrayKeys) + 1; - if ($arrayStartMatch === 1) { - $arrayKeys[] = $matches[1]; - $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> ["; - if ($arrayEndMatch !== 1) continue; - } - if ($arrayEndMatch === 1) { - unsetArrayByKeys($langContent, $arrayKeys); - $key = array_pop($arrayKeys); - if (isset($formatted[$index])) { - $formatted[$index] .= '],'; - } else { - $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],"; - } - continue; - } + if ($mode === 'body') { + $matches = []; - // Translation - $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\'(.*)?\'.+?$/', $trimLine, $matches); - if ($translationMatch === 1) { - $key = $matches[1]; - $keys = array_merge($arrayKeys, [$key]); - $langVal = getTranslationByKeys($langContent, $keys); - if (empty($langVal)) continue; - - $keyPad = $longestKeyLength; - if (count($arrayKeys) === 0) { - unset($langContent[$key]); - } else { - $keyPad = longestKey(getTranslationByKeys($enContent, $arrayKeys)); + // Comment + if (strpos($trimLine, '//') === 0) { + $formatted[$index] = "\t" . $trimLine; + continue; } - $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad); - continue; + // Arrays + $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches); + $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine); + $indent = count($arrayKeys) + 1; + if ($arrayStartMatch === 1) { + $arrayKeys[] = $matches[1]; + $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> ["; + if ($arrayEndMatch !== 1) continue; + } + if ($arrayEndMatch === 1) { + unsetArrayByKeys($langContent, $arrayKeys); + array_pop($arrayKeys); + if (isset($formatted[$index])) { + $formatted[$index] .= '],'; + } else { + $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],"; + } + continue; + } + + // Translation + $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\'(.*)?\'.+?$/', $trimLine, $matches); + if ($translationMatch === 1) { + $key = $matches[1]; + $keys = array_merge($arrayKeys, [$key]); + $langVal = getTranslationByKeys($langContent, $keys); + if (empty($langVal)) continue; + + $keyPad = $longestKeyLength; + if (count($arrayKeys) === 0) { + unset($langContent[$key]); + } else { + $keyPad = calculateKeyPadding(getTranslationByKeys($enContent, $arrayKeys)); + } + + $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad); + continue; + } + } + + } + + // Fill missing lines + $arraySize = max(array_keys($formatted)); + $formatted = array_replace(array_fill(0, $arraySize, ''), $formatted); + + // Add remaining translations + $langContent = array_filter($langContent, function($item) { + return !is_null($item) && !empty($item); + }); + if (count($langContent) > 0) { + $formatted[] = ''; + $formatted[] = "\t// Unmatched"; + } + foreach ($langContent as $key => $value) { + if (is_array($value)) { + $formatted[] = formatTranslationArray($key, $value); + } else { + $formatted[] = formatTranslationLine($key, $value); } } + // Add end line + $formatted[] = '];'; + return implode("\n", $formatted); } -// Fill missing lines -$arraySize = max(array_keys($formatted)); -$formatted = array_replace(array_fill(0, $arraySize, ''), $formatted); - -// Add remaining translations -$langContent = array_filter($langContent, function($item) { - return !is_null($item) && !empty($item); -}); -if (count($langContent) > 0) { - $formatted[] = ''; - $formatted[] = "\t// Unmatched"; -} -foreach ($langContent as $key => $value) { - if (is_array($value)) { - $formatted[] = formatTranslationArray($key, $value); - } else { - $formatted[] = formatTranslationLine($key, $value); - } -} - -// Add end line -$formatted[] = '];'; -$formatted = implode("\n", $formatted); - -writeLangFile($lang, $fileName, $formatted); - -function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) { +/** + * Format a translation line. + * @param string $key + * @param string $value + * @param int $indent + * @param int $keyPad + * @return string + */ +function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) : string { $escapedValue = str_replace("'", "\\'", $value); return str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ') ."=> '{$escapedValue}',"; } -function longestKey(array $array) { +/** + * Find the longest key in the array and provide the length + * for all keys to be used when printed. + * @param array $array + * @return int + */ +function calculateKeyPadding(array $array) : int { $top = 0; foreach ($array as $key => $value) { $keyLen = strlen($key); @@ -125,12 +156,27 @@ function longestKey(array $array) { return $top + 3; } -function formatTranslationArray(string $key, array $array) { +/** + * Format an translation array with the given key. + * Simply prints as an old-school php array. + * Used as a last-resort backup to save unused translations. + * @param string $key + * @param array $array + * @return string + */ +function formatTranslationArray(string $key, array $array) : string { $arrayPHP = var_export($array, true); return " '{$key}' => {$arrayPHP},"; } -function getTranslationByKeys(array $translations, array $keys) { +/** + * Find a string translation value within a multi-dimensional array + * by traversing the given array of keys. + * @param array $translations + * @param array $keys + * @return string|array + */ +function getTranslationByKeys(array $translations, array $keys) { $val = $translations; foreach ($keys as $key) { $val = $val[$key] ?? ''; @@ -139,6 +185,12 @@ function getTranslationByKeys(array $translations, array $keys) { return $val; } +/** + * Unset an inner item of a multi-dimensional array by + * traversing the given array of keys. + * @param array $input + * @param array $keys + */ function unsetArrayByKeys(array &$input, array $keys) { $val = &$input; $lastIndex = count($keys) - 1; @@ -151,6 +203,12 @@ function unsetArrayByKeys(array &$input, array $keys) { } } +/** + * Write the given content to a translation file. + * @param string $lang + * @param string $fileName + * @param string $content + */ function writeLangFile(string $lang, string $fileName, string $content) { $path = __DIR__ . "/{$lang}/{$fileName}.php"; if (!file_exists($path)) { @@ -159,7 +217,13 @@ function writeLangFile(string $lang, string $fileName, string $content) { file_put_contents($path, $content); } -function loadLangFileLines(string $lang, string $fileName) { +/** + * Load the contents of a language file as an array of text lines. + * @param string $lang + * @param string $fileName + * @return array + */ +function loadLangFileLines(string $lang, string $fileName) : array { $path = __DIR__ . "/{$lang}/{$fileName}.php"; if (!file_exists($path)) { errorOut("Expected translation file '{$path}' does not exist"); @@ -170,7 +234,13 @@ function loadLangFileLines(string $lang, string $fileName) { }, $lines); } -function loadLang(string $lang, string $fileName) { +/** + * Load the contents of a language file + * @param string $lang + * @param string $fileName + * @return array + */ +function loadLang(string $lang, string $fileName) : array { $path = __DIR__ . "/{$lang}/{$fileName}.php"; if (!file_exists($path)) { errorOut("Expected translation file '{$path}' does not exist"); @@ -180,21 +250,57 @@ function loadLang(string $lang, string $fileName) { return $fileData; } -function formatLang($lang) { +/** + * Fetch an array containing the names of all translation files without the extension. + * @return array + */ +function getTranslationFileNames() : array { + $dir = __DIR__ . "/en"; + if (!file_exists($dir)) { + errorOut("Expected directory '{$dir}' does not exist"); + } + $files = scandir($dir); + $fileNames = []; + foreach ($files as $file) { + if (substr($file, -4) === '.php') { + $fileNames[] = substr($file, 0, strlen($file) - 4); + } + } + return $fileNames; +} + +/** + * Format a locale to follow the lowercase_UPERCASE standard + * @param string $lang + * @return string + */ +function formatLocale(string $lang) : string { $langParts = explode('_', strtoupper($lang)); $langParts[0] = strtolower($langParts[0]); return implode('_', $langParts); } +/** + * Dump a variable then die. + * @param $content + */ function dd($content) { print_r($content); exit(1); } +/** + * Log out some information text in blue + * @param $text + */ function info($text) { echo "\e[34m" . $text . "\e[0m\n"; } +/** + * Log out an error in red and exit. + * @param $text + */ function errorOut($text) { echo "\e[31m" . $text . "\e[0m\n"; exit(1); From 1930ed4d6aba0070991a3ab525719cc7fac02daa Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 16 Dec 2018 14:04:04 +0000 Subject: [PATCH 18/43] Made some further fixes to the formatting script Takes into account single and double quotes. Ignores //! comments and the 'language_select' array. Language files may need some cleaning up and may encounter some other bugs when running. --- resources/lang/en/settings.php | 10 ++++----- resources/lang/format.php | 37 +++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 6e48c517d..736d91849 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -113,10 +113,10 @@ return [ 'users_social_connected' => ':socialAccount account was successfully attached to your profile.', 'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.', - // Since these labels are already localized this array does not need to be - // translated in the language-specific files. - // DELETE BELOW IF COPIED FROM EN - /////////////////////////////////// + //! Since these labels are already localized this array does not need to be + //! translated in the language-specific files. + //! DELETE BELOW IF COPIED FROM EN + //!//////////////////////////////// 'language_select' => [ 'en' => 'English', 'ar' => 'العربية', @@ -137,5 +137,5 @@ return [ 'zh_CN' => '简体中文', 'zh_TW' => '繁體中文' ] - /////////////////////////////////// + //!//////////////////////////////// ]; diff --git a/resources/lang/format.php b/resources/lang/format.php index 6815268c6..45d0b4842 100755 --- a/resources/lang/format.php +++ b/resources/lang/format.php @@ -15,7 +15,7 @@ if (count($args) < 2) { $lang = formatLocale($args[0]); $fileName = explode('.', $args[1])[0]; -$filesNames = [$fileName]; +$fileNames = [$fileName]; if ($fileName === '--all') { $fileNames = getTranslationFileNames(); } @@ -43,9 +43,10 @@ function formatFileContents(string $lang, string $fileName) : string { // Start formatted content $formatted = []; $mode = 'header'; + $skipArray = false; $arrayKeys = []; - foreach($enLines as $index => $line) { + foreach ($enLines as $index => $line) { $trimLine = trim($line); if ($mode === 'header') { $formatted[$index] = $line; @@ -54,6 +55,18 @@ function formatFileContents(string $lang, string $fileName) : string { if ($mode === 'body') { $matches = []; + $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine); + + if ($skipArray) { + if ($arrayEndMatch) $skipArray = false; + continue; + } + + // Comment to ignore + if (strpos($trimLine, '//!') === 0) { + $formatted[$index] = ""; + continue; + } // Comment if (strpos($trimLine, '//') === 0) { @@ -63,9 +76,13 @@ function formatFileContents(string $lang, string $fileName) : string { // Arrays $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches); - $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine); + $indent = count($arrayKeys) + 1; if ($arrayStartMatch === 1) { + if ($fileName === 'settings' && $matches[1] === 'language_select') { + $skipArray = true; + continue; + } $arrayKeys[] = $matches[1]; $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> ["; if ($arrayEndMatch !== 1) continue; @@ -82,7 +99,7 @@ function formatFileContents(string $lang, string $fileName) : string { } // Translation - $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\'(.*)?\'.+?$/', $trimLine, $matches); + $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?[\'"](.*)?[\'"].+?$/', $trimLine, $matches); if ($translationMatch === 1) { $key = $matches[1]; $keys = array_merge($arrayKeys, [$key]); @@ -137,8 +154,14 @@ function formatFileContents(string $lang, string $fileName) : string { * @return string */ function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) : string { - $escapedValue = str_replace("'", "\\'", $value); - return str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ') ."=> '{$escapedValue}',"; + $start = str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' '); + if (strpos($value, "\n") !== false) { + $escapedValue = '"' . str_replace("\n", '\n', $value) . '"'; + $escapedValue = '"' . str_replace('"', '\"', $escapedValue) . '"'; + } else { + $escapedValue = "'" . str_replace("'", "\\'", $value) . "'"; + } + return "{$start} => {$escapedValue},"; } /** @@ -153,7 +176,7 @@ function calculateKeyPadding(array $array) : int { $keyLen = strlen($key); $top = max($top, $keyLen); } - return $top + 3; + return min(35, $top + 2); } /** From 101a7b40b922e7045154e428a5daea5bf25080e2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 16 Dec 2018 15:38:49 +0000 Subject: [PATCH 19/43] Updated codemirror SQL mode name Now will highlight a lot more SQL syntax. Closes #1181. --- resources/assets/js/services/code.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/js/services/code.js b/resources/assets/js/services/code.js index 636b84133..cfeabd3be 100644 --- a/resources/assets/js/services/code.js +++ b/resources/assets/js/services/code.js @@ -52,7 +52,7 @@ const modeMap = { sh: 'shell', bash: 'shell', toml: 'toml', - sql: 'sql', + sql: 'text/x-sql', xml: 'xml', yaml: 'yaml', yml: 'yaml', From 651ae2f3be949b43c02d09789f540f5a19a09f53 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 16 Dec 2018 15:46:02 +0000 Subject: [PATCH 20/43] Fixed failing language test after addition of formatter --- tests/LanguageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LanguageTest.php b/tests/LanguageTest.php index 64cad4817..91abadc91 100644 --- a/tests/LanguageTest.php +++ b/tests/LanguageTest.php @@ -11,7 +11,7 @@ class LanguageTest extends TestCase public function setUp() { parent::setUp(); - $this->langs = array_diff(scandir(resource_path('lang')), ['..', '.', 'check.php']); + $this->langs = array_diff(scandir(resource_path('lang')), ['..', '.', 'check.php', 'format.php']); } public function test_locales_config_key_set_properly() From 7f6929d716ce20a1173e80aba80ef0a0d21c3e4a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 16 Dec 2018 20:44:57 +0000 Subject: [PATCH 21/43] Re-enabled plaintext view for email notifications Updated mail notifications to set the HTML and plaintext views since before no plaintext version was being created. Closes #1182 --- app/Notifications/ConfirmEmail.php | 35 ++++--------------- app/Notifications/MailNotification.php | 35 +++++++++++++++++++ app/Notifications/ResetPassword.php | 21 ++--------- config/mail.php | 16 +++++---- .../notifications/email-plain.blade.php | 2 -- 5 files changed, 55 insertions(+), 54 deletions(-) create mode 100644 app/Notifications/MailNotification.php diff --git a/app/Notifications/ConfirmEmail.php b/app/Notifications/ConfirmEmail.php index f3797e6e2..7ecadc298 100644 --- a/app/Notifications/ConfirmEmail.php +++ b/app/Notifications/ConfirmEmail.php @@ -1,17 +1,7 @@ -token = $token; } - /** - * Get the notification's delivery channels. - * - * @param mixed $notifiable - * @return array - */ - public function via($notifiable) - { - return ['mail']; - } - /** * Get the mail representation of the notification. * @@ -43,10 +22,10 @@ class ConfirmEmail extends Notification implements ShouldQueue public function toMail($notifiable) { $appName = ['appName' => setting('app-name')]; - return (new MailMessage) - ->subject(trans('auth.email_confirm_subject', $appName)) - ->greeting(trans('auth.email_confirm_greeting', $appName)) - ->line(trans('auth.email_confirm_text')) - ->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token)); + return $this->newMailMessage() + ->subject(trans('auth.email_confirm_subject', $appName)) + ->greeting(trans('auth.email_confirm_greeting', $appName)) + ->line(trans('auth.email_confirm_text')) + ->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token)); } } diff --git a/app/Notifications/MailNotification.php b/app/Notifications/MailNotification.php new file mode 100644 index 000000000..413ac6d73 --- /dev/null +++ b/app/Notifications/MailNotification.php @@ -0,0 +1,35 @@ +view([ + 'html' => 'vendor.notifications.email', + 'text' => 'vendor.notifications.email-plain' + ]); + } + +} \ No newline at end of file diff --git a/app/Notifications/ResetPassword.php b/app/Notifications/ResetPassword.php index 86e93228f..282aa335a 100644 --- a/app/Notifications/ResetPassword.php +++ b/app/Notifications/ResetPassword.php @@ -1,11 +1,7 @@ -token = $token; } - /** - * Get the notification's channels. - * - * @param mixed $notifiable - * @return array|string - */ - public function via($notifiable) - { - return ['mail']; - } - /** * Build the mail representation of the notification. * @@ -42,7 +27,7 @@ class ResetPassword extends Notification */ public function toMail() { - return (new MailMessage) + return $this->newMailMessage() ->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')])) ->line(trans('auth.email_reset_text')) ->action(trans('auth.reset_password'), baseUrl('password/reset/' . $this->token)) diff --git a/config/mail.php b/config/mail.php index 689be99b8..a5aff0239 100644 --- a/config/mail.php +++ b/config/mail.php @@ -110,15 +110,19 @@ return [ /* |-------------------------------------------------------------------------- - | Mail "Pretend" + | Markdown Mail Settings |-------------------------------------------------------------------------- | - | When this option is enabled, e-mail will not actually be sent over the - | web and will instead be written to your application's logs files so - | you may inspect the message. This is great for local development. + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! | */ - - 'pretend' => env('MAIL_PRETEND', false), + 'markdown' => [ + 'theme' => 'default', + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], ]; diff --git a/resources/views/vendor/notifications/email-plain.blade.php b/resources/views/vendor/notifications/email-plain.blade.php index 7ca1dc8d0..d8e2a0acd 100644 --- a/resources/views/vendor/notifications/email-plain.blade.php +++ b/resources/views/vendor/notifications/email-plain.blade.php @@ -2,8 +2,6 @@ if (! empty($greeting)) { echo $greeting, "\n\n"; -} else { - echo $level == 'error' ? 'Whoops!' : 'Hello!', "\n\n"; } if (! empty($introLines)) { From 106f32591d867a89f060ffa2a584f56e40977e78 Mon Sep 17 00:00:00 2001 From: Mantikor Date: Mon, 17 Dec 2018 14:10:54 +0200 Subject: [PATCH 22/43] Update app.php added 'uk' locale --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index 3040a36c6..3a704e83c 100755 --- a/config/app.php +++ b/config/app.php @@ -84,7 +84,7 @@ return [ */ 'locale' => env('APP_LANG', 'en'), - 'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'], + 'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'uk', 'zh_CN', 'zh_TW'], /* |-------------------------------------------------------------------------- From 9a444b4a04c2a2126d383ea828667c3f13b0a42f Mon Sep 17 00:00:00 2001 From: Mantikor Date: Mon, 17 Dec 2018 18:16:43 +0200 Subject: [PATCH 23/43] Update settings.php added 'uk' language --- resources/lang/en/settings.php | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 736d91849..ef947829d 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -134,6 +134,7 @@ return [ 'pl' => 'Polski', 'it' => 'Italian', 'ru' => 'Русский', + 'uk' => 'Українська', 'zh_CN' => '简体中文', 'zh_TW' => '繁體中文' ] From c67f76f776764563371ca81d1578ccb6ea14c20f Mon Sep 17 00:00:00 2001 From: Mantikor Date: Tue, 18 Dec 2018 10:00:45 +0200 Subject: [PATCH 24/43] Add files via upload --- resources/lang/activities.php | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 resources/lang/activities.php diff --git a/resources/lang/activities.php b/resources/lang/activities.php new file mode 100644 index 000000000..fd8c79660 --- /dev/null +++ b/resources/lang/activities.php @@ -0,0 +1,50 @@ + 'створив сторінку', + 'page_create_notification' => 'Сторінка успішно створена', + 'page_update' => 'оновив сторінку', + 'page_update_notification' => 'Сторінка успішно оновлена', + 'page_delete' => 'видалив сторінку', + 'page_delete_notification' => 'Сторінка успішно видалена', + 'page_restore' => 'відновив сторінку', + 'page_restore_notification' => 'Сторінка успішно відновлена', + 'page_move' => 'перемістив сторінку', + + // Chapters + 'chapter_create' => 'створив розділ', + 'chapter_create_notification' => 'Розділ успішно створено', + 'chapter_update' => 'оновив розділ', + 'chapter_update_notification' => 'Розділ успішно оновлено', + 'chapter_delete' => 'видалив розділ', + 'chapter_delete_notification' => 'Розділ успішно видалено', + 'chapter_move' => 'перемістив розділ', + + // Books + 'book_create' => 'створив книгу', + 'book_create_notification' => 'Книгу успішно створено', + 'book_update' => 'оновив книгу', + 'book_update_notification' => 'Книгу успішно оновлено', + 'book_delete' => 'видалив книгу', + 'book_delete_notification' => 'Книгу успішно видалено', + 'book_sort' => 'sorted книгу', + 'book_sort_notification' => 'Книгу успішно відновлено', + + // Bookshelves + 'bookshelf_create' => 'створено книжкову полицю', + 'bookshelf_create_notification' => 'Книжкову полицю успішно створено', + 'bookshelf_update' => 'оновив книжкову полицю', + 'bookshelf_update_notification' => 'Книжкову полицю успішно оновлено', + 'bookshelf_delete' => 'видалив книжкову полицю', + 'bookshelf_delete_notification' => 'Книжкову полицю успішно видалено', + + // Other + 'commented_on' => 'прокоментував', +]; From 7fcd7a5d9140806df489ec81dde10a5d022d7de9 Mon Sep 17 00:00:00 2001 From: Mantikor Date: Tue, 18 Dec 2018 10:01:18 +0200 Subject: [PATCH 25/43] Rename resources/lang/activities.php to resources/lang/uk/activities.php --- resources/lang/{ => uk}/activities.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/lang/{ => uk}/activities.php (100%) diff --git a/resources/lang/activities.php b/resources/lang/uk/activities.php similarity index 100% rename from resources/lang/activities.php rename to resources/lang/uk/activities.php From 37ab97af8c7f2221229898cce46461eb30c74a9a Mon Sep 17 00:00:00 2001 From: Mantikor Date: Tue, 18 Dec 2018 10:01:50 +0200 Subject: [PATCH 26/43] Add files via upload --- resources/lang/uk/auth.php | 67 +++++++ resources/lang/uk/common.php | 60 +++++++ resources/lang/uk/components.php | 34 ++++ resources/lang/uk/entities.php | 295 +++++++++++++++++++++++++++++++ resources/lang/uk/errors.php | 83 +++++++++ resources/lang/uk/pagination.php | 15 ++ resources/lang/uk/passwords.php | 16 ++ resources/lang/uk/settings.php | 119 +++++++++++++ resources/lang/uk/validation.php | 86 +++++++++ 9 files changed, 775 insertions(+) create mode 100644 resources/lang/uk/auth.php create mode 100644 resources/lang/uk/common.php create mode 100644 resources/lang/uk/components.php create mode 100644 resources/lang/uk/entities.php create mode 100644 resources/lang/uk/errors.php create mode 100644 resources/lang/uk/pagination.php create mode 100644 resources/lang/uk/passwords.php create mode 100644 resources/lang/uk/settings.php create mode 100644 resources/lang/uk/validation.php diff --git a/resources/lang/uk/auth.php b/resources/lang/uk/auth.php new file mode 100644 index 000000000..1fb3e41c8 --- /dev/null +++ b/resources/lang/uk/auth.php @@ -0,0 +1,67 @@ + 'Цей обліковий запис не знайдено.', + 'throttle' => 'Забагато спроб входу в систему. Будь ласка, спробуйте ще раз через :seconds секунд.', + + // Login & Register + 'sign_up' => 'Реєстрація', + 'log_in' => 'Увійти', + 'log_in_with' => 'Увійти з :socialDriver', + 'sign_up_with' => 'Зареєструватись з :socialDriver', + 'logout' => 'Вихід', + + 'name' => 'Ім’я', + 'username' => 'Логін', + 'email' => 'Email', + 'password' => 'Пароль', + 'password_confirm' => 'Підтвердження пароля', + 'password_hint' => 'Має бути більше 5 символів', + 'forgot_password' => 'Забули пароль?', + 'remember_me' => 'Запам’ятати мене', + 'ldap_email_hint' => 'Введіть email для цього облікового запису.', + 'create_account' => 'Створити обліковий запис', + 'social_login' => 'Вхід через соціальну мережу', + 'social_registration' => 'Реєстрація через соціальну мережу', + 'social_registration_text' => 'Реєстрація і вхід через інший сервіс', + + 'register_thanks' => 'Дякуємо за реєстрацію!', + 'register_confirm' => 'Будь ласка, перевірте свою електронну пошту та натисніть кнопку підтвердження, щоб отримати доступ до :appName.', + 'registrations_disabled' => 'Реєстрацію вимкнено', + 'registration_email_domain_invalid' => 'Цей домен електронної пошти не має доступу до реєстрації', + 'register_success' => 'Дякуємо за реєстрацію! Ви зареєстровані та ввійшли в систему.', + + + // Password Reset + 'reset_password' => 'Скинути пароль', + 'reset_password_send_instructions' => 'Введіть адресу електронної пошти нижче, і вам буде надіслано електронне повідомлення з посиланням на зміну пароля.', + 'reset_password_send_button' => 'Надіслати посилання для скидання', + 'reset_password_sent_success' => 'Посилання для скидання пароля було надіслано на :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_confirm_action' => 'Підтвердити Email', + 'email_confirm_send_error' => 'Необхідно підтвердження електронною поштою, але система не змогла надіслати електронний лист. Зверніться до адміністратора, щоб правильно налаштувати електронну пошту.', + 'email_confirm_success' => 'Ваш електронну адресу підтверджено!', + 'email_confirm_resent' => 'Лист з підтвердженням надіслано, перевірте свою пошту.', + + 'email_not_confirmed' => 'Адреса Email не підтверджена', + 'email_not_confirmed_text' => 'Ваша електронна адреса ще не підтверджена.', + 'email_not_confirmed_click_link' => 'Будь-ласка, натисніть на посилання в електронному листі, яке було надіслано після реєстрації.', + 'email_not_confirmed_resend' => 'Якщо ви не можете знайти електронний лист, ви можете повторно надіслати підтвердження електронною поштою, на формі нижче.', + 'email_not_confirmed_resend_button' => 'Повторне підтвердження електронної пошти', +]; \ No newline at end of file diff --git a/resources/lang/uk/common.php b/resources/lang/uk/common.php new file mode 100644 index 000000000..53bf0a5fb --- /dev/null +++ b/resources/lang/uk/common.php @@ -0,0 +1,60 @@ + 'Скасувати', + 'confirm' => 'Застосувати', + 'back' => 'Назад', + 'save' => 'Зберегти', + 'continue' => 'Продовжити', + 'select' => 'Вибрати', + 'more' => 'Ще', + + // Form Labels + 'name' => 'Назва', + 'description' => 'Опис', + 'role' => 'Роль', + 'cover_image' => 'Обкладинка', + 'cover_image_description' => 'Це зображення має бути приблизно 440x250px.', + + // Actions + 'actions' => 'Дії', + 'view' => 'Перегляд', + 'create' => 'Створити', + 'update' => 'Оновити', + 'edit' => 'Редагувати', + 'sort' => 'Сортувати', + 'move' => 'Перемістити', + 'copy' => 'Копіювати', + 'reply' => 'Відповісти', + 'delete' => 'Видалити', + 'search' => 'Шукати', + 'search_clear' => 'Очистити пошук', + 'reset' => 'Скинути', + 'remove' => 'Видалити', + 'add' => 'Додати', + + // Misc + 'deleted_user' => 'Видалений користувач', + 'no_activity' => 'Немає активності для показу', + 'no_items' => 'Немає доступних елементів', + 'back_to_top' => 'Повернутися до початку', + 'toggle_details' => 'Подробиці', + 'toggle_thumbnails' => 'Мініатюри', + 'details' => 'Деталі', + 'grid_view' => 'Вигляд Сіткою', + 'list_view' => 'Вигляд Списком', + 'default' => 'За замовчуванням', + + // Header + 'view_profile' => 'Переглянути профіль', + 'edit_profile' => 'Редагувати профіль', + + // Email Content + 'email_action_help' => 'Якщо у вас виникають проблеми при натисканні кнопки ":actionText", скопіюйте та вставте URL у свій веб-браузер:', + 'email_rights' => 'Всі права захищені', +]; \ No newline at end of file diff --git a/resources/lang/uk/components.php b/resources/lang/uk/components.php new file mode 100644 index 000000000..3a2883dc9 --- /dev/null +++ b/resources/lang/uk/components.php @@ -0,0 +1,34 @@ + 'Вибрати зображення', + 'image_all' => 'Всі', + 'image_all_title' => 'Переглянути всі зображення', + 'image_book_title' => 'Переглянути зображення, завантажені в цю книгу', + 'image_page_title' => 'Переглянути зображення, завантажені на цю сторінку', + 'image_search_hint' => 'Пошук по імені зображення', + 'image_uploaded' => 'Завантажено :uploadedDate', + 'image_load_more' => 'Завантажити ще', + 'image_image_name' => 'Назва зображення', + 'image_delete_used' => 'Це зображення використовується на наступних сторінках.', + 'image_delete_confirm' => 'Натисніть кнопку Видалити ще раз, щоб підтвердити, що хочете видалити це зображення.', + 'image_select_image' => 'Вибрати зображення', + 'image_dropzone' => 'Перетягніть зображення, або натисніть тут для завантаження', + 'images_deleted' => 'Зображень видалено', + 'image_preview' => 'Попередній перегляд зображення', + 'image_upload_success' => 'Зображення завантажено успішно', + 'image_update_success' => 'Деталі зображення успішно оновлені', + 'image_delete_success' => 'Зображення успішно видалено', + 'image_upload_remove' => 'Видалити', + + // Code Editor + 'code_editor' => 'Редагувати код', + 'code_language' => 'Мова коду', + 'code_content' => 'Вміст коду', + 'code_save' => 'Зберегти Код', +]; diff --git a/resources/lang/uk/entities.php b/resources/lang/uk/entities.php new file mode 100644 index 000000000..abf052fdd --- /dev/null +++ b/resources/lang/uk/entities.php @@ -0,0 +1,295 @@ + 'Недавно створено', + 'recently_created_pages' => 'Нещодавно створені сторінки', + 'recently_updated_pages' => 'Нещодавно оновлені сторінки', + 'recently_created_chapters' => 'Нещодавно створені розділи', + 'recently_created_books' => 'Нещодавно створені книги', + 'recently_update' => 'Недавно оновлено', + 'recently_viewed' => 'Недавно переглянуто', + 'recent_activity' => 'Остання активність', + 'create_now' => 'Створити зараз', + 'revisions' => 'Версія', + 'meta_revision' => 'Версія #:revisionCount', + 'meta_created' => 'Створено :timeLength', + 'meta_created_name' => ':user створив :timeLength', + 'meta_updated' => 'Оновлено :timeLength', + 'meta_updated_name' => ':user оновив :timeLength', + 'entity_select' => 'Вибір об\'єкта', + 'images' => 'Зображення', + 'my_recent_drafts' => 'Мої останні чернетки', + 'my_recently_viewed' => 'Мої недавні перегляди', + 'no_pages_viewed' => 'Ви не переглядали жодної сторінки', + 'no_pages_recently_created' => 'Не було створено жодної сторінки', + 'no_pages_recently_updated' => 'Немає недавно оновлених сторінок', + 'export' => 'Експорт', + 'export_html' => 'Вбудований веб-файл', + 'export_pdf' => 'PDF файл', + 'export_text' => 'Текстовий файл', + + // Permissions and restrictions + 'permissions' => 'Дозволи', + 'permissions_intro' => 'Після ввімкнення ці дозволи будуть мати пріоритет над усіма дозволеними ролями.', + 'permissions_enable' => 'Увімкнути спеціальні дозволи', + 'permissions_save' => 'Зберегти дозволи', + + // Search + 'search_results' => 'Результати пошуку', + 'search_total_results_found' => ':count результатів знайдено|:count всього результатів знайдено', + 'search_clear' => 'Очистити пошук', + 'search_no_pages' => 'Немає сторінок, які відповідають цьому пошуку', + 'search_for_term' => 'Шукати :term', + 'search_more' => 'Більше результатів', + 'search_filters' => 'Фільтри пошуку', + 'search_content_type' => 'Тип вмісту', + 'search_exact_matches' => 'Точна відповідність', + 'search_tags' => 'Пошукові теги', + 'search_options' => 'Параметри', + 'search_viewed_by_me' => 'Переглянуто мною', + 'search_not_viewed_by_me' => 'Не переглянуто мною', + 'search_permissions_set' => 'Налаштування дозволів', + 'search_created_by_me' => 'Створено мною', + 'search_updated_by_me' => 'Оновлено мною', + 'search_date_options' => 'Параметри дати', + 'search_updated_before' => 'Оновлено до', + 'search_updated_after' => 'Оновлено після', + 'search_created_before' => 'Створено до', + 'search_created_after' => 'Створено після', + 'search_set_date' => 'Встановити дату', + 'search_update' => 'Оновити пошук', + + // Shelves + 'shelf' => 'Полиця', + 'shelves' => 'Полиці', + 'shelves_long' => 'Книжкові полиці', + 'shelves_empty' => 'Жодних полиць не було створено', + 'shelves_create' => 'Створити нову полицю', + 'shelves_popular' => 'Популярні полиці', + 'shelves_new' => 'Нові полиці', + 'shelves_popular_empty' => 'Найпопулярніші полиці з\'являться тут.', + 'shelves_new_empty' => 'Тут будуть з\'являтися останні створені полиці.', + 'shelves_save' => 'Зберегти полицю', + 'shelves_books' => 'Книги на цій полиці', + 'shelves_add_books' => 'Додати книги до цієї полиці', + 'shelves_drag_books' => 'Перетягніть книги сюди, щоб додати їх до цієї полиці', + 'shelves_empty_contents' => 'Ця полиця не має призначених їй книг', + 'shelves_edit_and_assign' => 'Редагувати полицю для присвоєння книг', + 'shelves_edit_named' => 'Редагувати книжкову полицю :name', + 'shelves_edit' => 'Редагувати книжкову полицю', + 'shelves_delete' => 'Видалити книжкову полицю', + 'shelves_delete_named' => 'Видалити книжкову полицю :name', + 'shelves_delete_explain' => "Це дозволить видалити книжкову полицю з назвою ':name'. Книги не будуть видалені.", + 'shelves_delete_confirmation' => 'Ви впевнені, що хочете видалити цю книжкову полицю?', + 'shelves_permissions' => 'Дозволи на книжкову полицю', + 'shelves_permissions_updated' => 'Дозволи на книжкову полицю оновлено', + 'shelves_permissions_active' => 'Діючі дозволи на книжкову полицю', + 'shelves_copy_permissions_to_books' => 'Копіювати дозволи на книги', + 'shelves_copy_permissions' => 'Копіювати дозволи', + 'shelves_copy_permissions_explain' => 'Це застосовує поточні налаштування дозволів цієї книжкової полиці до всіх книг, що містяться всередині. Перш ніж активувати, переконайтесь що будь-які зміни дозволів цієї книжкової полиці були збережені.', + 'shelves_copy_permission_success' => 'Дозволи книжкової полиці скопійовано на :count книг', + + // Books + 'book' => 'Книга', + 'books' => 'Книги', + 'x_books' => ':count книга|:count книг', + 'books_empty' => 'Немає створених книг', + 'books_popular' => 'Популярні книги', + 'books_recent' => 'Останні книги', + 'books_new' => 'Нові книги', + 'books_popular_empty' => 'Найпопулярніші книги з\'являться тут.', + 'books_new_empty' => 'Найновіші книги з\'являться тут.', + 'books_create' => 'Створити нову книгу', + 'books_delete' => 'Видалити книгу', + 'books_delete_named' => 'Видалити книгу :bookName', + 'books_delete_explain' => 'Це призведе до видалення книги з назвою \':bookName\'. Всі сторінки та розділи будуть видалені.', + 'books_delete_confirmation' => 'Ви впевнені, що хочете видалити цю книгу?', + 'books_edit' => 'Редагувати книгу', + 'books_edit_named' => 'Редагувати книгу :bookName', + 'books_form_book_name' => 'Назва книги', + 'books_save' => 'Зберегти книгу', + 'books_permissions' => 'Дозволи на книгу', + 'books_permissions_updated' => 'Дозволи на книгу оновлено', + 'books_empty_contents' => 'Для цієї книги не створено жодної сторінки або розділів.', + 'books_empty_create_page' => 'Створити нову сторінку', + 'books_empty_or' => 'або', + 'books_empty_sort_current_book' => 'Сортувати поточну книгу', + 'books_empty_add_chapter' => 'Додати розділ', + 'books_permissions_active' => 'Діючі дозволи на книгу', + 'books_search_this' => 'Шукати цю книгу', + 'books_navigation' => 'Навігація по книзі', + 'books_sort' => 'Сортувати вміст книги', + 'books_sort_named' => 'Сортувати книгу :bookName', + 'books_sort_show_other' => 'Показати інші книги', + 'books_sort_save' => 'Зберегти нове замовлення', + + // Chapters + 'chapter' => 'Розділ', + 'chapters' => 'Розділи', + 'x_chapters' => ':count розділ|:count розділів', + 'chapters_popular' => 'Популярні розділи', + 'chapters_new' => 'Новий розділ', + 'chapters_create' => 'Створити новий розділ', + 'chapters_delete' => 'Видалити розділ', + '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_permissions' => 'Дозволи розділу', + 'chapters_empty' => 'У цьому розділі немає сторінок.', + 'chapters_permissions_active' => 'Діючі дозволи на розділ', + 'chapters_permissions_success' => 'Дозволи на розділ оновлено', + 'chapters_search_this' => 'Шукати в цьому розділі', + + // Pages + 'page' => 'Сторінка', + 'pages' => 'Сторінки', + '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_draft' => 'Видалити чернетку', + 'pages_delete_success' => 'Сторінка видалена', + 'pages_delete_draft_success' => 'Чернетка видалена', + 'pages_delete_confirm' => 'Ви впевнені, що хочете видалити цю сторінку?', + 'pages_delete_draft_confirm' => 'Ви впевнені, що хочете видалити цю чернетку?', + 'pages_editing_named' => 'Редагування сторінки :pageName', + 'pages_edit_toggle_header' => 'Переключити заголовок', + 'pages_edit_save_draft' => 'Зберегти чернетку', + 'pages_edit_draft' => 'Редагувати чернетку сторінки', + 'pages_editing_draft' => 'Редагування чернетки', + 'pages_editing_page' => 'Редагування сторінки', + '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_save' => 'Зберегти сторінку', + 'pages_title' => 'Заголовок сторінки', + 'pages_name' => 'Назва сторінки', + 'pages_md_editor' => 'Редактор', + 'pages_md_preview' => 'Попередній перегляд', + 'pages_md_insert_image' => 'Вставити зображення', + 'pages_md_insert_link' => 'Вставити посилання на об\'єкт', + 'pages_md_insert_drawing' => 'Вставити малюнок', + 'pages_not_in_chapter' => 'Сторінка не знаходиться в розділі', + 'pages_move' => 'Перемістити сторінку', + 'pages_move_success' => 'Сторінку переміщено до ":parentName"', + 'pages_copy' => 'Копіювати сторінку', + 'pages_copy_desination' => 'Ціль копіювання', + 'pages_copy_success' => 'Сторінка успішно скопійована', + 'pages_permissions' => 'Дозволи на сторінку', + 'pages_permissions_success' => 'Дозволи на сторінку оновлено', + 'pages_revision' => 'Версія', + 'pages_revisions' => 'Версія сторінки', + 'pages_revisions_named' => 'Версії сторінки для :pageName', + 'pages_revision_named' => 'Версія сторінки для :pageName', + 'pages_revisions_created_by' => 'Створена', + 'pages_revisions_date' => 'Дата версії', + 'pages_revisions_number' => '#', + 'pages_revisions_changelog' => 'Історія змін', + 'pages_revisions_changes' => 'Зміни', + 'pages_revisions_current' => 'Поточна версія', + 'pages_revisions_preview' => 'Попередній перегляд', + 'pages_revisions_restore' => 'Відновити', + 'pages_revisions_none' => 'Ця сторінка не має версій', + 'pages_copy_link' => 'Копіювати посилання', + 'pages_edit_content_link' => 'Редагувати вміст', + 'pages_permissions_active' => 'Активні дозволи сторінки', + 'pages_initial_revision' => 'Початкова публікація', + 'pages_initial_name' => 'Нова сторінка', + 'pages_editing_draft_notification' => 'Ви наразі редагуєте чернетку, що була збережена останньою :timeDiff.', + 'pages_draft_edited_notification' => 'З того часу ця сторінка була оновлена. Рекомендуємо відмовитися від цього проекту.', + 'pages_draft_edit_active' => [ + 'start_a' => ':count користувачі(в) почала редагувати цю сторінку', + 'start_b' => ':userName розпочав редагування цієї сторінки', + 'time_a' => 'з моменту останньої оновлення сторінки', + 'time_b' => 'за останні :minCount хвилин', + 'message' => ':start :time. Будьте обережні, щоб не перезаписати оновлення інших!', + ], + 'pages_draft_discarded' => 'Чернетка відхилена, редактор оновлено з поточним вмістом сторінки', + 'pages_specific' => 'Конкретна сторінка', + + // Editor Sidebar + 'page_tags' => 'Теги сторінки', + 'chapter_tags' => 'Теги розділів', + 'book_tags' => 'Теги книг', + 'shelf_tags' => 'Теги полиць', + 'tag' => 'Тег', + 'tags' => 'Теги', + 'tag_value' => 'Значення тегу (необов\'язково)', + 'tags_explain' => "Додайте кілька тегів, щоб краще класифікувати ваш вміст. \n Ви можете присвоїти значення тегу для більш глибокої організації.", + 'tags_add' => 'Додати ще один тег', + 'attachments' => 'Вкладення', + 'attachments_explain' => 'Завантажте файли, або додайте посилання, які відображатимуться на вашій сторінці. Їх буде видно на бічній панелі сторінки.', + 'attachments_explain_instant_save' => 'Зміни тут зберігаються миттєво.', + 'attachments_items' => 'Додані елементи', + 'attachments_upload' => 'Завантажити файл', + 'attachments_link' => 'Приєднати посилання', + 'attachments_set_link' => 'Встановити посилання', + 'attachments_delete_confirm' => 'Натисніть кнопку Видалити ще раз, щоб підтвердити, що ви хочете видалити це вкладення.', + 'attachments_dropzone' => 'Перетягніть файли, або натисніть тут щоб прикріпити файл', + 'attachments_no_files' => 'Файли не завантажені', + 'attachments_explain_link' => 'Ви можете приєднати посилання, якщо не бажаєте завантажувати файл. Це може бути посилання на іншу сторінку або посилання на файл у хмарі.', + 'attachments_link_name' => 'Назва посилання', + 'attachment_link' => 'Посилання на вкладення', + 'attachments_link_url' => 'Посилання на файл', + 'attachments_link_url_hint' => 'URL-адреса сайту або файлу', + 'attach' => 'Приєднати', + 'attachments_edit_file' => 'Редагувати файл', + 'attachments_edit_file_name' => 'Назва файлу', + 'attachments_edit_drop_upload' => 'Перетягніть файли, або натисніть тут щоб завантажити та перезаписати', + 'attachments_order_updated' => 'Порядок вкладень оновлено', + 'attachments_updated_success' => 'Деталі вкладень оновлено', + 'attachments_deleted' => 'Вкладення видалено', + 'attachments_file_uploaded' => 'Файл успішно завантажений', + 'attachments_file_updated' => 'Файл успішно оновлено', + 'attachments_link_attached' => 'Посилання успішно додано до сторінки', + + // 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 не створив жодної книги', + + // Comments + 'comment' => 'Коментар', + 'comments' => 'Коментарі', + 'comment_add' => 'Додати коментар', + 'comment_placeholder' => 'Залиште коментар тут', + 'comment_count' => '{0} Без коментарів|{1} 1 коментар|[2,*] :count коментарі(в)', + 'comment_save' => 'Зберегти коментар', + 'comment_saving' => 'Збереження коментаря...', + 'comment_deleting' => 'Видалення коментаря...', + 'comment_new' => 'Новий коментар', + 'comment_created' => 'прокоментував :createDiff', + 'comment_updated' => 'Оновлено :updateDiff користувачем :username', + 'comment_deleted_success' => 'Коментар видалено', + 'comment_created_success' => 'Коментар додано', + 'comment_updated_success' => 'Коментар оновлено', + 'comment_delete_confirm' => 'Ви впевнені, що хочете видалити цей коментар?', + 'comment_in_reply_to' => 'У відповідь на :commentId', + + // Revision + 'revision_delete_confirm' => 'Ви впевнені, що хочете видалити цю версію?', + 'revision_delete_success' => 'Версія видалена', + 'revision_cannot_delete_latest' => 'Неможливо видалити останню версію.' +]; \ No newline at end of file diff --git a/resources/lang/uk/errors.php b/resources/lang/uk/errors.php new file mode 100644 index 000000000..8b0244a5c --- /dev/null +++ b/resources/lang/uk/errors.php @@ -0,0 +1,83 @@ + 'Ви не маєте дозволу на доступ до цієї сторінки.', + 'permissionJson' => 'Ви не маєте дозволу виконувати заявлену дію.', + + // Auth + 'error_user_exists_different_creds' => 'Користувач з електронною адресою: електронна адреса вже існує, але з іншими обліковими даними.', + 'email_already_confirmed' => 'Електронна пошта вже підтверджена, спробуйте увійти.', + 'email_confirmation_invalid' => 'Цей токен підтвердження недійсний або вже був використаний, будь ласка, спробуйте знову зареєструватися.', + 'email_confirmation_expired' => 'Термін дії токена підтвердження минув, новий електронний лист підтвердження був відправлений.', + 'ldap_fail_anonymous' => 'LDAP-доступ невдалий, з використання анонімного зв\'язку', + 'ldap_fail_authed' => 'LDAP-доступ невдалий, використовуючи задані параметри dn та password', + 'ldap_extension_not_installed' => 'Розширення PHP LDAP не встановлено', + 'ldap_cannot_connect' => 'Неможливо підключитися до ldap-сервера, Помилка з\'єднання', + 'social_no_action_defined' => 'Жодних дій не визначено', + 'social_login_bad_response' => "Помилка, отримана під час входу з :socialAccount помилка : \n:error", + 'social_account_in_use' => 'Цей :socialAccount обліковий запис вже використовується, спробуйте ввійти з параметрами :socialAccount.', + 'social_account_email_in_use' => 'Електронна пошта :email вже використовується. Якщо у вас вже є обліковий запис, ви можете підключити свій обліковий запис :socialAccount з налаштувань вашого профілю.', + 'social_account_existing' => 'Цей :socialAccount вже додано до вашого профілю.', + 'social_account_already_used_existing' => 'Цей обліковий запис :socialAccount вже використовується іншим користувачем.', + 'social_account_not_used' => 'Цей обліковий запис :socialAccount account не пов\'язаний з жодним користувачем. Будь ласка, додайте його в налаштуваннях вашого профілю. ', + 'social_account_register_instructions' => 'Якщо у вас ще немає облікового запису, ви можете зареєструвати обліковий запис за допомогою параметра :socialAccount.', + 'social_driver_not_found' => 'Драйвер для СоціальноїМережі не знайдено', + 'social_driver_not_configured' => 'Ваші соціальні настройки :socialAccount не правильно налаштовані.', + + // System + 'path_not_writable' => 'Не вдається завантажити шлях до файлу :filePath. Переконайтеся, що він доступний для запису на сервер.', + 'cannot_get_image_from_url' => 'Неможливо отримати зображення з :url', + 'cannot_create_thumbs' => 'Сервер не може створювати ескізи. Будь ласка, перевірте, чи встановлено розширення GD PHP.', + 'server_upload_limit' => 'Сервер не дозволяє завантажувати файли такого розміру. Спробуйте менший розмір файлу.', + 'uploaded' => 'Сервер не дозволяє завантажувати файли такого розміру. Спробуйте менший розмір файлу.', + 'image_upload_error' => 'Виникла помилка під час завантаження зображення', + 'image_upload_type_error' => 'Тип завантаженого зображення недійсний', + + // Attachments + 'attachment_page_mismatch' => 'Невідповідність сторінки при оновленні вкладень', + 'attachment_not_found' => 'Вкладення не знайдено', + + // Pages + 'page_draft_autosave_fail' => 'Не вдалося зберегти чернетку. Перед збереженням цієї сторінки переконайтеся, що у вас є зв\'язок з сервером.', + 'page_custom_home_deletion' => 'Неможливо видалити сторінку, коли вона встановлена як домашня сторінка', + + // Entities + 'entity_not_found' => 'Об\'єкт не знайдено', + 'bookshelf_not_found' => 'Книжкова полиця не знайдена', + 'book_not_found' => 'Книга не знайдена', + 'page_not_found' => 'Сторінку не знайдено', + 'chapter_not_found' => 'Розділ не знайдено', + 'selected_book_not_found' => 'Вибрана книга не знайдена', + 'selected_book_chapter_not_found' => 'Вибрана книга або глава не знайдена', + 'guests_cannot_save_drafts' => 'Гості не можуть зберігати чернетки', + + // Users + 'users_cannot_delete_only_admin' => 'Ви не можете видалити єдиного адміністратора', + 'users_cannot_delete_guest' => 'Ви не можете видалити гостьового користувача', + + // Roles + 'role_cannot_be_edited' => 'Цю роль не можна редагувати', + 'role_system_cannot_be_deleted' => 'Ця роль є системною, і її не можна видалити', + 'role_registration_default_cannot_delete' => 'Цю роль не можна видалити, бо вона встановлена як роль реєстрації за умовчанням', + + // Comments + 'comment_list' => 'Під час отримання коментарів сталася помилка.', + 'cannot_add_comment_to_draft' => 'Ви не можете додати коментарі до проекту.', + 'comment_add' => 'Під час додавання/оновлення коментарів сталася помилка.', + 'comment_delete' => 'Під час видалення коментаря сталася помилка.', + 'empty_comment' => 'Неможливо додати порожній коментар.', + + // Error pages + '404_page_not_found' => 'Сторінку не знайдено', + 'sorry_page_not_found' => 'Вибачте, сторінку, яку ви шукали, не знайдено.', + 'return_home' => 'Повернутися на головну', + 'error_occurred' => 'Виникла помилка', + 'app_down' => ':appName зараз недоступний', + 'back_soon' => 'Він повернеться найближчим часом.', + +]; diff --git a/resources/lang/uk/pagination.php b/resources/lang/uk/pagination.php new file mode 100644 index 000000000..dd7f3533b --- /dev/null +++ b/resources/lang/uk/pagination.php @@ -0,0 +1,15 @@ + '« Попередня', + 'next' => 'Наступна »', + +]; diff --git a/resources/lang/uk/passwords.php b/resources/lang/uk/passwords.php new file mode 100644 index 000000000..c03631d4b --- /dev/null +++ b/resources/lang/uk/passwords.php @@ -0,0 +1,16 @@ + 'Паролі повинні містити принаймні шість символів і відповідати підтвердженню.', + 'user' => "Ми не можемо знайти користувача з цією адресою електронної пошти.", + 'token' => 'Цей токен для скидання пароля недійсний.', + 'sent' => 'Ми надіслали вам електронний лист із посиланням на скидання пароля!', + 'reset' => 'Ваш пароль був скинутий!', + +]; diff --git a/resources/lang/uk/settings.php b/resources/lang/uk/settings.php new file mode 100644 index 000000000..151b870e5 --- /dev/null +++ b/resources/lang/uk/settings.php @@ -0,0 +1,119 @@ + 'Налаштування', + 'settings_save' => 'Зберегти налаштування', + 'settings_save_success' => 'Налаштування збережено', + + // App Settings + 'app_settings' => 'Налаштування програми', + 'app_name' => 'Назва програми', + 'app_name_desc' => 'Ця назва відображається у заголовку та у всіх листах.', + 'app_name_header' => 'Показати назву програми в заголовку?', + 'app_public_viewing' => 'Дозволити публічний перегляд?', + 'app_secure_images' => 'Увімкунти вищі налаштування безпеки для завантаження зображень?', + 'app_secure_images_desc' => 'З міркувань продуктивності всі зображення є загальнодоступними. Цей параметр додає випадковий, важко передбачуваний рядок перед URL-адресами зображень. Переконайтеся, що індексація каталогів не активована, щоб запобігти легкому доступу.', + 'app_editor' => 'Редактор сторінок', + 'app_editor_desc' => 'Виберіть, який редактор буде використовуватися всіма користувачами для редагування сторінок.', + 'app_custom_html' => 'Користувацький вміст HTML-заголовку', + 'app_custom_html_desc' => 'Будь-який доданий тут вміст буде вставлено в нижню частину розділу кожної сторінки. Це зручно для перевизначення стилів, або додавання коду аналітики.', + 'app_logo' => 'Логотип програми', + 'app_logo_desc' => 'Це зображення має бути висотою 43px.
Великі зображення будуть зменшені.', + 'app_primary_color' => 'Основний колір програми', + 'app_primary_color_desc' => 'Колір потрібно вказати у hex-форматі.
Залиште порожнім, щоб використати стандартний колір.', + 'app_homepage' => 'Домашня сторінка програми', + 'app_homepage_desc' => 'Виберіть сторінку, яка відображатиметься на домашній сторінці замість перегляду за умовчанням. Права на сторінку не враховуються для вибраних сторінок.', + 'app_homepage_select' => 'Вибрати сторінку', + 'app_disable_comments' => 'Вимкнути коментарі', + 'app_disable_comments_desc' => 'Вимкнути коментарі на всіх сторінках програми. Існуючі коментарі не відображаються.', + + // Registration Settings + 'reg_settings' => 'Налаштування реєстрації', + 'reg_allow' => 'Дозволити реєстрацію?', + 'reg_default_role' => 'Роль користувача за умовчанням після реєстрації', + 'reg_confirm_email' => 'Потрібне підтвердження електронною поштою?', + 'reg_confirm_email_desc' => 'Якщо використовується обмеження домену, то підтвердження електронною поштою буде потрібно, а нижче значення буде проігноровано.', + 'reg_confirm_restrict_domain' => 'Обмежити реєстрацію до домену', + 'reg_confirm_restrict_domain_desc' => 'Введіть список розділених комами доменів електронної пошти, до яких ви хочете обмежити реєстрацію. Користувачам буде надіслано електронне повідомлення для підтвердження своєї адреси, перш ніж дозволяти взаємодіяти з додатком.
Зауважте, що користувачі зможуть змінювати свої електронні адреси після успішної реєстрації.', + 'reg_confirm_restrict_domain_placeholder' => 'Не встановлено обмежень', + + // Maintenance settings + 'maint' => 'Обслуговування', + 'maint_image_cleanup' => 'Очищення зображень', + 'maint_image_cleanup_desc' => "Сканує вміст сторінки та версій, щоб перевірити, які зображення та малюнки в даний час використовуються, а також які зображення зайві. Переконайтеся, що ви створили повну резервну копію бази даних та зображення, перш ніж запускати це.", + 'maint_image_cleanup_ignore_revisions' => 'Ігнорувати зображення в версіях', + 'maint_image_cleanup_run' => 'Запустити очищення', + 'maint_image_cleanup_warning' => ':count потенційно невикористаних зображень було знайдено. Ви впевнені, що хочете видалити ці зображення?', + 'maint_image_cleanup_success' => ':count потенційно невикористані зображення знайдено і видалено!', + 'maint_image_cleanup_nothing_found' => 'Не знайдено невикористовуваних зображень, нічого не видалено!', + + // Role Settings + 'roles' => 'Ролі', + 'role_user_roles' => 'Ролі користувача', + 'role_create' => 'Створити нову роль', + 'role_create_success' => 'Роль успішно створена', + 'role_delete' => 'Видалити роль', + 'role_delete_confirm' => 'Це призведе до видалення ролі з назвою \':roleName\'.', + 'role_delete_users_assigned' => 'Цій ролі належать :userCount користувачі(в). Якщо ви хочете перенести користувачів із цієї ролі, виберіть нову роль нижче.', + 'role_delete_no_migration' => "Не мігрувати користувачів", + 'role_delete_sure' => 'Ви впевнені, що хочете видалити цю роль?', + 'role_delete_success' => 'Роль успішно видалена', + 'role_edit' => 'Редагувати роль', + 'role_details' => 'Деталі ролі', + '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_settings' => 'Керування налаштуваннями програми', + 'role_asset' => 'Дозволи', + 'role_asset_desc' => 'Ці дозволи контролюють стандартні доступи всередині системи. Права на книги, розділи та сторінки перевизначать ці дозволи.', + 'role_asset_admins' => 'Адміністратори автоматично отримують доступ до всього вмісту, але ці параметри можуть відображати або приховувати параметри інтерфейсу користувача.', + 'role_all' => 'Все', + 'role_own' => 'Власне', + 'role_controlled_by_asset' => 'Контролюється за об\'єктом, до якого вони завантажуються', + 'role_save' => 'Зберегти роль', + 'role_update_success' => 'Роль успішно оновлена', + 'role_users' => 'Користувачі в цій ролі', + 'role_users_none' => 'Наразі жоден користувач не призначений для цієї ролі', + + // Users + 'users' => 'Користувачі', + 'user_profile' => 'Профіль користувача', + 'users_add_new' => 'Додати нового користувача', + 'users_search' => 'Пошук користувачів', + 'users_role' => 'Ролі користувача', + 'users_external_auth_id' => 'Зовнішній ID автентифікації', + 'users_password_warning' => 'Тільки якщо ви хочете змінити свій пароль, заповніть поля нижче:', + 'users_system_public' => 'Цей користувач представляє будь-яких гостьових користувачів, які відвідують ваш екземпляр. Його не можна використовувати для входу, але він призначається автоматично.', + 'users_delete' => 'Видалити користувача', + 'users_delete_named' => 'Видалити користувача :userName', + 'users_delete_warning' => 'Це повне видалення цього користувача з ім\'ям \':userName\' з системи.', + 'users_delete_confirm' => 'Ви впевнені, що хочете видалити цього користувача?', + 'users_delete_success' => 'Користувачі успішно видалені', + 'users_edit' => 'Редагувати користувача', + 'users_edit_profile' => 'Редагувати профіль', + 'users_edit_success' => 'Користувача успішно оновлено', + 'users_avatar' => 'Аватар користувача', + 'users_avatar_desc' => 'Це квадратне зображення має бути приблизно 256px.', + 'users_preferred_language' => 'Бажана мова', + 'users_social_accounts' => 'Соціальні акаунти', + 'users_social_accounts_info' => 'Тут ви можете підключити інші облікові записи для швидшого та легшого входу. Від\'єднання соціального облікового запису тут не дозволяється. Скасуйте доступ із налаштувань вашого профілю в пов\'язаній соціальній мережі.', + 'users_social_connect' => 'Підключити обліковий запис', + 'users_social_disconnect' => 'Від\'єднати обліковий запис', + 'users_social_connected' => 'Обліковий запис :socialAccount успішно додано до вашого профілю.', + 'users_social_disconnected' => 'Обліковий запис :socialAccount був успішно відключений від вашого профілю.', + +]; diff --git a/resources/lang/uk/validation.php b/resources/lang/uk/validation.php new file mode 100644 index 000000000..0e45bc0cc --- /dev/null +++ b/resources/lang/uk/validation.php @@ -0,0 +1,86 @@ + ':attribute повинен бути прийнятий.', + 'active_url' => ':attribute не є дійсною URL-адресою.', + 'after' => ':attribute повинно бути датою після :date.', + 'alpha' => ':attribute може містити лише літери.', + 'alpha_dash' => ':attribute може містити лише літери, цифри та дефіси.', + 'alpha_num' => ':attribute може містити лише літери та цифри.', + 'array' => ':attribute повинен бути масивом.', + 'before' => ':attribute повинен бути датою до :date.', + 'between' => [ + 'numeric' => ':attribute повинен бути між :min та :max.', + 'file' => ':attribute повинен бути між :min та :max кілобайт.', + 'string' => ':attribute повинен бути між :min та :max символів.', + 'array' => ':attribute повинен бути між :min та :max елементів.', + ], + 'boolean' => ':attribute поле має бути true або false.', + 'confirmed' => ':attribute підтвердження не збігається.', + 'date' => ':attribute не є дійсною датою.', + 'date_format' => ':attribute не відповідає формату :format.', + 'different' => ':attribute та :other повинні бути різними.', + 'digits' => ':attribute повинні бути :digits цифрами.', + 'digits_between' => ':attribute має бути між :min та :max цифр.', + 'email' => ':attribute повинна бути дійсною електронною адресою.', + 'filled' => ':attribute поле обов\'язкове.', + 'exists' => 'Вибраний :attribute недійсний.', + 'image' => ':attribute повинен бути зображенням.', + 'in' => 'Вибраний :attribute недійсний.', + 'integer' => ':attribute повинен бути цілим числом.', + 'ip' => ':attribute повинна бути дійсною IP-адресою.', + 'max' => [ + 'numeric' => ':attribute не може бути більшим за :max.', + 'file' => ':attribute не може бути більшим за :max кілобайт.', + 'string' => ':attribute не може бути більшим за :max символів.', + 'array' => ':attribute не може бути більше ніж :max елементів.', + ], + 'mimes' => ':attribute повинен бути файлом типу: :values.', + 'min' => [ + 'numeric' => ':attribute повинен бути принаймні :min.', + 'file' => ':attribute повинен бути принаймні :min кілобайт.', + 'string' => ':attribute повинен бути принаймні :min символів.', + 'array' => ':attribute повинен містити принаймні :min елементів.', + ], + 'not_in' => 'Вибраний :attribute недійсний.', + 'numeric' => ':attribute повинен бути числом.', + 'regex' => ':attribute формат недійсний.', + 'required' => ':attribute поле обов\'язкове.', + 'required_if' => ':attribute поле бов\'язкове, коли :other з значенням :value.', + 'required_with' => ':attribute поле бов\'язкове, коли :values встановлено.', + 'required_with_all' => ':attribute поле бов\'язкове, коли :values встановлені.', + 'required_without' => ':attribute поле бов\'язкове, коли :values не встановлені.', + 'required_without_all' => ':attribute поле бов\'язкове, коли жодне з :values не встановлене.', + 'same' => ':attribute та :other мають збігатись.', + 'size' => [ + 'numeric' => ':attribute має бути :size.', + 'file' => ':attribute має бути :size кілобайт.', + 'string' => ':attribute має бути :size символів.', + 'array' => ':attribute має містити :size елементів.', + ], + 'string' => ':attribute повинен бути рядком.', + 'timezone' => ':attribute повинен бути дійсною зоною.', + 'unique' => ':attribute вже є.', + 'url' => ':attribute формат недійсний.', + + // Custom validation lines + 'custom' => [ + 'password-confirm' => [ + 'required_with' => 'Необхідне підтвердження пароля', + ], + ], + + // Custom validation attributes + 'attributes' => [], +]; From d476e30df0515b07e2b61451fd73c02806baaa89 Mon Sep 17 00:00:00 2001 From: Mantikor Date: Tue, 18 Dec 2018 10:03:10 +0200 Subject: [PATCH 27/43] Added 'uk' locale From 26ec1cc3dcd4ebc205ab7746cc4a92603f37ea97 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 20 Dec 2018 20:04:09 +0000 Subject: [PATCH 28/43] Added proper escaping to LDAP filter operations To cover #1163 --- app/Auth/Access/Ldap.php | 23 +++++++++++++++++++++++ app/Auth/Access/LdapService.php | 19 ++++++++++++------- tests/Auth/LdapTest.php | 27 ++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/app/Auth/Access/Ldap.php b/app/Auth/Access/Ldap.php index 468c37626..843a2f204 100644 --- a/app/Auth/Access/Ldap.php +++ b/app/Auth/Access/Ldap.php @@ -92,4 +92,27 @@ class Ldap { return ldap_bind($ldapConnection, $bindRdn, $bindPassword); } + + /** + * Explode a LDAP dn string into an array of components. + * @param string $dn + * @param int $withAttrib + * @return array + */ + public function explodeDn(string $dn, int $withAttrib) + { + return ldap_explode_dn($dn, $withAttrib); + } + + /** + * Escape a string for use in an LDAP filter. + * @param string $value + * @param string $ignore + * @param int $flags + * @return string + */ + public function escape(string $value, string $ignore = "", int $flags = 0) + { + return ldap_escape($value, $ignore, $flags); + } } diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index d3a177f8e..b49ecf129 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -107,6 +107,7 @@ class LdapService if ($ldapUser === null) { return false; } + if ($ldapUser['uid'] !== $user->external_auth_id) { return false; } @@ -195,7 +196,7 @@ class LdapService $newAttrs = []; foreach ($attrs as $key => $attrText) { $newKey = '${' . $key . '}'; - $newAttrs[$newKey] = $attrText; + $newAttrs[$newKey] = $this->ldap->escape($attrText); } return strtr($filterString, $newAttrs); } @@ -265,7 +266,8 @@ class LdapService $baseDn = $this->config['base_dn']; $groupsAttr = strtolower($this->config['group_attribute']); - $groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, 'CN='.$groupName, [$groupsAttr]); + $groupFilter = 'CN=' . $this->ldap->escape($groupName); + $groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $groupFilter, [$groupsAttr]); if ($groups['count'] === 0) { return []; } @@ -277,23 +279,26 @@ class LdapService /** * Filter out LDAP CN and DN language in a ldap search return * Gets the base CN (common name) of the string - * @param string $ldapSearchReturn + * @param array $userGroupSearchResponse * @return array */ - protected function groupFilter($ldapSearchReturn) + protected function groupFilter(array $userGroupSearchResponse) { $groupsAttr = strtolower($this->config['group_attribute']); $ldapGroups = []; $count = 0; - if (isset($ldapSearchReturn[$groupsAttr]['count'])) { - $count = (int) $ldapSearchReturn[$groupsAttr]['count']; + + if (isset($userGroupSearchResponse[$groupsAttr]['count'])) { + $count = (int) $userGroupSearchResponse[$groupsAttr]['count']; } + for ($i=0; $i<$count; $i++) { - $dnComponents = ldap_explode_dn($ldapSearchReturn[$groupsAttr][$i], 1); + $dnComponents = $this->ldap->explodeDn($userGroupSearchResponse[$groupsAttr][$i], 1); if (!in_array($dnComponents[0], $ldapGroups)) { $ldapGroups[] = $dnComponents[0]; } } + return $ldapGroups; } diff --git a/tests/Auth/LdapTest.php b/tests/Auth/LdapTest.php index 25d8a5906..16ba11358 100644 --- a/tests/Auth/LdapTest.php +++ b/tests/Auth/LdapTest.php @@ -31,6 +31,20 @@ class LdapTest extends BrowserKitTest $this->mockUser = factory(User::class)->make(); } + protected function mockEscapes($times = 1) + { + $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function($val) { + return ldap_escape($val); + }); + } + + protected function mockExplodes($times = 1) + { + $this->mockLdap->shouldReceive('explodeDn')->times($times)->andReturnUsing(function($dn, $withAttrib) { + return ldap_explode_dn($dn, $withAttrib); + }); + } + public function test_login() { $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId); @@ -44,6 +58,7 @@ class LdapTest extends BrowserKitTest 'dn' => ['dc=test' . config('services.ldap.base_dn')] ]]); $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true); + $this->mockEscapes(4); $this->visit('/login') ->see('Username') @@ -73,6 +88,7 @@ class LdapTest extends BrowserKitTest 'mail' => [$this->mockUser->email] ]]); $this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true); + $this->mockEscapes(2); $this->visit('/login') ->see('Username') @@ -97,6 +113,7 @@ class LdapTest extends BrowserKitTest 'dn' => ['dc=test' . config('services.ldap.base_dn')] ]]); $this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true, true, false); + $this->mockEscapes(2); $this->visit('/login') ->see('Username') @@ -146,7 +163,7 @@ class LdapTest extends BrowserKitTest ->dontSee('External Authentication'); } - public function test_login_maps_roles_and_retains_existsing_roles() + public function test_login_maps_roles_and_retains_existing_roles() { $roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'display_name' => 'LdapTester']); $roleToReceive2 = factory(Role::class)->create(['name' => 'ldaptester-second', 'display_name' => 'LdapTester Second']); @@ -176,6 +193,8 @@ class LdapTest extends BrowserKitTest ] ]]); $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true); + $this->mockEscapes(5); + $this->mockExplodes(6); $this->visit('/login') ->see('Username') @@ -227,6 +246,8 @@ class LdapTest extends BrowserKitTest ] ]]); $this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true); + $this->mockEscapes(4); + $this->mockExplodes(2); $this->visit('/login') ->see('Username') @@ -279,6 +300,8 @@ class LdapTest extends BrowserKitTest ] ]]); $this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true); + $this->mockEscapes(4); + $this->mockExplodes(2); $this->visit('/login') ->see('Username') @@ -328,6 +351,8 @@ class LdapTest extends BrowserKitTest ] ]]); $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true); + $this->mockEscapes(5); + $this->mockExplodes(6); $this->visit('/login') ->see('Username') From f62843c8614e64ef39d15fedbf32688ecbcc1b95 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 22 Dec 2018 15:45:13 +0000 Subject: [PATCH 29/43] Updated DZ upload timeout var name and error handling For #1133 & #876 Concerns BookStackApp/website#31 --- .../assets/js/vues/components/dropzone.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/resources/assets/js/vues/components/dropzone.js b/resources/assets/js/vues/components/dropzone.js index 6cfcecb40..31a84a267 100644 --- a/resources/assets/js/vues/components/dropzone.js +++ b/resources/assets/js/vues/components/dropzone.js @@ -10,26 +10,27 @@ const props = ['placeholder', 'uploadUrl', 'uploadedTo']; // TODO - Remove jQuery usage function mounted() { - let container = this.$el; - let _this = this; + const container = this.$el; + const _this = this; this._dz = new DropZone(container, { addRemoveLinks: true, dictRemoveFile: trans('components.image_upload_remove'), - timeout: +window.dropZoneTimeout || 60000, + timeout: Number(window.uploadTimeout) || 60000, url: function() { return _this.uploadUrl; }, init: function () { - let dz = this; + const dz = this; dz.on('sending', function (file, xhr, data) { - let token = window.document.querySelector('meta[name=token]').getAttribute('content'); + const token = window.document.querySelector('meta[name=token]').getAttribute('content'); data.append('_token', token); - let uploadedTo = typeof _this.uploadedTo === 'undefined' ? 0 : _this.uploadedTo; + const uploadedTo = typeof _this.uploadedTo === 'undefined' ? 0 : _this.uploadedTo; data.append('uploaded_to', uploadedTo); xhr.ontimeout = function (e) { - _this.$events.emit('error', trans('errors.file_upload_timeout')); + dz.emit('complete', file); + dz.emit('error', file, trans('errors.file_upload_timeout')); } }); @@ -47,8 +48,11 @@ function mounted() { $(file.previewElement).find('[data-dz-errormessage]').text(message); } - if (xhr && xhr.status === 413) setMessage(trans('errors.server_upload_limit')); - else if (errorMessage.file) setMessage(errorMessage.file); + if (xhr && xhr.status === 413) { + setMessage(trans('errors.server_upload_limit')) + } else if (errorMessage.file) { + setMessage(errorMessage.file); + } }); } From f4ea5f1f55902acde92211698638ed2f4a906229 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 22 Dec 2018 16:35:04 +0000 Subject: [PATCH 30/43] Updated page exports to use absolute time format For #1065 --- resources/views/pages/export.blade.php | 2 +- .../partials/entity-export-meta.blade.php | 33 +++++++++++++++++++ tests/Entity/ExportTest.php | 11 +++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 resources/views/partials/entity-export-meta.blade.php diff --git a/resources/views/pages/export.blade.php b/resources/views/pages/export.blade.php index ca708f8e0..0f76a8861 100644 --- a/resources/views/pages/export.blade.php +++ b/resources/views/pages/export.blade.php @@ -23,7 +23,7 @@
- @include('partials.entity-meta', ['entity' => $page]) + @include('partials.entity-export-meta', ['entity' => $page])
diff --git a/resources/views/partials/entity-export-meta.blade.php b/resources/views/partials/entity-export-meta.blade.php new file mode 100644 index 000000000..fa1394ed4 --- /dev/null +++ b/resources/views/partials/entity-export-meta.blade.php @@ -0,0 +1,33 @@ +
+ @if($entity->isA('revision')) + @icon('history'){{ trans('entities.pages_revision') }} + {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }} +
+ @endif + + @if ($entity->isA('page')) + @if (userCan('page-update', $entity)) @endif + @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
+ @if (userCan('page-update', $entity))
@endif + @endif + + @if ($entity->createdBy) + @icon('star'){!! trans('entities.meta_created_name', [ + 'timeLength' => ''.$entity->created_at->toDayDateTimeString() . '', + 'user' => "".htmlentities($entity->createdBy->name). "" + ]) !!} + @else + @icon('star'){{ trans('entities.meta_created', ['timeLength' => $entity->created_at->toDayDateTimeString()]) }} + @endif + +
+ + @if ($entity->updatedBy) + @icon('edit'){!! trans('entities.meta_updated_name', [ + 'timeLength' => '' . $entity->updated_at->toDayDateTimeString() .'', + 'user' => "".htmlentities($entity->updatedBy->name). "" + ]) !!} + @elseif (!$entity->isA('revision')) + @icon('edit'){{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->toDayDateTimeString()]) }} + @endif +
\ No newline at end of file diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index 51090650c..fdcd83366 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -123,4 +123,15 @@ class ExportTest extends TestCase $resp->assertSee($customHeadContent); } + public function test_page_html_export_use_absolute_dates() + { + $page = Page::first(); + + $resp = $this->asEditor()->get($page->getUrl('/export/html')); + $resp->assertSee($page->created_at->toDayDateTimeString()); + $resp->assertDontSee($page->created_at->diffForHumans()); + $resp->assertSee($page->updated_at->toDayDateTimeString()); + $resp->assertDontSee($page->updated_at->diffForHumans()); + } + } \ No newline at end of file From 18b10153e5729e5d4aeab2d6ab86db8d6c57ca8e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 22 Dec 2018 16:49:09 +0000 Subject: [PATCH 31/43] Updated composer with bumped php version and extra extensions --- composer.json | 7 +- composer.lock | 217 +++++++++++++++++++++++++++++++------------------- 2 files changed, 140 insertions(+), 84 deletions(-) diff --git a/composer.json b/composer.json index a9b38bff4..2850eb235 100644 --- a/composer.json +++ b/composer.json @@ -5,10 +5,13 @@ "license": "MIT", "type": "project", "require": { - "php": ">=7.0.0", + "php": ">=7.0.5", "ext-json": "*", "ext-tidy": "*", "ext-dom": "*", + "ext-xml": "*", + "ext-mbstring": "*", + "ext-gd": "*", "laravel/framework": "~5.5.44", "fideloper/proxy": "~3.3", "intervention/image": "^2.4", @@ -83,7 +86,7 @@ "optimize-autoloader": true, "preferred-install": "dist", "platform": { - "php": "7.0" + "php": "7.0.5" } } } diff --git a/composer.lock b/composer.lock index 8b7657da2..c524c0999 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "4a15ede09baa575d7accd7a4f66067fd", + "content-hash": "06219a5c2419ca23ec2924eb31f4ed16", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.70.3", + "version": "3.82.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "8278052a097a4ebe2b798fab7e2e3c907bc01a47" + "reference": "a0353c24b18d2ba0f5bb7ca8a478b4ce0b8153f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8278052a097a4ebe2b798fab7e2e3c907bc01a47", - "reference": "8278052a097a4ebe2b798fab7e2e3c907bc01a47", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a0353c24b18d2ba0f5bb7ca8a478b4ce0b8153f7", + "reference": "a0353c24b18d2ba0f5bb7ca8a478b4ce0b8153f7", "shasum": "" }, "require": { @@ -87,7 +87,7 @@ "s3", "sdk" ], - "time": "2018-11-02T20:04:36+00:00" + "time": "2018-12-21T22:21:50+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -729,30 +729,34 @@ }, { "name": "dompdf/dompdf", - "version": "v0.8.2", + "version": "v0.8.3", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6" + "reference": "75f13c700009be21a1965dc2c5b68a8708c22ba2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5113accd9ae5d466077cce5208dcf3fb871bf8f6", - "reference": "5113accd9ae5d466077cce5208dcf3fb871bf8f6", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/75f13c700009be21a1965dc2c5b68a8708c22ba2", + "reference": "75f13c700009be21a1965dc2c5b68a8708c22ba2", "shasum": "" }, "require": { "ext-dom": "*", - "ext-gd": "*", "ext-mbstring": "*", "phenx/php-font-lib": "0.5.*", "phenx/php-svg-lib": "0.3.*", "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "4.8.*", + "phpunit/phpunit": "^4.8|^5.5|^6.5", "squizlabs/php_codesniffer": "2.*" }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance" + }, "type": "library", "extra": { "branch-alias": { @@ -787,20 +791,20 @@ ], "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", "homepage": "https://github.com/dompdf/dompdf", - "time": "2017-11-26T14:49:08+00:00" + "time": "2018-12-14T02:40:31+00:00" }, { "name": "egulias/email-validator", - "version": "2.1.6", + "version": "2.1.7", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "0578b32b30b22de3e8664f797cf846fc9246f786" + "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0578b32b30b22de3e8664f797cf846fc9246f786", - "reference": "0578b32b30b22de3e8664f797cf846fc9246f786", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/709f21f92707308cdf8f9bcfa1af4cb26586521e", + "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e", "shasum": "" }, "require": { @@ -844,7 +848,7 @@ "validation", "validator" ], - "time": "2018-09-25T20:47:26+00:00" + "time": "2018-12-04T22:38:24+00:00" }, { "name": "erusev/parsedown", @@ -1116,32 +1120,33 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "9f83dded91781a01c63574e387eaa769be769115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", + "reference": "9f83dded91781a01c63574e387eaa769be769115", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -1171,13 +1176,14 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2018-12-04T20:46:45+00:00" }, { "name": "intervention/image", @@ -1514,16 +1520,16 @@ }, { "name": "league/flysystem", - "version": "1.0.48", + "version": "1.0.49", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa" + "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a6ded5b2f6055e2db97b4b859fdfca2b952b78aa", - "reference": "a6ded5b2f6055e2db97b4b859fdfca2b952b78aa", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a63cc83d8a931b271be45148fa39ba7156782ffd", + "reference": "a63cc83d8a931b271be45148fa39ba7156782ffd", "shasum": "" }, "require": { @@ -1594,7 +1600,7 @@ "sftp", "storage" ], - "time": "2018-10-15T13:53:10+00:00" + "time": "2018-11-23T23:41:29+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -1708,16 +1714,16 @@ }, { "name": "monolog/monolog", - "version": "1.23.0", + "version": "1.24.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", "shasum": "" }, "require": { @@ -1782,7 +1788,7 @@ "logging", "psr-3" ], - "time": "2017-06-19T01:22:40+00:00" + "time": "2018-11-05T09:00:11+00:00" }, { "name": "mtdowling/cron-expression", @@ -1885,16 +1891,16 @@ }, { "name": "nesbot/carbon", - "version": "1.34.0", + "version": "1.36.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33" + "reference": "63da8cdf89d7a5efe43aabc794365f6e7b7b8983" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", - "reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/63da8cdf89d7a5efe43aabc794365f6e7b7b8983", + "reference": "63da8cdf89d7a5efe43aabc794365f6e7b7b8983", "shasum": "" }, "require": { @@ -1902,9 +1908,12 @@ "symfony/translation": "~2.6 || ~3.0 || ~4.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~2", "phpunit/phpunit": "^4.8.35 || ^5.7" }, + "suggest": { + "friendsofphp/php-cs-fixer": "Needed for the `composer phpcs` command. Allow to automatically fix code style.", + "phpstan/phpstan": "Needed for the `composer phpstan` command. Allow to detect potential errors." + }, "type": "library", "extra": { "laravel": { @@ -1936,7 +1945,7 @@ "datetime", "time" ], - "time": "2018-09-20T19:36:25+00:00" + "time": "2018-11-22T18:23:02+00:00" }, { "name": "paragonie/random_compat", @@ -2211,16 +2220,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -2254,7 +2263,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" }, { "name": "psr/simple-cache", @@ -2304,6 +2313,46 @@ ], "time": "2017-10-23T01:57:42+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0", + "satooshi/php-coveralls": ">=1.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2016-02-11T07:05:27+00:00" + }, { "name": "ramsey/uuid", "version": "3.8.0", @@ -3667,16 +3716,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "5b68f3972083a7eeec0d6f161962fcda71a127c0" + "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/5b68f3972083a7eeec0d6f161962fcda71a127c0", - "reference": "5b68f3972083a7eeec0d6f161962fcda71a127c0", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/9d5caf43c5f3a3aea2178942f281054805872e7c", + "reference": "9d5caf43c5f3a3aea2178942f281054805872e7c", "shasum": "" }, "require": { @@ -3731,24 +3780,24 @@ "profiler", "webprofiler" ], - "time": "2018-08-22T11:06:19+00:00" + "time": "2018-11-09T08:37:55+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.5.1", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "7db1843473e1562d8e0490b51db847d3a1415140" + "reference": "3d7f1240896a075aa23b13f82dfcbe165dadeef2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/7db1843473e1562d8e0490b51db847d3a1415140", - "reference": "7db1843473e1562d8e0490b51db847d3a1415140", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/3d7f1240896a075aa23b13f82dfcbe165dadeef2", + "reference": "3d7f1240896a075aa23b13f82dfcbe165dadeef2", "shasum": "" }, "require": { - "barryvdh/reflection-docblock": "^2.0.4", + "barryvdh/reflection-docblock": "^2.0.6", "composer/composer": "^1.6", "illuminate/console": "^5.5,<5.8", "illuminate/filesystem": "^5.5,<5.8", @@ -3805,20 +3854,20 @@ "phpstorm", "sublime" ], - "time": "2018-09-06T18:41:09+00:00" + "time": "2018-12-19T12:12:05+00:00" }, { "name": "barryvdh/reflection-docblock", - "version": "v2.0.4", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/barryvdh/ReflectionDocBlock.git", - "reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c" + "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/3dcbd98b5d9384a5357266efba8fd29884458e5c", - "reference": "3dcbd98b5d9384a5357266efba8fd29884458e5c", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/6b69015d83d3daf9004a71a89f26e27d27ef6a16", + "reference": "6b69015d83d3daf9004a71a89f26e27d27ef6a16", "shasum": "" }, "require": { @@ -3854,7 +3903,7 @@ "email": "mike.vanriel@naenius.com" } ], - "time": "2016-06-13T19:28:20+00:00" + "time": "2018-12-13T10:34:14+00:00" }, { "name": "composer/ca-bundle", @@ -3914,16 +3963,16 @@ }, { "name": "composer/composer", - "version": "1.7.3", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "e965b9aaa8854c3067f1ed2ae45f436572d73eb7" + "reference": "d8aef3af866b28786ce9b8647e52c42496436669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/e965b9aaa8854c3067f1ed2ae45f436572d73eb7", - "reference": "e965b9aaa8854c3067f1ed2ae45f436572d73eb7", + "url": "https://api.github.com/repos/composer/composer/zipball/d8aef3af866b28786ce9b8647e52c42496436669", + "reference": "d8aef3af866b28786ce9b8647e52c42496436669", "shasum": "" }, "require": { @@ -3959,7 +4008,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -3990,7 +4039,7 @@ "dependency", "package" ], - "time": "2018-11-01T09:05:06+00:00" + "time": "2018-12-03T09:31:16+00:00" }, { "name": "composer/semver", @@ -4117,16 +4166,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" + "reference": "dc523135366eb68f22268d069ea7749486458562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", - "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", + "reference": "dc523135366eb68f22268d069ea7749486458562", "shasum": "" }, "require": { @@ -4157,7 +4206,7 @@ "Xdebug", "performance" ], - "time": "2018-08-31T19:07:57+00:00" + "time": "2018-11-29T10:59:02+00:00" }, { "name": "doctrine/instantiator", @@ -6020,16 +6069,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + "reference": "379deb987e26c7cd103a7b387aea178baec96e48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/379deb987e26c7cd103a7b387aea178baec96e48", + "reference": "379deb987e26c7cd103a7b387aea178baec96e48", "shasum": "" }, "require": { @@ -6067,7 +6116,7 @@ "phpcs", "standards" ], - "time": "2018-09-23T23:08:17+00:00" + "time": "2018-12-19T23:57:18+00:00" }, { "name": "symfony/dom-crawler", @@ -6271,12 +6320,16 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.0.0", + "php": ">=7.0.5", + "ext-json": "*", "ext-tidy": "*", - "ext-dom": "*" + "ext-dom": "*", + "ext-xml": "*", + "ext-mbstring": "*", + "ext-gd": "*" }, "platform-dev": [], "platform-overrides": { - "php": "7.0" + "php": "7.0.5" } } From b56fc21aafa08f8fd7a9909e20d7b9032b3080cb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 22 Dec 2018 19:29:19 +0000 Subject: [PATCH 32/43] Abstracted user avatar fetching away from gravatar Still uses gravatar as a default. Updated URL placeholders to follow LDAP format. Potential breaking config change: `GRAVATAR=false` replaced by `AVATAR_URL=false` Builds upon #1111 --- .env.example | 12 +++---- app/Auth/UserRepo.php | 17 ++++------ app/Console/Commands/CreateAdmin.php | 2 +- app/Http/Controllers/UserController.php | 2 +- app/Uploads/ImageService.php | 45 ++++++++++++++++++------- config/services.php | 9 +++-- 6 files changed, 55 insertions(+), 32 deletions(-) diff --git a/.env.example b/.env.example index c839d254b..1005ad208 100644 --- a/.env.example +++ b/.env.example @@ -60,13 +60,13 @@ GITLAB_BASE_URI=false DISCORD_APP_ID=false DISCORD_APP_SECRET=false -# External services such as Gravatar and Draw.IO + +# Disable default services such as Gravatar and Draw.IO DISABLE_EXTERNAL_SERVICES=false -# Default GRAVATAR_URL set to Gravatar service -GRAVATAR_URL=false -# To use a different service to get user's avatar like libravatar -# Possible placeholders: %{hash} %{size} %{email} -#GRAVATAR_URL=https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon +# Use custom avatar service, Sets fetch URL +# Possible placeholders: ${hash} ${size} ${email} +# If set, Avatars will be fetched regardless of DISABLE_EXTERNAL_SERVICES option. +# AVATAR_URL=https://seccdn.libravatar.org/avatar/${hash}?s=${size}&d=identicon # LDAP Settings LDAP_SERVER=false diff --git a/app/Auth/UserRepo.php b/app/Auth/UserRepo.php index abff7c641..d436ab8eb 100644 --- a/app/Auth/UserRepo.php +++ b/app/Auth/UserRepo.php @@ -85,9 +85,7 @@ class UserRepo { $user = $this->create($data, $verifyEmail); $this->attachDefaultRole($user); - - // Get avatar from gravatar and save - $this->downloadGravatarToUserAvatar($user); + $this->downloadAndAssignUserAvatar($user); return $user; } @@ -238,25 +236,24 @@ class UserRepo } /** - * Get a gravatar image for a user and set it as their avatar. - * Does not run if gravatar disabled in config. + * Get an avatar image for a user and set it as their avatar. + * Returns early if avatars disabled or not set in config. * @param User $user * @return bool */ - public function downloadGravatarToUserAvatar(User $user) + public function downloadAndAssignUserAvatar(User $user) { - // Get avatar from gravatar and save - if (!config('services.gravatar')) { + if (!Images::avatarFetchEnabled()) { return false; } try { - $avatar = Images::saveUserGravatar($user, config('services.gravatar_url')); + $avatar = Images::saveUserAvatar($user); $user->avatar()->associate($avatar); $user->save(); return true; } catch (Exception $e) { - \Log::error('Failed to save user gravatar image'); + \Log::error('Failed to save user avatar image'); return false; } } diff --git a/app/Console/Commands/CreateAdmin.php b/app/Console/Commands/CreateAdmin.php index 6bfc54469..90c1ddb1c 100644 --- a/app/Console/Commands/CreateAdmin.php +++ b/app/Console/Commands/CreateAdmin.php @@ -76,7 +76,7 @@ class CreateAdmin extends Command $user = $this->userRepo->create(['email' => $email, 'name' => $name, 'password' => $password]); $this->userRepo->attachSystemRole($user, 'admin'); - $this->userRepo->downloadGravatarToUserAvatar($user); + $this->userRepo->downloadAndAssignUserAvatar($user); $user->email_confirmed = true; $user->save(); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 5f5c8365e..24f8b67cb 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -92,7 +92,7 @@ class UserController extends Controller $user->roles()->sync($roles); } - $this->userRepo->downloadGravatarToUserAvatar($user); + $this->userRepo->downloadAndAssignUserAvatar($user); return redirect('/settings/users'); } diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index b65a476f4..d5f4068ef 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -279,30 +279,51 @@ class ImageService extends UploadService } /** - * Save a gravatar image and set a the profile image for a user. + * Save an avatar image from an external service. * @param \BookStack\Auth\User $user - * @param null|string $gravatarUrl * @param int $size - * @return mixed + * @return Image * @throws Exception */ - public function saveUserGravatar(User $user, $gravatarUrl, $size = 500) + public function saveUserAvatar(User $user, $size = 500) { - if (!is_string($gravatarUrl) || empty($gravatarUrl)) { - $gravatarUrl = 'https://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'; - } + $avatarUrl = $this->getAvatarUrl(); $email = strtolower(trim($user->email)); - $gravatarUrl = str_replace('%{hash}', md5($email), $gravatarUrl); - $gravatarUrl = str_replace('%{size}', $size, $gravatarUrl); - $gravatarUrl = str_replace('%{email}', urlencode($email), $gravatarUrl); - $imageName = str_replace(' ', '-', $user->name . '-gravatar.png'); - $image = $this->saveNewFromUrl($gravatarUrl, 'user', $imageName); + + $replacements = [ + '${hash}' => md5($email), + '${size}' => $size, + '${email}' => urlencode($email), + ]; + + $userAvatarUrl = strtr($avatarUrl, $replacements); + $imageName = str_replace(' ', '-', $user->name . '-avatar.png'); + $image = $this->saveNewFromUrl($userAvatarUrl, 'user', $imageName); $image->created_by = $user->id; $image->updated_by = $user->id; $image->save(); + return $image; } + /** + * Check if fetching external avatars is enabled. + * @return bool + */ + public function avatarFetchEnabled() + { + $fetchUrl = $this->getAvatarUrl(); + return is_string($fetchUrl) && strpos($fetchUrl, 'http') === 0; + } + + /** + * Get the URL to fetch avatars from. + * @return string|mixed + */ + protected function getAvatarUrl() + { + return trim(config('services.avatar_url')); + } /** * Delete gallery and drawings that are not within HTML content of pages or page revisions. diff --git a/config/services.php b/config/services.php index 310ea295f..7b9cf4e74 100644 --- a/config/services.php +++ b/config/services.php @@ -16,11 +16,16 @@ return [ // Single option to disable non-auth external services such as Gravatar and Draw.io 'disable_services' => env('DISABLE_EXTERNAL_SERVICES', false), - 'gravatar' => env('GRAVATAR', !env('DISABLE_EXTERNAL_SERVICES', false)), + + // Draw.io integration active 'drawio' => env('DRAWIO', !env('DISABLE_EXTERNAL_SERVICES', false)), - 'gravatar_url' => env('GRAVATAR_URL', false), + // URL for fetching avatars + 'avatar_url' => env('AVATAR_URL', + env('DISABLE_EXTERNAL_SERVICES', false) ? false : 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon' + ), + // Callback URL for social authentication methods 'callback_url' => env('APP_URL', false), 'mailgun' => [ From 68017e25534b13db5b784c634c0d6f2d3981affb Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 23 Dec 2018 15:34:38 +0000 Subject: [PATCH 33/43] Added testing for avatar fetching systems & config Abstracts imageservice http interaction. Closes #1193 --- app/Exceptions/HttpFetchException.php | 5 ++ app/Providers/CustomFacadeProvider.php | 4 +- app/Uploads/HttpFetcher.php | 34 +++++++++++ app/Uploads/ImageService.php | 28 +++++---- composer.json | 1 + config/services.php | 4 +- phpunit.xml | 1 + tests/{ => Uploads}/AttachmentTest.php | 0 tests/Uploads/AvatarTest.php | 84 ++++++++++++++++++++++++++ tests/{ => Uploads}/ImageTest.php | 58 +----------------- tests/Uploads/UsesImages.php | 69 +++++++++++++++++++++ 11 files changed, 217 insertions(+), 71 deletions(-) create mode 100644 app/Exceptions/HttpFetchException.php create mode 100644 app/Uploads/HttpFetcher.php rename tests/{ => Uploads}/AttachmentTest.php (100%) create mode 100644 tests/Uploads/AvatarTest.php rename tests/{ => Uploads}/ImageTest.php (85%) create mode 100644 tests/Uploads/UsesImages.php diff --git a/app/Exceptions/HttpFetchException.php b/app/Exceptions/HttpFetchException.php new file mode 100644 index 000000000..48e30e1e6 --- /dev/null +++ b/app/Exceptions/HttpFetchException.php @@ -0,0 +1,5 @@ +app->make(Image::class), $this->app->make(ImageManager::class), $this->app->make(Factory::class), - $this->app->make(Repository::class) + $this->app->make(Repository::class), + $this->app->make(HttpFetcher::class) ); }); } diff --git a/app/Uploads/HttpFetcher.php b/app/Uploads/HttpFetcher.php new file mode 100644 index 000000000..3ebe17eee --- /dev/null +++ b/app/Uploads/HttpFetcher.php @@ -0,0 +1,34 @@ + $uri, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CONNECTTIMEOUT => 5 + ]); + + $data = curl_exec($ch); + $err = curl_error($ch); + curl_close($ch); + + if ($err) { + throw new HttpFetchException($err); + } + + return $data; + } + +} \ No newline at end of file diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index d5f4068ef..1dd8b713d 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -1,6 +1,7 @@ image = $image; $this->imageTool = $imageTool; $this->cache = $cache; + $this->http = $http; parent::__construct($fileSystem); } @@ -95,8 +99,9 @@ class ImageService extends UploadService private function saveNewFromUrl($url, $type, $imageName = false) { $imageName = $imageName ? $imageName : basename($url); - $imageData = file_get_contents($url); - if ($imageData === false) { + try { + $imageData = $this->http->fetch($url); + } catch (HttpFetchException $exception) { throw new \Exception(trans('errors.cannot_get_image_from_url', ['url' => $url])); } return $this->saveNew($imageName, $imageData, $type); @@ -322,7 +327,13 @@ class ImageService extends UploadService */ protected function getAvatarUrl() { - return trim(config('services.avatar_url')); + $url = trim(config('services.avatar_url')); + + if (empty($url) && !config('services.disable_services')) { + $url = 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon'; + } + + return $url; } /** @@ -392,14 +403,7 @@ class ImageService extends UploadService } } else { try { - $ch = curl_init(); - curl_setopt_array($ch, [CURLOPT_URL => $uri, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]); - $imageData = curl_exec($ch); - $err = curl_error($ch); - curl_close($ch); - if ($err) { - throw new \Exception("Image fetch failed, Received error: " . $err); - } + $imageData = $this->http->fetch($uri); } catch (\Exception $e) { } } diff --git a/composer.json b/composer.json index 2850eb235..48b977e23 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "ext-xml": "*", "ext-mbstring": "*", "ext-gd": "*", + "ext-curl": "*", "laravel/framework": "~5.5.44", "fideloper/proxy": "~3.3", "intervention/image": "^2.4", diff --git a/config/services.php b/config/services.php index 7b9cf4e74..a07fab23f 100644 --- a/config/services.php +++ b/config/services.php @@ -21,9 +21,7 @@ return [ 'drawio' => env('DRAWIO', !env('DISABLE_EXTERNAL_SERVICES', false)), // URL for fetching avatars - 'avatar_url' => env('AVATAR_URL', - env('DISABLE_EXTERNAL_SERVICES', false) ? false : 'https://www.gravatar.com/avatar/${hash}?s=${size}&d=identicon' - ), + 'avatar_url' => env('AVATAR_URL', ''), // Callback URL for social authentication methods 'callback_url' => env('APP_URL', false), diff --git a/phpunit.xml b/phpunit.xml index 3d18d9bbf..804afcf5d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -31,6 +31,7 @@ + diff --git a/tests/AttachmentTest.php b/tests/Uploads/AttachmentTest.php similarity index 100% rename from tests/AttachmentTest.php rename to tests/Uploads/AttachmentTest.php diff --git a/tests/Uploads/AvatarTest.php b/tests/Uploads/AvatarTest.php new file mode 100644 index 000000000..ecf7037a9 --- /dev/null +++ b/tests/Uploads/AvatarTest.php @@ -0,0 +1,84 @@ +asAdmin()->post('/settings/users/create', [ + 'name' => $user->name, + 'email' => $user->email, + 'password' => 'testing', + 'password-confirm' => 'testing', + ]); + return User::where('email', '=', $user->email)->first(); + } + + protected function assertImageFetchFrom(string $url) + { + $http = \Mockery::mock(HttpFetcher::class); + $this->app->instance(HttpFetcher::class, $http); + + $http->shouldReceive('fetch') + ->once()->with($url) + ->andReturn($this->getTestImageContent()); + } + + protected function deleteUserImage(User $user) + { + $this->deleteImage($user->avatar->path); + } + + public function test_gravatar_fetched_on_user_create() + { + config()->set([ + 'services.disable_services' => false, + ]); + $user = factory(User::class)->make(); + $this->assertImageFetchFrom('https://www.gravatar.com/avatar/'.md5(strtolower($user->email)).'?s=500&d=identicon'); + + $user = $this->createUserRequest($user); + $this->assertDatabaseHas('images', [ + 'type' => 'user', + 'created_by' => $user->id + ]); + $this->deleteUserImage($user); + } + + + public function test_custom_url_used_if_set() + { + config()->set([ + 'services.avatar_url' => 'https://example.com/${email}/${hash}/${size}', + ]); + + $user = factory(User::class)->make(); + $url = 'https://example.com/'. urlencode(strtolower($user->email)) .'/'. md5(strtolower($user->email)).'/500'; + $this->assertImageFetchFrom($url); + + $user = $this->createUserRequest($user); + $this->deleteUserImage($user); + } + + public function test_avatar_not_fetched_if_no_custom_url_and_services_disabled() + { + config()->set([ + 'services.disable_services' => true, + ]); + + $user = factory(User::class)->make(); + + $http = \Mockery::mock(HttpFetcher::class); + $this->app->instance(HttpFetcher::class, $http); + $http->shouldNotReceive('fetch'); + + $this->createUserRequest($user); + } + +} diff --git a/tests/ImageTest.php b/tests/Uploads/ImageTest.php similarity index 85% rename from tests/ImageTest.php rename to tests/Uploads/ImageTest.php index 38bac2cca..6dafa7f5a 100644 --- a/tests/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -1,67 +1,15 @@ -getTestImageFilePath(), $fileName, 'image/png', 5238); - } - - /** - * Get the path for a test image. - * @param $type - * @param $fileName - * @return string - */ - protected function getTestImagePath($type, $fileName) - { - return '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/' . $fileName; - } - - /** - * Uploads an image with the given name. - * @param $name - * @param int $uploadedTo - * @return \Illuminate\Foundation\Testing\TestResponse - */ - protected function uploadImage($name, $uploadedTo = 0) - { - $file = $this->getTestImage($name); - return $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []); - } - - /** - * Delete an uploaded image. - * @param $relPath - */ - protected function deleteImage($relPath) - { - $path = public_path($relPath); - if (file_exists($path)) { - unlink($path); - } - } + use UsesImages; public function test_image_upload() { diff --git a/tests/Uploads/UsesImages.php b/tests/Uploads/UsesImages.php new file mode 100644 index 000000000..16cb7c2b9 --- /dev/null +++ b/tests/Uploads/UsesImages.php @@ -0,0 +1,69 @@ +getTestImageFilePath(), $fileName, 'image/png', 5238); + } + + /** + * Get the raw file data for the test image. + * @return false|string + */ + protected function getTestImageContent() + { + return file_get_contents($this->getTestImageFilePath()); + } + + /** + * Get the path for a test image. + * @param $type + * @param $fileName + * @return string + */ + protected function getTestImagePath($type, $fileName) + { + return '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/' . $fileName; + } + + /** + * Uploads an image with the given name. + * @param $name + * @param int $uploadedTo + * @return \Illuminate\Foundation\Testing\TestResponse + */ + protected function uploadImage($name, $uploadedTo = 0) + { + $file = $this->getTestImage($name); + return $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []); + } + + /** + * Delete an uploaded image. + * @param $relPath + */ + protected function deleteImage($relPath) + { + $path = public_path($relPath); + if (file_exists($path)) { + unlink($path); + } + } + +} \ No newline at end of file From 456afdcd4c9a724ad50b4bddbe4812ccccd9606a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 23 Dec 2018 16:26:39 +0000 Subject: [PATCH 34/43] Updated configuration files Added a notice to the top of each to explain they should not be normally modified. Standardised comment format used for each item. Better aligned some files with laravel 5.5 options. --- config/app.php | 207 ++++++++++-------------------------- config/auth.php | 92 +++++----------- config/broadcasting.php | 38 +++---- config/cache.php | 46 +++----- config/compile.php | 35 ------ config/database.php | 82 ++++---------- config/dompdf.php | 18 ++-- config/filesystems.php | 67 +++--------- config/mail.php | 123 ++++----------------- config/queue.php | 48 +++------ config/services.php | 24 ++--- config/session.php | 194 ++++++++------------------------- config/setting-defaults.php | 21 ++-- config/snappy.php | 8 ++ config/view.php | 54 ++++------ 15 files changed, 287 insertions(+), 770 deletions(-) delete mode 100644 config/compile.php diff --git a/config/app.php b/config/app.php index 3a704e83c..e2885d196 100755 --- a/config/app.php +++ b/config/app.php @@ -1,175 +1,84 @@ env('APP_ENV', 'production'), - /** - * Set the default view type for various lists. Can be overridden by user preferences. - * This will be used for public viewers and users that have not set a preference. - */ + // Enter the application in debug mode. + // Shows much more verbose error messages. Has potential to show + // 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') ], - /** - * 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. - */ + // 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. 'revision_limit' => env('REVISION_LIMIT', 50), - /** - * Allow