From 55456a57d62ff6500e48de73ba43e0e2bcbcc056 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 18 Feb 2023 13:51:18 +0000 Subject: [PATCH 01/27] Added tests for not-yet-built role API endpoints --- app/Auth/Permissions/RolePermission.php | 2 + .../Controllers/Api/UserApiController.php | 4 +- tests/Api/RolesApiTest.php | 227 ++++++++++++++++++ tests/Api/UsersApiTest.php | 6 +- 4 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 tests/Api/RolesApiTest.php diff --git a/app/Auth/Permissions/RolePermission.php b/app/Auth/Permissions/RolePermission.php index f34de917c..467c43ce2 100644 --- a/app/Auth/Permissions/RolePermission.php +++ b/app/Auth/Permissions/RolePermission.php @@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int $id + * @property string $name + * @property string $display_name */ class RolePermission extends Model { diff --git a/app/Http/Controllers/Api/UserApiController.php b/app/Http/Controllers/Api/UserApiController.php index 64e9d732d..da6ca4321 100644 --- a/app/Http/Controllers/Api/UserApiController.php +++ b/app/Http/Controllers/Api/UserApiController.php @@ -13,9 +13,9 @@ use Illuminate\Validation\Rules\Unique; class UserApiController extends ApiController { - protected $userRepo; + protected UserRepo $userRepo; - protected $fieldsToExpose = [ + protected array $fieldsToExpose = [ 'email', 'created_at', 'updated_at', 'last_activity_at', 'external_auth_id', ]; diff --git a/tests/Api/RolesApiTest.php b/tests/Api/RolesApiTest.php new file mode 100644 index 000000000..38026a40a --- /dev/null +++ b/tests/Api/RolesApiTest.php @@ -0,0 +1,227 @@ +<?php + +namespace Tests\Api; + +use BookStack\Actions\ActivityType; +use BookStack\Auth\Role; +use BookStack\Auth\User; +use Tests\TestCase; + +class RolesApiTest extends TestCase +{ + use TestsApi; + + protected string $baseEndpoint = '/api/roles'; + + protected array $endpointMap = [ + ['get', '/api/roles'], + ['post', '/api/roles'], + ['get', '/api/roles/1'], + ['put', '/api/roles/1'], + ['delete', '/api/roles/1'], + ]; + + public function test_user_roles_manage_permission_needed_for_all_endpoints() + { + $this->actingAsApiEditor(); + foreach ($this->endpointMap as [$method, $uri]) { + $resp = $this->json($method, $uri); + $resp->assertStatus(403); + $resp->assertJson($this->permissionErrorResponse()); + } + } + + public function test_index_endpoint_returns_expected_role_and_count() + { + $this->actingAsApiAdmin(); + /** @var Role $firstRole */ + $firstRole = Role::query()->orderBy('id', 'asc')->first(); + + $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id'); + $resp->assertJson(['data' => [ + [ + 'id' => $firstRole->id, + 'display_name' => $firstRole->display_name, + 'description' => $firstRole->description, + 'mfa_enforced' => $firstRole->mfa_enforced, + 'permissions_count' => $firstRole->permissions()->count(), + 'users_count' => $firstRole->users()->count(), + 'created_at' => $firstRole->created_at->toJSON(), + 'updated_at' => $firstRole->updated_at->toJSON(), + ], + ]]); + + $resp->assertJson(['total' => Role::query()->count()]); + } + + public function test_create_endpoint() + { + $this->actingAsApiAdmin(); + /** @var Role $role */ + $role = Role::query()->first(); + + $resp = $this->postJson($this->baseEndpoint, [ + 'display_name' => 'My awesome role', + 'description' => 'My great role description', + 'mfa_enforced' => true, + 'permissions' => [ + 'content-export', + 'users-manage', + 'page-view-own', + 'page-view-all', + ] + ]); + + $resp->assertStatus(200); + $resp->assertJson([ + 'display_name' => 'My awesome role', + 'description' => 'My great role description', + 'mfa_enforced' => true, + 'permissions' => [ + 'content-export', + 'users-manage', + 'page-view-own', + 'page-view-all', + ] + ]); + + $this->assertDatabaseHas('roles', [ + 'display_name' => 'My awesome role', + 'description' => 'My great role description', + 'mfa_enforced' => true, + ]); + + /** @var Role $role */ + $role = Role::query()->where('display_name', '=', 'My awesome role')->first(); + $this->assertActivityExists(ActivityType::ROLE_CREATE, null, $role->logDescriptor()); + $this->assertEquals(4, $role->permissions()->count()); + } + + public function test_create_name_and_description_validation() + { + $this->actingAsApiAdmin(); + /** @var User $existingUser */ + $existingUser = User::query()->first(); + + $resp = $this->postJson($this->baseEndpoint, [ + 'description' => 'My new role', + ]); + $resp->assertStatus(422); + $resp->assertJson($this->validationResponse(['display_name' => ['The display_name field is required.']])); + + $resp = $this->postJson($this->baseEndpoint, [ + 'name' => 'My great role with a too long desc', + 'description' => str_repeat('My great desc', 20), + ]); + $resp->assertStatus(422); + $resp->assertJson($this->validationResponse(['description' => ['The description may not be greater than 180 characters.']])); + } + + public function test_read_endpoint() + { + $this->actingAsApiAdmin(); + $role = $this->users->editor()->roles()->first(); + $resp = $this->getJson($this->baseEndpoint . "/{$role->id}"); + + $resp->assertStatus(200); + $resp->assertJson([ + 'display_name' => $role->display_name, + 'description' => $role->description, + 'mfa_enforced' => $role->mfa_enforced, + 'permissions' => $role->permissions()->pluck('name')->toArray(), + 'users' => $role->users()->get()->map(function (User $user) { + return [ + 'id' => $user->id, + 'name' => $user->name, + 'slug' => $user->slug, + ]; + })->toArray(), + ]); + } + + public function test_update_endpoint() + { + $this->actingAsApiAdmin(); + $role = $this->users->editor()->roles()->first(); + $resp = $this->putJson($this->baseEndpoint . "/{$role->id}", [ + 'display_name' => 'My updated role', + 'description' => 'My great role description', + 'mfa_enforced' => true, + 'permissions' => [ + 'content-export', + 'users-manage', + 'page-view-own', + 'page-view-all', + ] + ]); + + $resp->assertStatus(200); + $resp->assertJson([ + 'id' => $role->id, + 'display_name' => 'My updated role', + 'description' => 'My great role description', + 'mfa_enforced' => true, + 'permissions' => [ + 'content-export', + 'users-manage', + 'page-view-own', + 'page-view-all', + ] + ]); + + $role->refresh(); + $this->assertEquals(4, $role->permissions()->count()); + } + + public function test_update_endpoint_does_not_remove_info_if_not_provided() + { + $this->actingAsApiAdmin(); + $role = $this->users->editor()->roles()->first(); + $resp = $this->putJson($this->baseEndpoint . "/{$role->id}", []); + $permissionCount = $role->permissions()->count(); + + $resp->assertStatus(200); + $this->assertDatabaseHas('users', [ + 'id' => $role->id, + 'display_name' => $role->display_name, + 'description' => $role->description, + ]); + + $role->refresh(); + $this->assertEquals($permissionCount, $role->permissions()->count()); + } + + public function test_delete_endpoint() + { + $this->actingAsApiAdmin(); + $role = $this->users->editor()->roles()->first(); + + $resp = $this->deleteJson($this->baseEndpoint . "/{$role->id}"); + + $resp->assertStatus(204); + $this->assertActivityExists(ActivityType::ROLE_DELETE, null, $role->logDescriptor()); + } + + public function test_delete_endpoint_fails_deleting_system_role() + { + $this->actingAsApiAdmin(); + $adminRole = Role::getSystemRole('admin'); + + $resp = $this->deleteJson($this->baseEndpoint . "/{$adminRole->id}"); + + $resp->assertStatus(500); + $resp->assertJson($this->errorResponse('This role is a system role and cannot be deleted', 500)); + } + + public function test_delete_endpoint_fails_deleting_default_registration_role() + { + $this->actingAsApiAdmin(); + $role = $this->users->attachNewRole($this->users->editor()); + $this->setSettings(['registration-role' => $role->id]); + + $resp = $this->deleteJson($this->baseEndpoint . "/{$role->id}"); + + $resp->assertStatus(500); + $resp->assertJson($this->errorResponse('This role cannot be deleted while set as the default registration role', 500)); + } +} diff --git a/tests/Api/UsersApiTest.php b/tests/Api/UsersApiTest.php index c89f9e6e3..fadd2610c 100644 --- a/tests/Api/UsersApiTest.php +++ b/tests/Api/UsersApiTest.php @@ -15,9 +15,9 @@ class UsersApiTest extends TestCase { use TestsApi; - protected $baseEndpoint = '/api/users'; + protected string $baseEndpoint = '/api/users'; - protected $endpointMap = [ + protected array $endpointMap = [ ['get', '/api/users'], ['post', '/api/users'], ['get', '/api/users/1'], @@ -47,7 +47,7 @@ class UsersApiTest extends TestCase } } - public function test_index_endpoint_returns_expected_shelf() + public function test_index_endpoint_returns_expected_user() { $this->actingAsApiAdmin(); /** @var User $firstUser */ From 723f108bd9b7f53ab90ff113d1a3ecb6958db801 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 18 Feb 2023 18:36:34 +0000 Subject: [PATCH 02/27] Aded roles API controller methods Altered & updated permissions repo, and existing connected RoleController to suit. Also extracts in-app success notifications to auto activity system. Tweaked tests where required. --- app/Auth/Permissions/PermissionsRepo.php | 68 ++++----- app/Auth/Role.php | 2 +- app/Auth/User.php | 2 +- app/Http/Controllers/Api/ApiController.php | 11 +- .../Controllers/Api/RoleApiController.php | 133 ++++++++++++++++++ app/Http/Controllers/RoleController.php | 32 +++-- lang/en/activities.php | 5 + lang/en/settings.php | 3 - routes/api.php | 7 + tests/Api/RolesApiTest.php | 43 +++--- tests/Helpers/UserRoleProvider.php | 2 +- tests/Permissions/RolesTest.php | 2 +- 12 files changed, 238 insertions(+), 72 deletions(-) create mode 100644 app/Http/Controllers/Api/RoleApiController.php diff --git a/app/Auth/Permissions/PermissionsRepo.php b/app/Auth/Permissions/PermissionsRepo.php index 6dcef7256..75354e79b 100644 --- a/app/Auth/Permissions/PermissionsRepo.php +++ b/app/Auth/Permissions/PermissionsRepo.php @@ -12,11 +12,8 @@ use Illuminate\Database\Eloquent\Collection; class PermissionsRepo { protected JointPermissionBuilder $permissionBuilder; - protected $systemRoles = ['admin', 'public']; + protected array $systemRoles = ['admin', 'public']; - /** - * PermissionsRepo constructor. - */ public function __construct(JointPermissionBuilder $permissionBuilder) { $this->permissionBuilder = $permissionBuilder; @@ -41,7 +38,7 @@ class PermissionsRepo /** * Get a role via its ID. */ - public function getRoleById($id): Role + public function getRoleById(int $id): Role { return Role::query()->findOrFail($id); } @@ -52,10 +49,10 @@ class PermissionsRepo public function saveNewRole(array $roleData): Role { $role = new Role($roleData); - $role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true'; + $role->mfa_enforced = boolval($roleData['mfa_enforced'] ?? false); $role->save(); - $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; + $permissions = $roleData['permissions'] ?? []; $this->assignRolePermissions($role, $permissions); $this->permissionBuilder->rebuildForRole($role); @@ -66,42 +63,45 @@ class PermissionsRepo /** * Updates an existing role. - * Ensure Admin role always have core permissions. + * Ensures Admin system role always have core permissions. */ - public function updateRole($roleId, array $roleData) + public function updateRole($roleId, array $roleData): Role { $role = $this->getRoleById($roleId); - $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; + if (isset($roleData['permissions'])) { + $this->assignRolePermissions($role, $roleData['permissions']); + } + + $role->fill($roleData); + $role->save(); + $this->permissionBuilder->rebuildForRole($role); + + Activity::add(ActivityType::ROLE_UPDATE, $role); + + return $role; + } + + /** + * Assign a list of permission names to the given role. + */ + protected function assignRolePermissions(Role $role, array $permissionNameArray = []): void + { + $permissions = []; + $permissionNameArray = array_values($permissionNameArray); + + // Ensure the admin system role retains vital system permissions if ($role->system_name === 'admin') { - $permissions = array_merge($permissions, [ + $permissionNameArray = array_unique(array_merge($permissionNameArray, [ 'users-manage', 'user-roles-manage', 'restrictions-manage-all', 'restrictions-manage-own', 'settings-manage', - ]); + ])); } - $this->assignRolePermissions($role, $permissions); - - $role->fill($roleData); - $role->mfa_enforced = ($roleData['mfa_enforced'] ?? 'false') === 'true'; - $role->save(); - $this->permissionBuilder->rebuildForRole($role); - - Activity::add(ActivityType::ROLE_UPDATE, $role); - } - - /** - * Assign a list of permission names to a role. - */ - protected function assignRolePermissions(Role $role, array $permissionNameArray = []) - { - $permissions = []; - $permissionNameArray = array_values($permissionNameArray); - - if ($permissionNameArray) { + if (!empty($permissionNameArray)) { $permissions = RolePermission::query() ->whereIn('name', $permissionNameArray) ->pluck('id') @@ -114,13 +114,13 @@ class PermissionsRepo /** * Delete a role from the system. * Check it's not an admin role or set as default before deleting. - * If an migration Role ID is specified the users assign to the current role + * If a migration Role ID is specified the users assign to the current role * will be added to the role of the specified id. * * @throws PermissionsException * @throws Exception */ - public function deleteRole($roleId, $migrateRoleId) + public function deleteRole(int $roleId, int $migrateRoleId = 0): void { $role = $this->getRoleById($roleId); @@ -131,7 +131,7 @@ class PermissionsRepo throw new PermissionsException(trans('errors.role_registration_default_cannot_delete')); } - if ($migrateRoleId) { + if ($migrateRoleId !== 0) { $newRole = Role::query()->find($migrateRoleId); if ($newRole) { $users = $role->users()->pluck('id')->toArray(); diff --git a/app/Auth/Role.php b/app/Auth/Role.php index b293d1af2..0f43dd18c 100644 --- a/app/Auth/Role.php +++ b/app/Auth/Role.php @@ -27,7 +27,7 @@ class Role extends Model implements Loggable { use HasFactory; - protected $fillable = ['display_name', 'description', 'external_auth_id']; + protected $fillable = ['display_name', 'description', 'external_auth_id', 'mfa_enforced']; protected $hidden = ['pivot']; diff --git a/app/Auth/User.php b/app/Auth/User.php index cf9f20e52..90bb3d68e 100644 --- a/app/Auth/User.php +++ b/app/Auth/User.php @@ -72,7 +72,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ protected $hidden = [ 'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email', - 'created_at', 'updated_at', 'image_id', 'roles', 'avatar', 'user_id', + 'created_at', 'updated_at', 'image_id', 'roles', 'avatar', 'user_id', 'pivot', ]; /** diff --git a/app/Http/Controllers/Api/ApiController.php b/app/Http/Controllers/Api/ApiController.php index 9652654be..5c448e49f 100644 --- a/app/Http/Controllers/Api/ApiController.php +++ b/app/Http/Controllers/Api/ApiController.php @@ -32,10 +32,15 @@ abstract class ApiController extends Controller */ public function getValidationRules(): array { - if (method_exists($this, 'rules')) { - return $this->rules(); - } + return $this->rules(); + } + /** + * Get the validation rules for the actions in this controller. + * Defaults to a $rules property but can be a rules() method. + */ + protected function rules(): array + { return $this->rules; } } diff --git a/app/Http/Controllers/Api/RoleApiController.php b/app/Http/Controllers/Api/RoleApiController.php new file mode 100644 index 000000000..119279822 --- /dev/null +++ b/app/Http/Controllers/Api/RoleApiController.php @@ -0,0 +1,133 @@ +<?php + +namespace BookStack\Http\Controllers\Api; + +use BookStack\Auth\Permissions\PermissionsRepo; +use BookStack\Auth\Role; +use BookStack\Exceptions\UserUpdateException; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; + +class RoleApiController extends ApiController +{ + protected PermissionsRepo $permissionsRepo; + + protected array $fieldsToExpose = [ + 'display_name', 'description', 'mfa_enforced', 'external_auth_id', 'created_at', 'updated_at', + ]; + + protected $rules = [ + 'create' => [ + 'display_name' => ['required', 'min:3', 'max:180'], + 'description' => ['max:180'], + 'mfa_enforced' => ['boolean'], + 'external_auth_id' => ['string'], + 'permissions' => ['array'], + 'permissions.*' => ['string'], + ], + 'update' => [ + 'display_name' => ['min:3', 'max:180'], + 'description' => ['max:180'], + 'mfa_enforced' => ['boolean'], + 'external_auth_id' => ['string'], + 'permissions' => ['array'], + 'permissions.*' => ['string'], + ] + ]; + + public function __construct(PermissionsRepo $permissionsRepo) + { + $this->permissionsRepo = $permissionsRepo; + + // Checks for all endpoints in this controller + $this->middleware(function ($request, $next) { + $this->checkPermission('user-roles-manage'); + + return $next($request); + }); + } + + /** + * Get a listing of roles in the system. + * Requires permission to manage roles. + */ + public function list() + { + $roles = Role::query()->select(['*']) + ->withCount(['users', 'permissions']); + + return $this->apiListingResponse($roles, [ + ...$this->fieldsToExpose, + 'permissions_count', + 'users_count', + ]); + } + + /** + * Create a new role in the system. + * Requires permission to manage roles. + */ + public function create(Request $request) + { + $data = $this->validate($request, $this->rules()['create']); + + $role = null; + DB::transaction(function () use ($data, &$role) { + $role = $this->permissionsRepo->saveNewRole($data); + }); + + $this->singleFormatter($role); + + return response()->json($role); + } + + /** + * View the details of a single user. + * Requires permission to manage roles. + */ + public function read(string $id) + { + $user = $this->permissionsRepo->getRoleById($id); + $this->singleFormatter($user); + + return response()->json($user); + } + + /** + * Update an existing role in the system. + * Requires permission to manage roles. + */ + public function update(Request $request, string $id) + { + $data = $this->validate($request, $this->rules()['update']); + $role = $this->permissionsRepo->updateRole($id, $data); + + $this->singleFormatter($role); + + return response()->json($role); + } + + /** + * Delete a user from the system. + * Can optionally accept a user id via `migrate_ownership_id` to indicate + * who should be the new owner of their related content. + * Requires permission to manage roles. + */ + public function delete(string $id) + { + $this->permissionsRepo->deleteRole(intval($id)); + + return response('', 204); + } + + /** + * Format the given role model for single-result display. + */ + protected function singleFormatter(Role $role) + { + $role->load('users:id,name,slug'); + $role->unsetRelation('permissions'); + $role->setAttribute('permissions', $role->permissions()->pluck('name')); + $role->makeVisible(['users', 'permissions']); + } +} diff --git a/app/Http/Controllers/RoleController.php b/app/Http/Controllers/RoleController.php index a9be19e0c..2bf0a7b19 100644 --- a/app/Http/Controllers/RoleController.php +++ b/app/Http/Controllers/RoleController.php @@ -74,13 +74,17 @@ class RoleController extends Controller public function store(Request $request) { $this->checkPermission('user-roles-manage'); - $this->validate($request, [ + $data = $this->validate($request, [ 'display_name' => ['required', 'min:3', 'max:180'], 'description' => ['max:180'], + 'external_auth_id' => ['string'], + 'permissions' => ['array'], + 'mfa_enforced' => ['string'], ]); - $this->permissionsRepo->saveNewRole($request->all()); - $this->showSuccessNotification(trans('settings.role_create_success')); + $data['permissions'] = array_keys($data['permissions'] ?? []); + $data['mfa_enforced'] = ($data['mfa_enforced'] ?? 'false') === 'true'; + $this->permissionsRepo->saveNewRole($data); return redirect('/settings/roles'); } @@ -100,19 +104,27 @@ class RoleController extends Controller /** * Updates a user role. - * - * @throws ValidationException */ public function update(Request $request, string $id) { $this->checkPermission('user-roles-manage'); - $this->validate($request, [ + $data = $this->validate($request, [ 'display_name' => ['required', 'min:3', 'max:180'], 'description' => ['max:180'], + 'external_auth_id' => ['string'], + 'permissions' => ['array'], + 'mfa_enforced' => ['string'], ]); - $this->permissionsRepo->updateRole($id, $request->all()); - $this->showSuccessNotification(trans('settings.role_update_success')); + if (isset($data['permissions'])) { + $data['permissions'] = array_keys($data['permissions']); + } + + if (isset($data['mfa_enforced'])) { + $data['mfa_enforced'] = $data['mfa_enforced'] === 'true'; + } + + $this->permissionsRepo->updateRole($id, $data); return redirect('/settings/roles'); } @@ -145,15 +157,13 @@ class RoleController extends Controller $this->checkPermission('user-roles-manage'); try { - $this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id')); + $this->permissionsRepo->deleteRole($id, $request->get('migrate_role_id', 0)); } catch (PermissionsException $e) { $this->showErrorNotification($e->getMessage()); return redirect()->back(); } - $this->showSuccessNotification(trans('settings.role_delete_success')); - return redirect('/settings/roles'); } } diff --git a/lang/en/activities.php b/lang/en/activities.php index f348bff1f..e89b8eab2 100644 --- a/lang/en/activities.php +++ b/lang/en/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'commented on', 'permissions_update' => 'updated permissions', diff --git a/lang/en/settings.php b/lang/en/settings.php index 6f4376d42..5a94a824a 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -143,13 +143,11 @@ return [ 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Create New Role', - 'role_create_success' => 'Role successfully created', 'role_delete' => 'Delete Role', 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.', 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', 'role_delete_no_migration' => "Don't migrate users", 'role_delete_sure' => 'Are you sure you want to delete this role?', - 'role_delete_success' => 'Role successfully deleted', 'role_edit' => 'Edit Role', 'role_details' => 'Role Details', 'role_name' => 'Role Name', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', 'role_save' => 'Save Role', - 'role_update_success' => 'Role successfully updated', 'role_users' => 'Users in this role', 'role_users_none' => 'No users are currently assigned to this role', diff --git a/routes/api.php b/routes/api.php index d350fd86b..aa3f66b60 100644 --- a/routes/api.php +++ b/routes/api.php @@ -16,6 +16,7 @@ use BookStack\Http\Controllers\Api\ChapterExportApiController; use BookStack\Http\Controllers\Api\PageApiController; use BookStack\Http\Controllers\Api\PageExportApiController; use BookStack\Http\Controllers\Api\RecycleBinApiController; +use BookStack\Http\Controllers\Api\RoleApiController; use BookStack\Http\Controllers\Api\SearchApiController; use BookStack\Http\Controllers\Api\UserApiController; use Illuminate\Support\Facades\Route; @@ -75,6 +76,12 @@ Route::get('users/{id}', [UserApiController::class, 'read']); Route::put('users/{id}', [UserApiController::class, 'update']); Route::delete('users/{id}', [UserApiController::class, 'delete']); +Route::get('roles', [RoleApiController::class, 'list']); +Route::post('roles', [RoleApiController::class, 'create']); +Route::get('roles/{id}', [RoleApiController::class, 'read']); +Route::put('roles/{id}', [RoleApiController::class, 'update']); +Route::delete('roles/{id}', [RoleApiController::class, 'delete']); + Route::get('recycle-bin', [RecycleBinApiController::class, 'list']); Route::put('recycle-bin/{deletionId}', [RecycleBinApiController::class, 'restore']); Route::delete('recycle-bin/{deletionId}', [RecycleBinApiController::class, 'destroy']); diff --git a/tests/Api/RolesApiTest.php b/tests/Api/RolesApiTest.php index 38026a40a..e231c167c 100644 --- a/tests/Api/RolesApiTest.php +++ b/tests/Api/RolesApiTest.php @@ -40,14 +40,15 @@ class RolesApiTest extends TestCase $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id'); $resp->assertJson(['data' => [ [ - 'id' => $firstRole->id, - 'display_name' => $firstRole->display_name, - 'description' => $firstRole->description, - 'mfa_enforced' => $firstRole->mfa_enforced, + 'id' => $firstRole->id, + 'display_name' => $firstRole->display_name, + 'description' => $firstRole->description, + 'mfa_enforced' => $firstRole->mfa_enforced, + 'external_auth_id' => $firstRole->external_auth_id, 'permissions_count' => $firstRole->permissions()->count(), - 'users_count' => $firstRole->users()->count(), - 'created_at' => $firstRole->created_at->toJSON(), - 'updated_at' => $firstRole->updated_at->toJSON(), + 'users_count' => $firstRole->users()->count(), + 'created_at' => $firstRole->created_at->toJSON(), + 'updated_at' => $firstRole->updated_at->toJSON(), ], ]]); @@ -64,11 +65,12 @@ class RolesApiTest extends TestCase 'display_name' => 'My awesome role', 'description' => 'My great role description', 'mfa_enforced' => true, + 'external_auth_id' => 'auth_id', 'permissions' => [ 'content-export', - 'users-manage', - 'page-view-own', 'page-view-all', + 'page-view-own', + 'users-manage', ] ]); @@ -77,11 +79,12 @@ class RolesApiTest extends TestCase 'display_name' => 'My awesome role', 'description' => 'My great role description', 'mfa_enforced' => true, + 'external_auth_id' => 'auth_id', 'permissions' => [ 'content-export', - 'users-manage', - 'page-view-own', 'page-view-all', + 'page-view-own', + 'users-manage', ] ]); @@ -89,6 +92,7 @@ class RolesApiTest extends TestCase 'display_name' => 'My awesome role', 'description' => 'My great role description', 'mfa_enforced' => true, + 'external_auth_id' => 'auth_id', ]); /** @var Role $role */ @@ -107,7 +111,7 @@ class RolesApiTest extends TestCase 'description' => 'My new role', ]); $resp->assertStatus(422); - $resp->assertJson($this->validationResponse(['display_name' => ['The display_name field is required.']])); + $resp->assertJson($this->validationResponse(['display_name' => ['The display name field is required.']])); $resp = $this->postJson($this->baseEndpoint, [ 'name' => 'My great role with a too long desc', @@ -128,6 +132,7 @@ class RolesApiTest extends TestCase 'display_name' => $role->display_name, 'description' => $role->description, 'mfa_enforced' => $role->mfa_enforced, + 'external_auth_id' => $role->external_auth_id, 'permissions' => $role->permissions()->pluck('name')->toArray(), 'users' => $role->users()->get()->map(function (User $user) { return [ @@ -147,11 +152,12 @@ class RolesApiTest extends TestCase 'display_name' => 'My updated role', 'description' => 'My great role description', 'mfa_enforced' => true, + 'external_auth_id' => 'updated_auth_id', 'permissions' => [ 'content-export', - 'users-manage', - 'page-view-own', 'page-view-all', + 'page-view-own', + 'users-manage', ] ]); @@ -161,16 +167,18 @@ class RolesApiTest extends TestCase 'display_name' => 'My updated role', 'description' => 'My great role description', 'mfa_enforced' => true, + 'external_auth_id' => 'updated_auth_id', 'permissions' => [ 'content-export', - 'users-manage', - 'page-view-own', 'page-view-all', + 'page-view-own', + 'users-manage', ] ]); $role->refresh(); $this->assertEquals(4, $role->permissions()->count()); + $this->assertActivityExists(ActivityType::ROLE_UPDATE); } public function test_update_endpoint_does_not_remove_info_if_not_provided() @@ -181,10 +189,11 @@ class RolesApiTest extends TestCase $permissionCount = $role->permissions()->count(); $resp->assertStatus(200); - $this->assertDatabaseHas('users', [ + $this->assertDatabaseHas('roles', [ 'id' => $role->id, 'display_name' => $role->display_name, 'description' => $role->description, + 'external_auth_id' => $role->external_auth_id, ]); $role->refresh(); diff --git a/tests/Helpers/UserRoleProvider.php b/tests/Helpers/UserRoleProvider.php index 355c1687c..a06112189 100644 --- a/tests/Helpers/UserRoleProvider.php +++ b/tests/Helpers/UserRoleProvider.php @@ -90,7 +90,7 @@ class UserRoleProvider { $permissionRepo = app(PermissionsRepo::class); $roleData = Role::factory()->make()->toArray(); - $roleData['permissions'] = array_flip($rolePermissions); + $roleData['permissions'] = $rolePermissions; return $permissionRepo->saveNewRole($roleData); } diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index 8bf700c07..d4d975dbd 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -869,7 +869,7 @@ class RolesTest extends TestCase $this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [ 'display_name' => $viewerRole->display_name, 'description' => $viewerRole->description, - 'permission' => [], + 'permissions' => [], ])->assertStatus(302); $this->actingAs($viewer)->get($page->getUrl())->assertStatus(404); From 3c3c2ae9b59cd3e6adfc7b86acb6fb9b9d32ba1c Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 18 Feb 2023 18:50:01 +0000 Subject: [PATCH 03/27] Set order to role permissions API response --- app/Http/Controllers/Api/RoleApiController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/RoleApiController.php b/app/Http/Controllers/Api/RoleApiController.php index 119279822..988dfa215 100644 --- a/app/Http/Controllers/Api/RoleApiController.php +++ b/app/Http/Controllers/Api/RoleApiController.php @@ -4,7 +4,6 @@ namespace BookStack\Http\Controllers\Api; use BookStack\Auth\Permissions\PermissionsRepo; use BookStack\Auth\Role; -use BookStack\Exceptions\UserUpdateException; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -127,7 +126,7 @@ class RoleApiController extends ApiController { $role->load('users:id,name,slug'); $role->unsetRelation('permissions'); - $role->setAttribute('permissions', $role->permissions()->pluck('name')); + $role->setAttribute('permissions', $role->permissions()->orderBy('name', 'asc')->pluck('name')); $role->makeVisible(['users', 'permissions']); } } From 9502f349a282b8966ad6e5277680a64d85238028 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 18 Feb 2023 19:01:38 +0000 Subject: [PATCH 04/27] Updated test to have reliable check ordering --- tests/Api/RolesApiTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Api/RolesApiTest.php b/tests/Api/RolesApiTest.php index e231c167c..515dabe68 100644 --- a/tests/Api/RolesApiTest.php +++ b/tests/Api/RolesApiTest.php @@ -133,7 +133,7 @@ class RolesApiTest extends TestCase 'description' => $role->description, 'mfa_enforced' => $role->mfa_enforced, 'external_auth_id' => $role->external_auth_id, - 'permissions' => $role->permissions()->pluck('name')->toArray(), + 'permissions' => $role->permissions()->orderBy('name', 'asc')->pluck('name')->toArray(), 'users' => $role->users()->get()->map(function (User $user) { return [ 'id' => $user->id, From 950c02e996bcd5a142038fbeb2fc7db92f887b09 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sun, 19 Feb 2023 15:58:29 +0000 Subject: [PATCH 05/27] Added role API responses & requests Also applied other slight tweaks and comment updates based upon manual endpoint testing. --- app/Auth/Role.php | 4 ++ .../Controllers/Api/RoleApiController.php | 20 +++++---- dev/api/requests/roles-create.json | 11 +++++ dev/api/requests/roles-update.json | 14 +++++++ dev/api/responses/roles-create.json | 15 +++++++ dev/api/responses/roles-list.json | 41 +++++++++++++++++++ dev/api/responses/roles-read.json | 23 +++++++++++ dev/api/responses/roles-update.json | 26 ++++++++++++ routes/api.php | 2 +- 9 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 dev/api/requests/roles-create.json create mode 100644 dev/api/requests/roles-update.json create mode 100644 dev/api/responses/roles-create.json create mode 100644 dev/api/responses/roles-list.json create mode 100644 dev/api/responses/roles-read.json create mode 100644 dev/api/responses/roles-update.json diff --git a/app/Auth/Role.php b/app/Auth/Role.php index 0f43dd18c..d6c4a0951 100644 --- a/app/Auth/Role.php +++ b/app/Auth/Role.php @@ -31,6 +31,10 @@ class Role extends Model implements Loggable protected $hidden = ['pivot']; + protected $casts = [ + 'mfa_enforced' => 'boolean', + ]; + /** * The roles that belong to the role. */ diff --git a/app/Http/Controllers/Api/RoleApiController.php b/app/Http/Controllers/Api/RoleApiController.php index 988dfa215..4f78455e0 100644 --- a/app/Http/Controllers/Api/RoleApiController.php +++ b/app/Http/Controllers/Api/RoleApiController.php @@ -17,16 +17,16 @@ class RoleApiController extends ApiController protected $rules = [ 'create' => [ - 'display_name' => ['required', 'min:3', 'max:180'], - 'description' => ['max:180'], + 'display_name' => ['required', 'string', 'min:3', 'max:180'], + 'description' => ['string', 'max:180'], 'mfa_enforced' => ['boolean'], 'external_auth_id' => ['string'], 'permissions' => ['array'], 'permissions.*' => ['string'], ], 'update' => [ - 'display_name' => ['min:3', 'max:180'], - 'description' => ['max:180'], + 'display_name' => ['string', 'min:3', 'max:180'], + 'description' => ['string', 'max:180'], 'mfa_enforced' => ['boolean'], 'external_auth_id' => ['string'], 'permissions' => ['array'], @@ -64,6 +64,7 @@ class RoleApiController extends ApiController /** * Create a new role in the system. + * Permissions should be provided as an array of permission name strings. * Requires permission to manage roles. */ public function create(Request $request) @@ -81,7 +82,8 @@ class RoleApiController extends ApiController } /** - * View the details of a single user. + * View the details of a single role. + * Provides the permissions and a high-level list of the users assigned. * Requires permission to manage roles. */ public function read(string $id) @@ -94,6 +96,10 @@ class RoleApiController extends ApiController /** * Update an existing role in the system. + * Permissions should be provided as an array of permission name strings. + * An empty "permissions" array would clear granted permissions. + * In many cases, where permissions are changed, you'll want to fetch the existing + * permissions and then modify before providing in your update request. * Requires permission to manage roles. */ public function update(Request $request, string $id) @@ -107,9 +113,7 @@ class RoleApiController extends ApiController } /** - * Delete a user from the system. - * Can optionally accept a user id via `migrate_ownership_id` to indicate - * who should be the new owner of their related content. + * Delete a role from the system. * Requires permission to manage roles. */ public function delete(string $id) diff --git a/dev/api/requests/roles-create.json b/dev/api/requests/roles-create.json new file mode 100644 index 000000000..f8da44590 --- /dev/null +++ b/dev/api/requests/roles-create.json @@ -0,0 +1,11 @@ +{ + "display_name": "Book Maintainer", + "description": "People who maintain books", + "mfa_enforced": true, + "permissions": [ + "book-view-all", + "book-update-all", + "book-delete-all", + "restrictions-manage-all" + ] +} \ No newline at end of file diff --git a/dev/api/requests/roles-update.json b/dev/api/requests/roles-update.json new file mode 100644 index 000000000..c015cc56a --- /dev/null +++ b/dev/api/requests/roles-update.json @@ -0,0 +1,14 @@ +{ + "display_name": "Book & Shelf Maintainers", + "description": "All those who maintain books & shelves", + "mfa_enforced": false, + "permissions": [ + "book-view-all", + "book-update-all", + "book-delete-all", + "bookshelf-view-all", + "bookshelf-update-all", + "bookshelf-delete-all", + "restrictions-manage-all" + ] +} \ No newline at end of file diff --git a/dev/api/responses/roles-create.json b/dev/api/responses/roles-create.json new file mode 100644 index 000000000..e29dd128b --- /dev/null +++ b/dev/api/responses/roles-create.json @@ -0,0 +1,15 @@ +{ + "display_name": "Book Maintainer", + "description": "People who maintain books", + "mfa_enforced": true, + "updated_at": "2023-02-19T15:38:40.000000Z", + "created_at": "2023-02-19T15:38:40.000000Z", + "id": 26, + "permissions": [ + "book-delete-all", + "book-update-all", + "book-view-all", + "restrictions-manage-all" + ], + "users": [] +} \ No newline at end of file diff --git a/dev/api/responses/roles-list.json b/dev/api/responses/roles-list.json new file mode 100644 index 000000000..921c91779 --- /dev/null +++ b/dev/api/responses/roles-list.json @@ -0,0 +1,41 @@ +{ + "data": [ + { + "id": 1, + "display_name": "Admin", + "description": "Administrator of the whole application", + "created_at": "2021-09-29T16:29:19.000000Z", + "updated_at": "2022-11-03T13:26:18.000000Z", + "system_name": "admin", + "external_auth_id": "wizards", + "mfa_enforced": true, + "users_count": 11, + "permissions_count": 54 + }, + { + "id": 2, + "display_name": "Editor", + "description": "User can edit Books, Chapters & Pages", + "created_at": "2021-09-29T16:29:19.000000Z", + "updated_at": "2022-12-01T02:32:57.000000Z", + "system_name": "", + "external_auth_id": "", + "mfa_enforced": false, + "users_count": 17, + "permissions_count": 49 + }, + { + "id": 3, + "display_name": "Public", + "description": "The role given to public visitors if allowed", + "created_at": "2021-09-29T16:29:19.000000Z", + "updated_at": "2022-09-02T12:32:12.000000Z", + "system_name": "public", + "external_auth_id": "", + "mfa_enforced": false, + "users_count": 1, + "permissions_count": 2 + } + ], + "total": 3 +} \ No newline at end of file diff --git a/dev/api/responses/roles-read.json b/dev/api/responses/roles-read.json new file mode 100644 index 000000000..ead6b850e --- /dev/null +++ b/dev/api/responses/roles-read.json @@ -0,0 +1,23 @@ +{ + "id": 26, + "display_name": "Book Maintainer", + "description": "People who maintain books", + "created_at": "2023-02-19T15:38:40.000000Z", + "updated_at": "2023-02-19T15:38:40.000000Z", + "system_name": "", + "external_auth_id": "", + "mfa_enforced": true, + "permissions": [ + "book-delete-all", + "book-update-all", + "book-view-all", + "restrictions-manage-all" + ], + "users": [ + { + "id": 11, + "name": "Barry Scott", + "slug": "barry-scott" + } + ] +} \ No newline at end of file diff --git a/dev/api/responses/roles-update.json b/dev/api/responses/roles-update.json new file mode 100644 index 000000000..ca17e9505 --- /dev/null +++ b/dev/api/responses/roles-update.json @@ -0,0 +1,26 @@ +{ + "id": 26, + "display_name": "Book & Shelf Maintainers", + "description": "All those who maintain books & shelves", + "created_at": "2023-02-19T15:38:40.000000Z", + "updated_at": "2023-02-19T15:49:13.000000Z", + "system_name": "", + "external_auth_id": "", + "mfa_enforced": false, + "permissions": [ + "book-delete-all", + "book-update-all", + "book-view-all", + "bookshelf-delete-all", + "bookshelf-update-all", + "bookshelf-view-all", + "restrictions-manage-all" + ], + "users": [ + { + "id": 11, + "name": "Barry Scott", + "slug": "barry-scott" + } + ] +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index aa3f66b60..d1b64d455 100644 --- a/routes/api.php +++ b/routes/api.php @@ -60,7 +60,7 @@ Route::delete('pages/{id}', [PageApiController::class, 'delete']); Route::get('pages/{id}/export/html', [PageExportApiController::class, 'exportHtml']); Route::get('pages/{id}/export/pdf', [PageExportApiController::class, 'exportPdf']); Route::get('pages/{id}/export/plaintext', [PageExportApiController::class, 'exportPlainText']); -Route::get('pages/{id}/export/markdown', [PageExportApiController::class, 'exportMarkDown']); +Route::get('pages/{id}/export/markdown', [PageExportApiController::class, 'exportMarkdown']); Route::get('search', [SearchApiController::class, 'all']); From 4176b598ce8c61b284bcdbb1cb0c05b3a0951c70 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sun, 19 Feb 2023 16:03:50 +0000 Subject: [PATCH 06/27] Fixed unselectable checkbox role form options --- app/Http/Controllers/RoleController.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/RoleController.php b/app/Http/Controllers/RoleController.php index 2bf0a7b19..135ba329f 100644 --- a/app/Http/Controllers/RoleController.php +++ b/app/Http/Controllers/RoleController.php @@ -116,14 +116,8 @@ class RoleController extends Controller 'mfa_enforced' => ['string'], ]); - if (isset($data['permissions'])) { - $data['permissions'] = array_keys($data['permissions']); - } - - if (isset($data['mfa_enforced'])) { - $data['mfa_enforced'] = $data['mfa_enforced'] === 'true'; - } - + $data['permissions'] = array_keys($data['permissions'] ?? []); + $data['mfa_enforced'] = ($data['mfa_enforced'] ?? 'false') === 'true'; $this->permissionsRepo->updateRole($id, $data); return redirect('/settings/roles'); From 8da3e64039481ef5b02dd158c588fbaf2523cb27 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Mon, 20 Feb 2023 12:05:52 +0000 Subject: [PATCH 07/27] Updated language files to remove literal "1" values This is to encourge the ":count" values to be used instead of 1s in the translated variants so that non-pluralised languages are hardcoded with "1"s in their content, even when not used in a singular context. For #4040 --- lang/en/entities.php | 2 +- lang/en/settings.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lang/en/entities.php b/lang/en/entities.php index 8bf805774..9b02f3111 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', diff --git a/lang/en/settings.php b/lang/en/settings.php index 5a94a824a..76e689b6d 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -138,8 +138,8 @@ return [ 'roles' => 'Roles', 'role_user_roles' => 'User Roles', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Create New Role', @@ -249,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', From c80396136f1f34dce643ff5649e74e67449e6f78 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Mon, 20 Feb 2023 13:05:23 +0000 Subject: [PATCH 08/27] Increased attachment link limit from 192 to 2k Added test to cover. Did attempt a 64k limit, but values over 2k significantly increase chance of other issues since this URL may be used in redirect headers. Would rather catch issues in-app. For #4044 --- .../Api/AttachmentApiController.php | 12 +++---- app/Http/Controllers/AttachmentController.php | 18 ++++------- .../ValidationRuleServiceProvider.php | 4 +-- app/Uploads/Attachment.php | 8 ++--- ...93655_increase_attachments_path_length.php | 32 +++++++++++++++++++ tests/Uploads/AttachmentTest.php | 23 +++++++++++++ 6 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 database/migrations/2023_02_20_093655_increase_attachments_path_length.php diff --git a/app/Http/Controllers/Api/AttachmentApiController.php b/app/Http/Controllers/Api/AttachmentApiController.php index 7059ca282..9fc7f3bde 100644 --- a/app/Http/Controllers/Api/AttachmentApiController.php +++ b/app/Http/Controllers/Api/AttachmentApiController.php @@ -13,11 +13,9 @@ use Illuminate\Validation\ValidationException; class AttachmentApiController extends ApiController { - protected $attachmentService; - - public function __construct(AttachmentService $attachmentService) - { - $this->attachmentService = $attachmentService; + public function __construct( + protected AttachmentService $attachmentService + ) { } /** @@ -174,13 +172,13 @@ class AttachmentApiController extends ApiController 'name' => ['required', 'min:1', 'max:255', 'string'], 'uploaded_to' => ['required', 'integer', 'exists:pages,id'], 'file' => array_merge(['required_without:link'], $this->attachmentService->getFileValidationRules()), - 'link' => ['required_without:file', 'min:1', 'max:255', 'safe_url'], + 'link' => ['required_without:file', 'min:1', 'max:2000', 'safe_url'], ], 'update' => [ 'name' => ['min:1', 'max:255', 'string'], 'uploaded_to' => ['integer', 'exists:pages,id'], 'file' => $this->attachmentService->getFileValidationRules(), - 'link' => ['min:1', 'max:255', 'safe_url'], + 'link' => ['min:1', 'max:2000', 'safe_url'], ], ]; } diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index 03e362f4a..b6ce261d4 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -15,16 +15,10 @@ use Illuminate\Validation\ValidationException; class AttachmentController extends Controller { - protected AttachmentService $attachmentService; - protected PageRepo $pageRepo; - - /** - * AttachmentController constructor. - */ - public function __construct(AttachmentService $attachmentService, PageRepo $pageRepo) - { - $this->attachmentService = $attachmentService; - $this->pageRepo = $pageRepo; + public function __construct( + protected AttachmentService $attachmentService, + protected PageRepo $pageRepo + ) { } /** @@ -112,7 +106,7 @@ class AttachmentController extends Controller try { $this->validate($request, [ 'attachment_edit_name' => ['required', 'string', 'min:1', 'max:255'], - 'attachment_edit_url' => ['string', 'min:1', 'max:255', 'safe_url'], + 'attachment_edit_url' => ['string', 'min:1', 'max:2000', 'safe_url'], ]); } catch (ValidationException $exception) { return response()->view('attachments.manager-edit-form', array_merge($request->only(['attachment_edit_name', 'attachment_edit_url']), [ @@ -148,7 +142,7 @@ class AttachmentController extends Controller $this->validate($request, [ 'attachment_link_uploaded_to' => ['required', 'integer', 'exists:pages,id'], 'attachment_link_name' => ['required', 'string', 'min:1', 'max:255'], - 'attachment_link_url' => ['required', 'string', 'min:1', 'max:255', 'safe_url'], + 'attachment_link_url' => ['required', 'string', 'min:1', 'max:2000', 'safe_url'], ]); } catch (ValidationException $exception) { return response()->view('attachments.manager-link-form', array_merge($request->only(['attachment_link_name', 'attachment_link_url']), [ diff --git a/app/Providers/ValidationRuleServiceProvider.php b/app/Providers/ValidationRuleServiceProvider.php index 928918dc7..b3c2a4aa7 100644 --- a/app/Providers/ValidationRuleServiceProvider.php +++ b/app/Providers/ValidationRuleServiceProvider.php @@ -21,8 +21,8 @@ class ValidationRuleServiceProvider extends ServiceProvider Validator::extend('safe_url', function ($attribute, $value, $parameters, $validator) { $cleanLinkName = strtolower(trim($value)); - $isJs = strpos($cleanLinkName, 'javascript:') === 0; - $isData = strpos($cleanLinkName, 'data:') === 0; + $isJs = str_starts_with($cleanLinkName, 'javascript:'); + $isData = str_starts_with($cleanLinkName, 'data:'); return !$isJs && !$isData; }); diff --git a/app/Uploads/Attachment.php b/app/Uploads/Attachment.php index 01c927382..e33b13db4 100644 --- a/app/Uploads/Attachment.php +++ b/app/Uploads/Attachment.php @@ -40,12 +40,10 @@ class Attachment extends Model /** * Get the downloadable file name for this upload. - * - * @return mixed|string */ - public function getFileName() + public function getFileName(): string { - if (strpos($this->name, '.') !== false) { + if (str_contains($this->name, '.')) { return $this->name; } @@ -71,7 +69,7 @@ class Attachment extends Model */ public function getUrl($openInline = false): string { - if ($this->external && strpos($this->path, 'http') !== 0) { + if ($this->external && !str_starts_with($this->path, 'http')) { return $this->path; } diff --git a/database/migrations/2023_02_20_093655_increase_attachments_path_length.php b/database/migrations/2023_02_20_093655_increase_attachments_path_length.php new file mode 100644 index 000000000..f7cb64ce6 --- /dev/null +++ b/database/migrations/2023_02_20_093655_increase_attachments_path_length.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('attachments', function (Blueprint $table) { + $table->text('path')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('attachments', function (Blueprint $table) { + $table->string('path')->change(); + }); + } +}; diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index 1da12cd1c..bd03c339c 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -111,6 +111,29 @@ class AttachmentTest extends TestCase $this->files->deleteAllAttachmentFiles(); } + public function test_attaching_long_links_to_a_page() + { + $page = $this->entities->page(); + + $link = 'https://example.com?query=' . str_repeat('catsIScool', 195); + $linkReq = $this->asAdmin()->post('attachments/link', [ + 'attachment_link_url' => $link, + 'attachment_link_name' => 'Example Attachment Link', + 'attachment_link_uploaded_to' => $page->id, + ]); + + $linkReq->assertStatus(200); + $this->assertDatabaseHas('attachments', [ + 'uploaded_to' => $page->id, + 'path' => $link, + 'external' => true, + ]); + + $attachment = $page->attachments()->where('external', '=', true)->first(); + $resp = $this->get($attachment->getUrl()); + $resp->assertRedirect($link); + } + public function test_attachment_updating() { $page = $this->entities->page(); From 31495758a9459d13d6dd4718262c23f7f763bc92 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Wed, 22 Feb 2023 14:32:40 +0000 Subject: [PATCH 09/27] Made page-save HTML formatting much more efficient Replaced the existing xpath-heavy system with a more manual traversal approach. Fixes following slow areas of old system: - Old system would repeat ID-setting action for elements (Headers could be processed up to three times). - Old system had a few very open xpath queries for headers. - Old system would update links on every ID change, which triggers it's own xpath query for links, leading to exponential scaling issues. New system only does one xpath query for links when changes are needed. Added test to cover. For #3932 --- app/Entities/Tools/PageContent.php | 99 ++++++++++++++++-------------- tests/Entity/PageContentTest.php | 19 ++++++ 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 9aa2e8d35..2613359c3 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -19,20 +19,15 @@ use Illuminate\Support\Str; class PageContent { - protected Page $page; - - /** - * PageContent constructor. - */ - public function __construct(Page $page) - { - $this->page = $page; + public function __construct( + protected Page $page + ) { } /** * Update the content of the page with new provided HTML. */ - public function setNewHTML(string $html) + public function setNewHTML(string $html): void { $html = $this->extractBase64ImagesFromHtml($html); $this->page->html = $this->formatHtml($html); @@ -43,7 +38,7 @@ class PageContent /** * Update the content of the page with new provided Markdown content. */ - public function setNewMarkdown(string $markdown) + public function setNewMarkdown(string $markdown): void { $markdown = $this->extractBase64ImagesFromMarkdown($markdown); $this->page->markdown = $markdown; @@ -57,7 +52,7 @@ class PageContent */ protected function extractBase64ImagesFromHtml(string $htmlText): string { - if (empty($htmlText) || strpos($htmlText, 'data:image') === false) { + if (empty($htmlText) || !str_contains($htmlText, 'data:image')) { return $htmlText; } @@ -91,7 +86,7 @@ class PageContent * Attempting to capture the whole data uri using regex can cause PHP * PCRE limits to be hit with larger, multi-MB, files. */ - protected function extractBase64ImagesFromMarkdown(string $markdown) + protected function extractBase64ImagesFromMarkdown(string $markdown): string { $matches = []; $contentLength = strlen($markdown); @@ -183,32 +178,13 @@ class PageContent $childNodes = $body->childNodes; $xPath = new DOMXPath($doc); - // Set ids on top-level nodes + // Map to hold used ID references $idMap = []; - foreach ($childNodes as $index => $childNode) { - [$oldId, $newId] = $this->setUniqueId($childNode, $idMap); - if ($newId && $newId !== $oldId) { - $this->updateLinks($xPath, '#' . $oldId, '#' . $newId); - } - } + // Map to hold changing ID references + $changeMap = []; - // Set ids on nested header nodes - $nestedHeaders = $xPath->query('//body//*//h1|//body//*//h2|//body//*//h3|//body//*//h4|//body//*//h5|//body//*//h6'); - foreach ($nestedHeaders as $nestedHeader) { - [$oldId, $newId] = $this->setUniqueId($nestedHeader, $idMap); - if ($newId && $newId !== $oldId) { - $this->updateLinks($xPath, '#' . $oldId, '#' . $newId); - } - } - - // Ensure no duplicate ids within child items - $idElems = $xPath->query('//body//*//*[@id]'); - foreach ($idElems as $domElem) { - [$oldId, $newId] = $this->setUniqueId($domElem, $idMap); - if ($newId && $newId !== $oldId) { - $this->updateLinks($xPath, '#' . $oldId, '#' . $newId); - } - } + $this->updateIdsRecursively($body, 0, $idMap, $changeMap); + $this->updateLinks($xPath, $changeMap); // Generate inner html as a string $html = ''; @@ -223,20 +199,53 @@ class PageContent } /** - * Update the all links to the $old location to instead point to $new. + * For the given DOMNode, traverse its children recursively and update IDs + * where required (Top-level, headers & elements with IDs). + * Will update the provided $changeMap array with changes made, where keys are the old + * ids and the corresponding values are the new ids. */ - protected function updateLinks(DOMXPath $xpath, string $old, string $new) + protected function updateIdsRecursively(DOMNode $element, int $depth, array &$idMap, array &$changeMap): void { - $old = str_replace('"', '', $old); - $matchingLinks = $xpath->query('//body//*//*[@href="' . $old . '"]'); - foreach ($matchingLinks as $domElem) { - $domElem->setAttribute('href', $new); + /* @var DOMNode $child */ + foreach ($element->childNodes as $child) { + if ($child instanceof DOMElement && ($depth === 0 || in_array($child->nodeName, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) || $child->getAttribute('id'))) { + [$oldId, $newId] = $this->setUniqueId($child, $idMap); + if ($newId && $newId !== $oldId && !isset($idMap[$oldId])) { + $changeMap[$oldId] = $newId; + } + } + + if ($child->hasChildNodes()) { + $this->updateIdsRecursively($child, $depth + 1, $idMap, $changeMap); + } + } + } + + /** + * Update the all links in the given xpath to apply requires changes within the + * given $changeMap array. + */ + protected function updateLinks(DOMXPath $xpath, array $changeMap): void + { + if (empty($changeMap)) { + return; + } + + $links = $xpath->query('//body//*//*[@href]'); + /** @var DOMElement $domElem */ + foreach ($links as $domElem) { + $href = ltrim($domElem->getAttribute('href'), '#'); + $newHref = $changeMap[$href] ?? null; + if ($newHref) { + $domElem->setAttribute('href', '#' . $newHref); + } } } /** * Set a unique id on the given DOMElement. - * A map for existing ID's should be passed in to check for current existence. + * A map for existing ID's should be passed in to check for current existence, + * and this will be updated with any new IDs set upon elements. * Returns a pair of strings in the format [old_id, new_id]. */ protected function setUniqueId(DOMNode $element, array &$idMap): array @@ -247,7 +256,7 @@ class PageContent // Stop if there's an existing valid id that has not already been used. $existingId = $element->getAttribute('id'); - if (strpos($existingId, 'bkmrk') === 0 && !isset($idMap[$existingId])) { + if (str_starts_with($existingId, 'bkmrk') && !isset($idMap[$existingId])) { $idMap[$existingId] = true; return [$existingId, $existingId]; @@ -258,7 +267,7 @@ class PageContent // the same content is passed through. $contentId = 'bkmrk-' . mb_substr(strtolower(preg_replace('/\s+/', '-', trim($element->nodeValue))), 0, 20); $newId = urlencode($contentId); - $loopIndex = 0; + $loopIndex = 1; while (isset($idMap[$newId])) { $newId = urlencode($contentId . '-' . $loopIndex); diff --git a/tests/Entity/PageContentTest.php b/tests/Entity/PageContentTest.php index f62d09b1f..6d6224abf 100644 --- a/tests/Entity/PageContentTest.php +++ b/tests/Entity/PageContentTest.php @@ -732,4 +732,23 @@ class PageContentTest extends TestCase $this->assertStringContainsString('<p id="bkmrk-%C2%A0"> </p>', $page->refresh()->html); } + + public function test_page_save_with_many_headers_and_links_is_reasonable() + { + $page = $this->entities->page(); + + $content = ''; + for ($i = 0; $i < 500; $i++) { + $content .= "<table><tbody><tr><td><h5 id='header-{$i}'>Simple Test</h5><a href='#header-{$i}'></a></td></tr></tbody></table>"; + } + + $time = time(); + $this->asEditor()->put($page->getUrl(), [ + 'name' => $page->name, + 'html' => $content, + ])->assertRedirect(); + + $timeElapsed = time() - $time; + $this->assertLessThan(3, $timeElapsed); + } } From 6545afacd69bbb1d13e4e4c97da84004789a99e8 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 23 Feb 2023 12:30:27 +0000 Subject: [PATCH 10/27] Changed autosave handling for better editor performance This changes how the editors interact with the parent page-editor compontent, which handles auto-saving. Instead of blasting the full editor content upon any change to that parent compontent, the editors just alert of a change, without the content. The parent compontent then requests the editor content from the editor component when it needs that data for an autosave. For #3981 --- resources/js/components/markdown-editor.js | 9 ++++ resources/js/components/page-editor.js | 54 +++++++++++----------- resources/js/components/wysiwyg-editor.js | 15 +++++- resources/js/markdown/actions.js | 14 +++++- resources/js/wysiwyg/config.js | 3 +- 5 files changed, 64 insertions(+), 31 deletions(-) diff --git a/resources/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js index 4c3de91f6..5cd92cae2 100644 --- a/resources/js/components/markdown-editor.js +++ b/resources/js/components/markdown-editor.js @@ -137,4 +137,13 @@ export class MarkdownEditor extends Component { return drawioAttrEl.getAttribute('drawio-url') || ''; } + /** + * Get the content of this editor. + * Used by the parent page editor component. + * @return {{html: String, markdown: String}} + */ + getContent() { + return this.editor.actions.getContent(); + } + } diff --git a/resources/js/components/page-editor.js b/resources/js/components/page-editor.js index 950a5a3b3..c58f45b66 100644 --- a/resources/js/components/page-editor.js +++ b/resources/js/components/page-editor.js @@ -33,12 +33,11 @@ export class PageEditor extends Component { this.setChangelogText = this.$opts.setChangelogText; // State data - this.editorHTML = ''; - this.editorMarkdown = ''; this.autoSave = { interval: null, frequency: 30000, last: 0, + pendingChange: false, }; this.shownWarningsCache = new Set(); @@ -59,12 +58,12 @@ export class PageEditor extends Component { window.$events.listen('editor-save-page', this.savePage.bind(this)); // Listen to content changes from the editor - window.$events.listen('editor-html-change', html => { - this.editorHTML = html; - }); - window.$events.listen('editor-markdown-change', markdown => { - this.editorMarkdown = markdown; - }); + const onContentChange = () => this.autoSave.pendingChange = true; + window.$events.listen('editor-html-change', onContentChange); + window.$events.listen('editor-markdown-change', onContentChange); + + // Listen to changes on the title input + this.titleElem.addEventListener('input', onContentChange); // Changelog controls const updateChangelogDebounced = debounce(this.updateChangelogDisplay.bind(this), 300, false); @@ -89,18 +88,17 @@ export class PageEditor extends Component { } startAutoSave() { - let lastContent = this.titleElem.value.trim() + '::' + this.editorHTML; - this.autoSaveInterval = window.setInterval(() => { - // Stop if manually saved recently to prevent bombarding the server - let savedRecently = (Date.now() - this.autoSave.last < (this.autoSave.frequency)/2); - if (savedRecently) return; - const newContent = this.titleElem.value.trim() + '::' + this.editorHTML; - if (newContent !== lastContent) { - lastContent = newContent; - this.saveDraft(); - } + this.autoSave.interval = window.setInterval(this.runAutoSave.bind(this), this.autoSave.frequency); + } - }, this.autoSave.frequency); + runAutoSave() { + // Stop if manually saved recently to prevent bombarding the server + const savedRecently = (Date.now() - this.autoSave.last < (this.autoSave.frequency)/2); + if (savedRecently || !this.autoSave.pendingChange) { + return; + } + + this.saveDraft() } savePage() { @@ -108,14 +106,10 @@ export class PageEditor extends Component { } async saveDraft() { - const data = { - name: this.titleElem.value.trim(), - html: this.editorHTML, - }; + const data = {name: this.titleElem.value.trim()}; - if (this.editorType === 'markdown') { - data.markdown = this.editorMarkdown; - } + const editorContent = this.getEditorComponent().getContent(); + Object.assign(data, editorContent); let didSave = false; try { @@ -132,6 +126,7 @@ export class PageEditor extends Component { } didSave = true; + this.autoSave.pendingChange = false; } catch (err) { // Save the editor content in LocalStorage as a last resort, just in case. try { @@ -207,4 +202,11 @@ export class PageEditor extends Component { } } + /** + * @return MarkdownEditor|WysiwygEditor + */ + getEditorComponent() { + return window.$components.first('markdown-editor') || window.$components.first('wysiwyg-editor'); + } + } diff --git a/resources/js/components/wysiwyg-editor.js b/resources/js/components/wysiwyg-editor.js index 976dba68f..96731a0d9 100644 --- a/resources/js/components/wysiwyg-editor.js +++ b/resources/js/components/wysiwyg-editor.js @@ -25,7 +25,9 @@ export class WysiwygEditor extends Component { }); window.$events.emitPublic(this.elem, 'editor-tinymce::pre-init', {config: this.tinyMceConfig}); - window.tinymce.init(this.tinyMceConfig); + window.tinymce.init(this.tinyMceConfig).then(editors => { + this.editor = editors[0]; + }); } getDrawIoUrl() { @@ -36,4 +38,15 @@ export class WysiwygEditor extends Component { return ''; } + /** + * Get the content of this editor. + * Used by the parent page editor component. + * @return {{html: String}} + */ + getContent() { + return { + html: this.editor.getContent() + }; + } + } \ No newline at end of file diff --git a/resources/js/markdown/actions.js b/resources/js/markdown/actions.js index c2c3409a5..9faf43de3 100644 --- a/resources/js/markdown/actions.js +++ b/resources/js/markdown/actions.js @@ -6,6 +6,10 @@ export class Actions { */ constructor(editor) { this.editor = editor; + this.lastContent = { + html: '', + markdown: '', + }; } updateAndRender() { @@ -13,11 +17,17 @@ export class Actions { this.editor.config.inputEl.value = content; const html = this.editor.markdown.render(content); - window.$events.emit('editor-html-change', html); - window.$events.emit('editor-markdown-change', content); + window.$events.emit('editor-html-change', ''); + window.$events.emit('editor-markdown-change', ''); + this.lastContent.html = html; + this.lastContent.markdown = content; this.editor.display.patchWithHtml(html); } + getContent() { + return this.lastContent; + } + insertImage() { const cursorPos = this.editor.cm.getCursor('from'); /** @type {ImageManager} **/ diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index d5ec20e26..85c1919d4 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -185,11 +185,10 @@ function getSetupCallback(options) { }); function editorChange() { - const content = editor.getContent(); if (options.darkMode) { editor.contentDocument.documentElement.classList.add('dark-mode'); } - window.$events.emit('editor-html-change', content); + window.$events.emit('editor-html-change', ''); } // Custom handler hook From 8bebea4ccadbf3ff40fd836a1017cbc2ae474eca Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 23 Feb 2023 22:14:47 +0000 Subject: [PATCH 11/27] Changed the way settings are loaded This new method batch-loads them from the database, and removes the cache-layer with the intention that a couple of batch fetches from the DB is more efficient than hitting the cache each time. --- app/Settings/SettingService.php | 146 ++++++++++++++---------- tests/Commands/UpdateUrlCommandTest.php | 5 + 2 files changed, 90 insertions(+), 61 deletions(-) diff --git a/app/Settings/SettingService.php b/app/Settings/SettingService.php index d1bac164d..9bcd6c7ec 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -3,45 +3,29 @@ namespace BookStack\Settings; use BookStack\Auth\User; -use Illuminate\Contracts\Cache\Repository as Cache; /** * Class SettingService * The settings are a simple key-value database store. * For non-authenticated users, user settings are stored via the session instead. + * A local array-based cache is used to for setting accesses across a request. */ class SettingService { - protected Setting $setting; - protected Cache $cache; protected array $localCache = []; - protected string $cachePrefix = 'setting-'; - - public function __construct(Setting $setting, Cache $cache) - { - $this->setting = $setting; - $this->cache = $cache; - } /** * Gets a setting from the database, * If not found, Returns default, Which is false by default. */ - public function get(string $key, $default = null) + public function get(string $key, $default = null): mixed { if (is_null($default)) { $default = config('setting-defaults.' . $key, false); } - if (isset($this->localCache[$key])) { - return $this->localCache[$key]; - } - $value = $this->getValueFromStore($key) ?? $default; - $formatted = $this->formatValue($value, $default); - $this->localCache[$key] = $formatted; - - return $formatted; + return $this->formatValue($value, $default); } /** @@ -79,52 +63,78 @@ class SettingService } /** - * Gets a setting value from the cache or database. - * Looks at the system defaults if not cached or in database. - * Returns null if nothing is found. + * Gets a setting value from the local cache. + * Will load the local cache if not previously loaded. */ - protected function getValueFromStore(string $key) + protected function getValueFromStore(string $key): mixed { - // Check the cache - $cacheKey = $this->cachePrefix . $key; - $cacheVal = $this->cache->get($cacheKey, null); - if ($cacheVal !== null) { - return $cacheVal; + $cacheCategory = $this->localCacheCategory($key); + if (!isset($this->localCache[$cacheCategory])) { + $this->loadToLocalCache($cacheCategory); } - // Check the database - $settingObject = $this->getSettingObjectByKey($key); - if ($settingObject !== null) { - $value = $settingObject->value; - - if ($settingObject->type === 'array') { - $value = json_decode($value, true) ?? []; - } - - $this->cache->forever($cacheKey, $value); - - return $value; - } - - return null; + return $this->localCache[$cacheCategory][$key] ?? null; } /** - * Clear an item from the cache completely. + * Put the given value into the local cached under the given key. */ - protected function clearFromCache(string $key) + protected function putValueIntoLocalCache(string $key, mixed $value): void { - $cacheKey = $this->cachePrefix . $key; - $this->cache->forget($cacheKey); - if (isset($this->localCache[$key])) { - unset($this->localCache[$key]); + $cacheCategory = $this->localCacheCategory($key); + if (!isset($this->localCache[$cacheCategory])) { + $this->loadToLocalCache($cacheCategory); + } + + $this->localCache[$cacheCategory][$key] = $value; + } + + /** + * Get the category for the given setting key. + * Will return 'app' for a general app setting otherwise 'user:<user_id>' for a user setting. + */ + protected function localCacheCategory(string $key): string + { + if (str_starts_with($key, 'user:')) { + return implode(':', array_slice(explode(':', $key), 0, 2)); + } + + return 'app'; + } + + /** + * For the given category, load the relevant settings from the database into the local cache. + */ + protected function loadToLocalCache(string $cacheCategory): void + { + $query = Setting::query(); + + if ($cacheCategory === 'app') { + $query->where('setting_key', 'not like', 'user:%'); + } else { + $query->where('setting_key', 'like', $cacheCategory . ':%'); + } + $settings = $query->toBase()->get(); + + if (!isset($this->localCache[$cacheCategory])) { + $this->localCache[$cacheCategory] = []; + } + + foreach ($settings as $setting) { + $value = $setting->value; + + if ($setting->type === 'array') { + $value = json_decode($value, true) ?? []; + } + + $this->localCache[$cacheCategory][$setting->setting_key] = $value; } } /** * Format a settings value. */ - protected function formatValue($value, $default) + protected function formatValue(mixed $value, mixed $default): mixed { // Change string booleans to actual booleans if ($value === 'true') { @@ -155,21 +165,22 @@ class SettingService * Add a setting to the database. * Values can be an array or a string. */ - public function put(string $key, $value): bool + public function put(string $key, mixed $value): bool { - $setting = $this->setting->newQuery()->firstOrNew([ + $setting = Setting::query()->firstOrNew([ 'setting_key' => $key, ]); + $setting->type = 'string'; + $setting->value = $value; if (is_array($value)) { $setting->type = 'array'; - $value = $this->formatArrayValue($value); + $setting->value = $this->formatArrayValue($value); } - $setting->value = $value; $setting->save(); - $this->clearFromCache($key); + $this->putValueIntoLocalCache($key, $value); return true; } @@ -209,7 +220,7 @@ class SettingService * Can only take string value types since this may use * the session which is less flexible to data types. */ - public function putForCurrentUser(string $key, string $value) + public function putForCurrentUser(string $key, string $value): bool { return $this->putUser(user(), $key, $value); } @@ -231,15 +242,19 @@ class SettingService if ($setting) { $setting->delete(); } - $this->clearFromCache($key); + + $cacheCategory = $this->localCacheCategory($key); + if (isset($this->localCache[$cacheCategory])) { + unset($this->localCache[$cacheCategory][$key]); + } } /** * Delete settings for a given user id. */ - public function deleteUserSettings(string $userId) + public function deleteUserSettings(string $userId): void { - return $this->setting->newQuery() + Setting::query() ->where('setting_key', 'like', $this->userKey($userId) . '%') ->delete(); } @@ -249,7 +264,16 @@ class SettingService */ protected function getSettingObjectByKey(string $key): ?Setting { - return $this->setting->newQuery() - ->where('setting_key', '=', $key)->first(); + return Setting::query() + ->where('setting_key', '=', $key) + ->first(); + } + + /** + * Empty the local setting value cache used by this service. + */ + public function flushCache(): void + { + $this->localCache = []; } } diff --git a/tests/Commands/UpdateUrlCommandTest.php b/tests/Commands/UpdateUrlCommandTest.php index c07a80312..1788e9452 100644 --- a/tests/Commands/UpdateUrlCommandTest.php +++ b/tests/Commands/UpdateUrlCommandTest.php @@ -39,6 +39,8 @@ class UpdateUrlCommandTest extends TestCase setting()->put('my-custom-item', 'https://example.com/donkey/cat'); $this->runUpdate('https://example.com', 'https://cats.example.com'); + setting()->flushCache(); + $settingVal = setting('my-custom-item'); $this->assertEquals('https://cats.example.com/donkey/cat', $settingVal); } @@ -47,6 +49,9 @@ class UpdateUrlCommandTest extends TestCase { setting()->put('my-custom-array-item', [['name' => 'a https://example.com/donkey/cat url']]); $this->runUpdate('https://example.com', 'https://cats.example.com'); + + setting()->flushCache(); + $settingVal = setting('my-custom-array-item'); $this->assertEquals('a https://cats.example.com/donkey/cat url', $settingVal[0]['name']); } From a031edec16234a2e62a01ba3855107e2c5f763b7 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 23 Feb 2023 22:59:26 +0000 Subject: [PATCH 12/27] Fixed old deprecated encoding convert on HTML doc load --- app/Entities/Tools/PageContent.php | 4 ++-- app/References/CrossLinkParser.php | 4 ++-- app/Search/SearchIndex.php | 27 ++++++++++----------------- app/Util/HtmlContentFilter.php | 4 ++-- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 2613359c3..b4bc8b91b 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -449,8 +449,8 @@ class PageContent { libxml_use_internal_errors(true); $doc = new DOMDocument(); - $html = '<body>' . $html . '</body>'; - $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $html = '<?xml encoding="utf-8" ?><body>' . $html . '</body>'; + $doc->loadHTML($html); return $doc; } diff --git a/app/References/CrossLinkParser.php b/app/References/CrossLinkParser.php index 37db203df..e7afea5f1 100644 --- a/app/References/CrossLinkParser.php +++ b/app/References/CrossLinkParser.php @@ -54,10 +54,10 @@ class CrossLinkParser { $links = []; - $html = '<body>' . $html . '</body>'; + $html = '<?xml encoding="utf-8" ?><body>' . $html . '</body>'; libxml_use_internal_errors(true); $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $doc->loadHTML($html); $xPath = new DOMXPath($doc); $anchors = $xPath->query('//a[@href]'); diff --git a/app/Search/SearchIndex.php b/app/Search/SearchIndex.php index 54ed95ebb..25389226a 100644 --- a/app/Search/SearchIndex.php +++ b/app/Search/SearchIndex.php @@ -15,25 +15,18 @@ class SearchIndex { /** * A list of delimiter characters used to break-up parsed content into terms for indexing. - * - * @var string */ - public static $delimiters = " \n\t.,!?:;()[]{}<>`'\""; + public static string $delimiters = " \n\t.,!?:;()[]{}<>`'\""; - /** - * @var EntityProvider - */ - protected $entityProvider; - - public function __construct(EntityProvider $entityProvider) - { - $this->entityProvider = $entityProvider; + public function __construct( + protected EntityProvider $entityProvider + ) { } /** * Index the given entity. */ - public function indexEntity(Entity $entity) + public function indexEntity(Entity $entity): void { $this->deleteEntityTerms($entity); $terms = $this->entityToTermDataArray($entity); @@ -45,7 +38,7 @@ class SearchIndex * * @param Entity[] $entities */ - public function indexEntities(array $entities) + public function indexEntities(array $entities): void { $terms = []; foreach ($entities as $entity) { @@ -69,7 +62,7 @@ class SearchIndex * * @param callable(Entity, int, int):void|null $progressCallback */ - public function indexAllEntities(?callable $progressCallback = null) + public function indexAllEntities(?callable $progressCallback = null): void { SearchTerm::query()->truncate(); @@ -101,7 +94,7 @@ class SearchIndex /** * Delete related Entity search terms. */ - public function deleteEntityTerms(Entity $entity) + public function deleteEntityTerms(Entity $entity): void { $entity->searchTerms()->delete(); } @@ -145,12 +138,12 @@ class SearchIndex 'h6' => 1.5, ]; - $html = '<body>' . $html . '</body>'; + $html = '<?xml encoding="utf-8" ?><body>' . $html . '</body>'; $html = str_ireplace(['<br>', '<br />', '<br/>'], "\n", $html); libxml_use_internal_errors(true); $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $doc->loadHTML($html); $topElems = $doc->documentElement->childNodes->item(0)->childNodes; /** @var DOMNode $child */ diff --git a/app/Util/HtmlContentFilter.php b/app/Util/HtmlContentFilter.php index 5e3c4822c..e51f512bd 100644 --- a/app/Util/HtmlContentFilter.php +++ b/app/Util/HtmlContentFilter.php @@ -19,10 +19,10 @@ class HtmlContentFilter return $html; } - $html = '<body>' . $html . '</body>'; + $html = '<?xml encoding="utf-8" ?><body>' . $html . '</body>'; libxml_use_internal_errors(true); $doc = new DOMDocument(); - $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $doc->loadHTML($html); $xPath = new DOMXPath($doc); // Remove standard script tags From 8abb41abbd5833217823890da936a29a129bd046 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 23 Feb 2023 23:01:03 +0000 Subject: [PATCH 13/27] Added caching to the loading of system roles Admin system role was being loaded for each permission check performed. This caches the fetching for the request lifetime. --- app/Auth/Role.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Auth/Role.php b/app/Auth/Role.php index d6c4a0951..8f9702fa0 100644 --- a/app/Auth/Role.php +++ b/app/Auth/Role.php @@ -111,7 +111,13 @@ class Role extends Model implements Loggable */ public static function getSystemRole(string $systemName): ?self { - return static::query()->where('system_name', '=', $systemName)->first(); + static $cache = []; + + if (!isset($cache[$systemName])) { + $cache[$systemName] = static::query()->where('system_name', '=', $systemName)->first(); + } + + return $cache[$systemName]; } /** From b88b1bef2c0cf74627c5122b656dfabc2d5f23ee Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Thu, 23 Feb 2023 23:06:12 +0000 Subject: [PATCH 14/27] Added updated_at index to pages table This has a large impact on some areas where latest updated pages are shown, such as the homepage for example. --- ...3_200227_add_updated_at_index_to_pages.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2023_02_23_200227_add_updated_at_index_to_pages.php diff --git a/database/migrations/2023_02_23_200227_add_updated_at_index_to_pages.php b/database/migrations/2023_02_23_200227_add_updated_at_index_to_pages.php new file mode 100644 index 000000000..115bbb0c0 --- /dev/null +++ b/database/migrations/2023_02_23_200227_add_updated_at_index_to_pages.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('pages', function (Blueprint $table) { + $table->index('updated_at', 'pages_updated_at_index'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('pages', function (Blueprint $table) { + $table->dropIndex('pages_updated_at_index'); + }); + } +}; From f35c42b0b8259da31c016a76ff5af59fcd6d2e67 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sat, 25 Feb 2023 17:35:21 +0000 Subject: [PATCH 15/27] Updated php deps and translaters in prep for v23.02 --- .github/translators.txt | 3 ++ composer.lock | 97 +++++++++++++++++++++-------------------- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/.github/translators.txt b/.github/translators.txt index 2c9423081..5d29752a8 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -308,3 +308,6 @@ Adrian Ocneanu (aocneanu) :: Romanian Eduardo Castanho (EduardoCastanho) :: Portuguese VIET NAM VPS (vietnamvps) :: Vietnamese m4tthi4s :: French +toras9000 :: Japanese +pathab :: German +MichelSchoon85 :: Dutch diff --git a/composer.lock b/composer.lock index 26b2aa360..040e9ce04 100644 --- a/composer.lock +++ b/composer.lock @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.258.11", + "version": "3.260.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "5e339f47f86db7ed5f5afcda345d30ac1713aed3" + "reference": "547b8047b2f9a551a7100b22e1abe1a3cc1b0ff0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e339f47f86db7ed5f5afcda345d30ac1713aed3", - "reference": "5e339f47f86db7ed5f5afcda345d30ac1713aed3", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/547b8047b2f9a551a7100b22e1abe1a3cc1b0ff0", + "reference": "547b8047b2f9a551a7100b22e1abe1a3cc1b0ff0", "shasum": "" }, "require": { @@ -146,9 +146,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.258.11" + "source": "https://github.com/aws/aws-sdk-php/tree/3.260.3" }, - "time": "2023-02-15T20:08:42+00:00" + "time": "2023-02-24T19:25:34+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1944,16 +1944,16 @@ }, { "name": "laravel/framework", - "version": "v9.52.0", + "version": "v9.52.4", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "eb85cd9d72e5bfa54b4d0d9040786f26d6184a9e" + "reference": "9239128cfb4d22afefb64060dfecf53e82987267" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/eb85cd9d72e5bfa54b4d0d9040786f26d6184a9e", - "reference": "eb85cd9d72e5bfa54b4d0d9040786f26d6184a9e", + "url": "https://api.github.com/repos/laravel/framework/zipball/9239128cfb4d22afefb64060dfecf53e82987267", + "reference": "9239128cfb4d22afefb64060dfecf53e82987267", "shasum": "" }, "require": { @@ -2138,7 +2138,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-02-14T14:51:14+00:00" + "time": "2023-02-22T14:38:06+00:00" }, { "name": "laravel/serializable-closure", @@ -2271,16 +2271,16 @@ }, { "name": "laravel/tinker", - "version": "v2.8.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "74d0b287cc4ae65d15c368dd697aae71d62a73ad" + "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/74d0b287cc4ae65d15c368dd697aae71d62a73ad", - "reference": "74d0b287cc4ae65d15c368dd697aae71d62a73ad", + "url": "https://api.github.com/repos/laravel/tinker/zipball/04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", + "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", "shasum": "" }, "require": { @@ -2333,9 +2333,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.8.0" + "source": "https://github.com/laravel/tinker/tree/v2.8.1" }, - "time": "2023-01-10T18:03:30+00:00" + "time": "2023-02-15T16:40:09+00:00" }, { "name": "league/commonmark", @@ -2527,16 +2527,16 @@ }, { "name": "league/flysystem", - "version": "3.12.2", + "version": "3.12.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f6377c709d2275ed6feaf63e44be7a7162b0e77f" + "reference": "81e87e74dd5213795c7846d65089712d2dda90ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f6377c709d2275ed6feaf63e44be7a7162b0e77f", - "reference": "f6377c709d2275ed6feaf63e44be7a7162b0e77f", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/81e87e74dd5213795c7846d65089712d2dda90ce", + "reference": "81e87e74dd5213795c7846d65089712d2dda90ce", "shasum": "" }, "require": { @@ -2598,7 +2598,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.12.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.12.3" }, "funding": [ { @@ -2614,7 +2614,7 @@ "type": "tidelift" } ], - "time": "2023-01-19T12:02:19+00:00" + "time": "2023-02-18T15:32:41+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -8452,16 +8452,16 @@ }, { "name": "nunomaduro/larastan", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/larastan.git", - "reference": "14f631348ead3e245651606931863b4f218d1f78" + "reference": "238fdbfba3aae133cdec73e99826c9b0232141f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/14f631348ead3e245651606931863b4f218d1f78", - "reference": "14f631348ead3e245651606931863b4f218d1f78", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/238fdbfba3aae133cdec73e99826c9b0232141f7", + "reference": "238fdbfba3aae133cdec73e99826c9b0232141f7", "shasum": "" }, "require": { @@ -8524,7 +8524,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/larastan/issues", - "source": "https://github.com/nunomaduro/larastan/tree/2.4.0" + "source": "https://github.com/nunomaduro/larastan/tree/2.4.1" }, "funding": [ { @@ -8544,7 +8544,7 @@ "type": "patreon" } ], - "time": "2023-01-11T11:57:44+00:00" + "time": "2023-02-05T12:19:17+00:00" }, { "name": "phar-io/manifest", @@ -8746,16 +8746,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.17", + "version": "1.10.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2" + "reference": "5419375b5891add97dc74be71e6c1c34baaddf64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/204e459e7822f2c586463029f5ecec31bb45a1f2", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64", + "reference": "5419375b5891add97dc74be71e6c1c34baaddf64", "shasum": "" }, "require": { @@ -8785,7 +8785,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.17" + "source": "https://github.com/phpstan/phpstan/tree/1.10.3" }, "funding": [ { @@ -8801,27 +8801,27 @@ "type": "tidelift" } ], - "time": "2023-02-08T12:25:00+00:00" + "time": "2023-02-25T14:47:13+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.24", + "version": "9.2.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954", + "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -8870,7 +8870,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25" }, "funding": [ { @@ -8878,7 +8878,7 @@ "type": "github" } ], - "time": "2023-01-26T08:26:55+00:00" + "time": "2023-02-25T05:32:00+00:00" }, { "name": "phpunit/php-file-iterator", @@ -10189,16 +10189,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "version": "3.7.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", "shasum": "" }, "require": { @@ -10234,14 +10234,15 @@ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" ], "support": { "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2022-06-18T07:21:10+00:00" + "time": "2023-02-22T23:07:41+00:00" }, { "name": "ssddanbrown/asserthtml", From ba21b5419529a59edfc59d03dda35175ddaeda6d Mon Sep 17 00:00:00 2001 From: Dan Brown <email@danb.me> Date: Sun, 26 Feb 2023 10:36:15 +0000 Subject: [PATCH 16/27] Updated translations with latest Crowdin changes (#4025) --- lang/ar/activities.php | 5 + lang/ar/entities.php | 2 +- lang/ar/settings.php | 9 +- lang/bg/activities.php | 5 + lang/bg/entities.php | 2 +- lang/bg/settings.php | 9 +- lang/bs/activities.php | 5 + lang/bs/entities.php | 2 +- lang/bs/settings.php | 9 +- lang/ca/activities.php | 5 + lang/ca/entities.php | 2 +- lang/ca/settings.php | 9 +- lang/cs/activities.php | 5 + lang/cs/entities.php | 26 +-- lang/cs/errors.php | 2 +- lang/cs/settings.php | 21 +-- lang/cy/activities.php | 5 + lang/cy/entities.php | 2 +- lang/cy/settings.php | 9 +- lang/da/activities.php | 5 + lang/da/entities.php | 2 +- lang/da/settings.php | 9 +- lang/de/activities.php | 5 + lang/de/entities.php | 24 +-- lang/de/errors.php | 2 +- lang/de/settings.php | 9 +- lang/de_informal/activities.php | 5 + lang/de_informal/entities.php | 24 +-- lang/de_informal/errors.php | 2 +- lang/de_informal/settings.php | 9 +- lang/el/activities.php | 5 + lang/el/entities.php | 2 +- lang/el/settings.php | 9 +- lang/es/activities.php | 5 + lang/es/entities.php | 2 +- lang/es/settings.php | 9 +- lang/es_AR/activities.php | 5 + lang/es_AR/entities.php | 2 +- lang/es_AR/settings.php | 9 +- lang/et/activities.php | 5 + lang/et/entities.php | 2 +- lang/et/settings.php | 9 +- lang/eu/activities.php | 5 + lang/eu/entities.php | 2 +- lang/eu/settings.php | 9 +- lang/fa/activities.php | 5 + lang/fa/common.php | 4 +- lang/fa/editor.php | 6 +- lang/fa/entities.php | 30 ++-- lang/fa/settings.php | 21 +-- lang/fr/activities.php | 5 + lang/fr/entities.php | 2 +- lang/fr/settings.php | 9 +- lang/he/activities.php | 5 + lang/he/entities.php | 2 +- lang/he/settings.php | 9 +- lang/hr/activities.php | 5 + lang/hr/entities.php | 2 +- lang/hr/settings.php | 9 +- lang/hu/activities.php | 5 + lang/hu/entities.php | 2 +- lang/hu/settings.php | 9 +- lang/id/activities.php | 5 + lang/id/entities.php | 2 +- lang/id/settings.php | 9 +- lang/it/activities.php | 5 + lang/it/entities.php | 2 +- lang/it/settings.php | 9 +- lang/ja/activities.php | 5 + lang/ja/entities.php | 26 +-- lang/ja/errors.php | 2 +- lang/ja/settings.php | 31 ++-- lang/ka/activities.php | 5 + lang/ka/entities.php | 2 +- lang/ka/settings.php | 9 +- lang/ko/activities.php | 5 + lang/ko/entities.php | 2 +- lang/ko/settings.php | 9 +- lang/lt/activities.php | 5 + lang/lt/entities.php | 2 +- lang/lt/settings.php | 9 +- lang/lv/activities.php | 5 + lang/lv/entities.php | 2 +- lang/lv/settings.php | 9 +- lang/nb/activities.php | 37 ++-- lang/nb/auth.php | 16 +- lang/nb/common.php | 24 +-- lang/nb/editor.php | 310 ++++++++++++++++---------------- lang/nb/entities.php | 172 +++++++++--------- lang/nb/settings.php | 9 +- lang/nl/activities.php | 5 + lang/nl/entities.php | 4 +- lang/nl/settings.php | 9 +- lang/pl/activities.php | 5 + lang/pl/entities.php | 2 +- lang/pl/settings.php | 9 +- lang/pt/activities.php | 5 + lang/pt/auth.php | 16 +- lang/pt/entities.php | 26 +-- lang/pt/errors.php | 2 +- lang/pt/settings.php | 41 ++--- lang/pt_BR/activities.php | 5 + lang/pt_BR/entities.php | 2 +- lang/pt_BR/settings.php | 9 +- lang/ro/activities.php | 5 + lang/ro/entities.php | 2 +- lang/ro/settings.php | 9 +- lang/ru/activities.php | 5 + lang/ru/entities.php | 26 +-- lang/ru/errors.php | 2 +- lang/ru/settings.php | 27 ++- lang/sk/activities.php | 5 + lang/sk/entities.php | 2 +- lang/sk/settings.php | 9 +- lang/sl/activities.php | 5 + lang/sl/entities.php | 2 +- lang/sl/settings.php | 9 +- lang/sv/activities.php | 5 + lang/sv/entities.php | 2 +- lang/sv/settings.php | 9 +- lang/tr/activities.php | 5 + lang/tr/entities.php | 2 +- lang/tr/settings.php | 9 +- lang/uk/activities.php | 5 + lang/uk/entities.php | 2 +- lang/uk/settings.php | 9 +- lang/uz/activities.php | 5 + lang/uz/entities.php | 2 +- lang/uz/settings.php | 9 +- lang/vi/activities.php | 5 + lang/vi/entities.php | 2 +- lang/vi/settings.php | 9 +- lang/zh_CN/activities.php | 5 + lang/zh_CN/common.php | 4 +- lang/zh_CN/entities.php | 26 +-- lang/zh_CN/errors.php | 2 +- lang/zh_CN/settings.php | 27 ++- lang/zh_TW/activities.php | 5 + lang/zh_TW/entities.php | 2 +- lang/zh_TW/settings.php | 9 +- 140 files changed, 830 insertions(+), 746 deletions(-) diff --git a/lang/ar/activities.php b/lang/ar/activities.php index 93f1ff196..2587b77fb 100644 --- a/lang/ar/activities.php +++ b/lang/ar/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'تم تحديث المستخدم بنجاح', 'user_delete_notification' => 'تم إزالة المستخدم بنجاح', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'تم التعليق', 'permissions_update' => 'تحديث الأذونات', diff --git a/lang/ar/entities.php b/lang/ar/entities.php index a7871840d..c9ca3fe35 100644 --- a/lang/ar/entities.php +++ b/lang/ar/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'مُحدث :timeLength', 'meta_updated_name' => 'مُحدث :timeLength بواسطة :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'اختيار الكيان', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'صور', diff --git a/lang/ar/settings.php b/lang/ar/settings.php index 775e20509..78a1c9b29 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'الأدوار', 'role_user_roles' => 'أدوار المستخدمين', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'إنشاء دور جديد', - 'role_create_success' => 'تم إنشاء الدور بنجاح', 'role_delete' => 'حذف الدور', 'role_delete_confirm' => 'سيتم حذف الدور المسمى \':roleName\'.', 'role_delete_users_assigned' => 'هذا الدور له: عدد المستخدمين المعينين له. إذا كنت ترغب في ترحيل المستخدمين من هذا الدور ، فحدد دورًا جديدًا أدناه.', 'role_delete_no_migration' => "لا تقم بترجيل المستخدمين", 'role_delete_sure' => 'تأكيد حذف الدور؟', - 'role_delete_success' => 'تم حذف الدور بنجاح', 'role_edit' => 'تعديل الدور', 'role_details' => 'تفاصيل الدور', 'role_name' => 'اسم الدور', @@ -175,7 +173,6 @@ return [ 'role_own' => 'ما يخص', 'role_controlled_by_asset' => 'يتحكم فيها الأصول التي يتم رفعها إلى', 'role_save' => 'حفظ الدور', - 'role_update_success' => 'تم تحديث الدور بنجاح', 'role_users' => 'مستخدمون داخل هذا الدور', 'role_users_none' => 'لم يتم تعيين أي مستخدمين لهذا الدور', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/bg/activities.php b/lang/bg/activities.php index 854663bff..af7497e12 100644 --- a/lang/bg/activities.php +++ b/lang/bg/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Потребителят е обновен успешно', 'user_delete_notification' => 'Потребителят е премахнат успешно', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'коментирано на', 'permissions_update' => 'обновени права', diff --git a/lang/bg/entities.php b/lang/bg/entities.php index 88252a332..33ed434f5 100644 --- a/lang/bg/entities.php +++ b/lang/bg/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Актуализирано :timeLength', 'meta_updated_name' => 'Актуализирано преди :timeLength от :user', 'meta_owned_name' => 'Притежавано от :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Избор на обект', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Изображения', diff --git a/lang/bg/settings.php b/lang/bg/settings.php index 5e99a3cc9..a8ded9825 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Роли', 'role_user_roles' => 'Потребителски роли', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Създай нова роля', - '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' => 'Име на ролята', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Собствени', 'role_controlled_by_asset' => 'Контролирани от актива, към който са качени', 'role_save' => 'Запази ролята', - 'role_update_success' => 'Ролята беше успешно актуализирана', 'role_users' => 'Потребители в тази роля', 'role_users_none' => 'В момента няма потребители, назначени за тази роля', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Уебкука', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Създай нова уебкука', 'webhooks_none_created' => 'Няма създадени уебкуки.', 'webhooks_edit' => 'Редактирай уебкука', diff --git a/lang/bs/activities.php b/lang/bs/activities.php index f2c004043..735e31480 100644 --- a/lang/bs/activities.php +++ b/lang/bs/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'je komentarisao/la na', 'permissions_update' => 'je ažurirao/la dozvole', diff --git a/lang/bs/entities.php b/lang/bs/entities.php index aa8a5bcd7..e181f19a2 100644 --- a/lang/bs/entities.php +++ b/lang/bs/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Ažurirana :timeLength', 'meta_updated_name' => 'Ažurirana :timeLength od :user', 'meta_owned_name' => 'Vlasnik je :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Odaberi entitet', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Slike', diff --git a/lang/bs/settings.php b/lang/bs/settings.php index 6f4376d42..76e689b6d 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roles', 'role_user_roles' => 'User Roles', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Create New Role', - 'role_create_success' => 'Role successfully created', 'role_delete' => 'Delete Role', 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.', 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', 'role_delete_no_migration' => "Don't migrate users", 'role_delete_sure' => 'Are you sure you want to delete this role?', - 'role_delete_success' => 'Role successfully deleted', 'role_edit' => 'Edit Role', 'role_details' => 'Role Details', 'role_name' => 'Role Name', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', 'role_save' => 'Save Role', - 'role_update_success' => 'Role successfully updated', 'role_users' => 'Users in this role', 'role_users_none' => 'No users are currently assigned to this role', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/ca/activities.php b/lang/ca/activities.php index e579dbed0..45ccb45e4 100644 --- a/lang/ca/activities.php +++ b/lang/ca/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'ha comentat a', 'permissions_update' => 'ha actualitzat els permisos', diff --git a/lang/ca/entities.php b/lang/ca/entities.php index 7f2673da9..d55260f47 100644 --- a/lang/ca/entities.php +++ b/lang/ca/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualitzat :timeLength', 'meta_updated_name' => 'Actualitzat :timeLength per :user', 'meta_owned_name' => 'Propietat de :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Selecciona una entitat', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Imatges', diff --git a/lang/ca/settings.php b/lang/ca/settings.php index 8c1f709bf..7a3a611a8 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Rols', 'role_user_roles' => 'Rols d\'usuari', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Crea un rol nou', - 'role_create_success' => 'Rol creat correctament', 'role_delete' => 'Suprimeix el rol', 'role_delete_confirm' => 'Se suprimirà el rol amb el nom \':roleName\'.', 'role_delete_users_assigned' => 'Aquest rol té :userCount usuaris assignats. Si voleu migrar els usuaris d\'aquest rol, seleccioneu un rol nou a continuació.', 'role_delete_no_migration' => "No migris els usuaris", 'role_delete_sure' => 'Segur que voleu suprimir aquest rol?', - 'role_delete_success' => 'Rol suprimit correctament', 'role_edit' => 'Edita el rol', 'role_details' => 'Detalls del rol', 'role_name' => 'Nom del rol', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Propi', 'role_controlled_by_asset' => 'Controlat pel recurs en què es pugen', 'role_save' => 'Desa el rol', - 'role_update_success' => 'Rol actualitzat correctament', 'role_users' => 'Usuaris amb aquest rol', 'role_users_none' => 'Ara mateix no hi ha cap usuari assignat a aquest rol', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/cs/activities.php b/lang/cs/activities.php index 2afc33aa6..ac57d4e72 100644 --- a/lang/cs/activities.php +++ b/lang/cs/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Uživatel byl úspěšně aktualizován', 'user_delete_notification' => 'Uživatel byl úspěšně odstraněn', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'okomentoval/a', 'permissions_update' => 'oprávnění upravena', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index 031d8de9b..713463562 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aktualizováno :timeLength', 'meta_updated_name' => 'Aktualizováno :timeLength uživatelem :user', 'meta_owned_name' => 'Vlastník :user', - 'meta_reference_page_count' => 'Odkazováno na 1 stránce|Odkazováno na :count stránky', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Výběr entity', 'entity_select_lack_permission' => 'Nemáte dostatečná oprávnění k výběru této položky', 'images' => 'Obrázky', @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'Prohledat tuto knihu', 'books_navigation' => 'Navigace knihy', 'books_sort' => 'Seřadit obsah knihy', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'Přesunout kapitoly a stránky v knize pro přeuspořádání obsahu. Mohou být přidány další knihy, které umožňují snadný přesun kapitol a stránek mezi knihami.', 'books_sort_named' => 'Seřadit knihu :bookName', 'books_sort_name' => 'Seřadit podle názvu', 'books_sort_created' => 'Seřadit podle data vytvoření', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'Kapitoly jako poslední', 'books_sort_show_other' => 'Zobrazit ostatní knihy', 'books_sort_save' => 'Uložit nové pořadí', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'Přidejte sem další knihy, abyste je zahrnuli do operace třídění, a umožněte snadnou křížovou reorganizaci.', + 'books_sort_move_up' => 'Posunout Nahoru', + 'books_sort_move_down' => 'Posunout dolů', + 'books_sort_move_prev_book' => 'Přesunout se na předchozí knihu', + 'books_sort_move_next_book' => 'Přesunout se na další knihu', + 'books_sort_move_prev_chapter' => 'Přesunout se do předchozí kapitoly', + 'books_sort_move_next_chapter' => 'Přesunout se do další kapitoly', + 'books_sort_move_book_start' => 'Přesunout se na začátek knihy', + 'books_sort_move_book_end' => 'Přesunout se na konec knihy', + 'books_sort_move_before_chapter' => 'Přesunout se před kapitolu', + 'books_sort_move_after_chapter' => 'Přesunout se za kapitolu', 'books_copy' => 'Kopírovat knihu', 'books_copy_success' => 'Kniha byla úspěšně zkopírována', diff --git a/lang/cs/errors.php b/lang/cs/errors.php index ab509c106..b3ac5595d 100644 --- a/lang/cs/errors.php +++ b/lang/cs/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Nastala chyba během nahrávání souboru', 'image_upload_type_error' => 'Typ nahrávaného obrázku je neplatný.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Data výkresu nelze načíst. Výkresový soubor již nemusí existovat nebo nemusí mít oprávnění k němu přistupovat.', // Attachments 'attachment_not_found' => 'Příloha nenalezena', diff --git a/lang/cs/settings.php b/lang/cs/settings.php index b890f11ca..11f2058f9 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -49,12 +49,12 @@ return [ 'app_disable_comments_desc' => 'Vypne komentáře napříč všemi stránkami. <br> Existující komentáře se přestanou zobrazovat.', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'color_scheme' => 'Barevné schéma aplikace', + 'color_scheme_desc' => 'Nastavte barvy pro použití v rozhraní BookStack. Barvy mohou být nastaveny samostatně pro tmavé a světlé režimy, aby se nejlépe vešly do motivu a zajistila čitelnost.', + 'ui_colors_desc' => 'Nastavte primární barvu a výchozí barvu odkazů pro BookStack. Hlavní barva se používá hlavně pro banner hlavičky, tlačítka a dekorace rozhraní. Výchozí barva odkazu se používá pro textové odkazy a akce, a to jak v psaném obsahu, tak v rozhraní Bookstack.', + 'app_color' => 'Hlavní barva', + 'link_color' => 'Výchozí barva odkazu', + 'content_colors_desc' => 'Nastaví barvy pro všechny prvky v organizační struktuře stránky. Pro lepší čitelnost doporučujeme zvolit barvy s podobným jasem, jakou mají výchozí barvy.', 'bookshelf_color' => 'Barva knihovny', 'book_color' => 'Barva knihy', 'chapter_color' => 'Barva kapitoly', @@ -138,18 +138,16 @@ return [ 'roles' => 'Role', 'role_user_roles' => 'Uživatelské role', 'roles_index_desc' => 'Role se používají ke sdružování uživatelů a k poskytování systémových oprávnění jejich členům. Pokud je uživatel členem více rolí, udělená oprávnění budou uložena a uživatel zdědí všechny schopnosti.', - 'roles_x_users_assigned' => '1 přiřazený uživatel|:count přiřazených uživatelů', - 'roles_x_permissions_provided' => '1 oprávnění|:count oprávnění', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Přiřazení uživatelé', 'roles_permissions_provided' => 'Poskytnutá oprávnění', 'role_create' => 'Vytvořit novou roli', - 'role_create_success' => 'Role byla vytvořena', 'role_delete' => 'Odstranit roli', 'role_delete_confirm' => 'Role \':roleName\' bude odstraněna.', 'role_delete_users_assigned' => 'Role je přiřazena :userCount uživatelům. Pokud jim chcete náhradou přidělit jinou roli, zvolte jednu z následujících.', 'role_delete_no_migration' => "Nepřiřazovat uživatelům náhradní roli", 'role_delete_sure' => 'Opravdu chcete tuto roli odstranit?', - 'role_delete_success' => 'Role byla odstraněna', 'role_edit' => 'Upravit roli', 'role_details' => 'Detaily role', 'role_name' => 'Název role', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Vlastní', 'role_controlled_by_asset' => 'Řídí se obsahem, do kterého jsou nahrávány', 'role_save' => 'Uložit roli', - 'role_update_success' => 'Role byla aktualizována', 'role_users' => 'Uživatelé mající tuto roli', 'role_users_none' => 'Žádný uživatel nemá tuto roli', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooky', 'webhooks_index_desc' => 'Webhooks jsou způsob, jak odeslat data na externí URL, pokud se vyskytnou určité akce a události v systému, které umožňují integraci událostí s externími platformami, jako jsou systémy zasílání zpráv nebo oznámení.', - 'webhooks_x_trigger_events' => '1 spouštěcí událost|:count spouštěcí události', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Vytvořit nový webhook', 'webhooks_none_created' => 'Žádné webhooky nebyly doposud vytvořeny.', 'webhooks_edit' => 'Upravit webhook', diff --git a/lang/cy/activities.php b/lang/cy/activities.php index e890a7853..13e8113a8 100644 --- a/lang/cy/activities.php +++ b/lang/cy/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Diweddarwyd y defnyddiwr yn llwyddiannus', 'user_delete_notification' => 'Tynnwyd y defnyddiwr yn llwyddiannus', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'gwnaeth sylwadau ar', 'permissions_update' => 'caniatadau wedi\'u diweddaru', diff --git a/lang/cy/entities.php b/lang/cy/entities.php index 8bf805774..9b02f3111 100644 --- a/lang/cy/entities.php +++ b/lang/cy/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', diff --git a/lang/cy/settings.php b/lang/cy/settings.php index 6f4376d42..76e689b6d 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roles', 'role_user_roles' => 'User Roles', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Create New Role', - 'role_create_success' => 'Role successfully created', 'role_delete' => 'Delete Role', 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.', 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', 'role_delete_no_migration' => "Don't migrate users", 'role_delete_sure' => 'Are you sure you want to delete this role?', - 'role_delete_success' => 'Role successfully deleted', 'role_edit' => 'Edit Role', 'role_details' => 'Role Details', 'role_name' => 'Role Name', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', 'role_save' => 'Save Role', - 'role_update_success' => 'Role successfully updated', 'role_users' => 'Users in this role', 'role_users_none' => 'No users are currently assigned to this role', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/da/activities.php b/lang/da/activities.php index 72ad44a79..c15486266 100644 --- a/lang/da/activities.php +++ b/lang/da/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Brugeren blev opdateret', 'user_delete_notification' => 'Brugeren blev fjernet', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'kommenterede til', 'permissions_update' => 'Tilladelser opdateret', diff --git a/lang/da/entities.php b/lang/da/entities.php index 2066a3969..c2d0ca92c 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Opdateret :timeLength', 'meta_updated_name' => 'Opdateret :timeLength af :user', 'meta_owned_name' => 'Ejet af :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Vælg emne', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Billeder', diff --git a/lang/da/settings.php b/lang/da/settings.php index 7f432ed2c..ea5533b62 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roller', 'role_user_roles' => 'Brugerroller', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Opret en ny rolle', - 'role_create_success' => 'Rollen blev oprette korrekt', 'role_delete' => 'Slet rolle', 'role_delete_confirm' => 'Dette vil slette rollen med navnet \':roleName\'.', 'role_delete_users_assigned' => 'Denne rolle er tildelt :userCount brugere. Hvis du vil rykke disse brugere fra denne rolle, kan du vælge en ny nedenunder.', 'role_delete_no_migration' => "Ryk ikke brugere", 'role_delete_sure' => 'Er du sikker på, at du vil slette denne rolle?', - 'role_delete_success' => 'Rollen blev slettet', 'role_edit' => 'Rediger rolle', 'role_details' => 'Rolledetaljer', 'role_name' => 'Rollenavn', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Eget', 'role_controlled_by_asset' => 'Styres af det medie/"asset", de uploades til', 'role_save' => 'Gem rolle', - 'role_update_success' => 'Rollen blev opdateret', 'role_users' => 'Brugere med denne rolle', 'role_users_none' => 'Ingen brugere er i øjeblikket tildelt denne rolle', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Opret ny Webhook', 'webhooks_none_created' => 'Ingen webhooks er blevet oprettet endnu.', 'webhooks_edit' => 'Rediger Webhook', diff --git a/lang/de/activities.php b/lang/de/activities.php index c00b35398..e46edc1fd 100644 --- a/lang/de/activities.php +++ b/lang/de/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Benutzer erfolgreich aktualisiert', 'user_delete_notification' => 'Benutzer erfolgreich entfernt', + // Roles + 'role_create_notification' => 'Rolle erfolgreich angelegt', + 'role_update_notification' => 'Rolle erfolgreich aktualisiert', + 'role_delete_notification' => 'Rolle erfolgreich gelöscht', + // Other 'commented_on' => 'hat einen Kommentar hinzugefügt', 'permissions_update' => 'hat die Berechtigungen aktualisiert', diff --git a/lang/de/entities.php b/lang/de/entities.php index 9229faa09..9415e9cbb 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'Dieses Buch durchsuchen', 'books_navigation' => 'Buchnavigation', 'books_sort' => 'Buchinhalte sortieren', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'Kapitel und Seiten innerhalb eines Buches verschieben, um dessen Inhalt zu reorganisieren. Andere Bücher können hinzugefügt werden, was das Verschieben von Kapiteln und Seiten zwischen Büchern erleichtert.', 'books_sort_named' => 'Buch ":bookName" sortieren', 'books_sort_name' => 'Sortieren nach Namen', 'books_sort_created' => 'Sortieren nach Erstellungsdatum', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'Kapitel zuletzt', 'books_sort_show_other' => 'Andere Bücher anzeigen', 'books_sort_save' => 'Neue Reihenfolge speichern', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'Füge hier weitere Bücher hinzu, um sie in die Sortierung einzubinden und ermögliche so eine einfache und übergreifende Reorganisation.', + 'books_sort_move_up' => 'Nach oben bewegen', + 'books_sort_move_down' => 'Nach unten bewegen', + 'books_sort_move_prev_book' => 'Zum vorherigen Buch verschieben', + 'books_sort_move_next_book' => 'Zum nächsten Buch verschieben', + 'books_sort_move_prev_chapter' => 'In das vorherige Kapitel verschieben', + 'books_sort_move_next_chapter' => 'In nächstes Kapitel verschieben', + 'books_sort_move_book_start' => 'Zum Buchbeginn verschieben', + 'books_sort_move_book_end' => 'Zum Ende des Buches verschieben', + 'books_sort_move_before_chapter' => 'Vor Kapitel verschieben', + 'books_sort_move_after_chapter' => 'Nach Kapitel verschieben', 'books_copy' => 'Buch kopieren', 'books_copy_success' => 'Das Buch wurde erfolgreich kopiert', diff --git a/lang/de/errors.php b/lang/de/errors.php index 9d588ee05..d164674ef 100644 --- a/lang/de/errors.php +++ b/lang/de/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.', 'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Zeichnungsdaten konnten nicht geladen werden. Die Zeichnungsdatei existiert möglicherweise nicht mehr oder Sie haben nicht die Berechtigung, darauf zuzugreifen.', // Attachments 'attachment_not_found' => 'Anhang konnte nicht gefunden werden.', diff --git a/lang/de/settings.php b/lang/de/settings.php index c04eaaafd..08ee8fd3a 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -139,18 +139,16 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung 'roles' => 'Rollen', 'role_user_roles' => 'Benutzer-Rollen', 'roles_index_desc' => 'Rollen werden verwendet, um Benutzer zu gruppieren System-Berechtigung für ihre Mitglieder zuzuweisen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, stapeln die gewährten Berechtigungen und der Benutzer wird alle Fähigkeiten erben.', - 'roles_x_users_assigned' => '1 Benutzer zugewiesen|:count Benutzer zugewiesen', - 'roles_x_permissions_provided' => '1 Berechtigung|:count Berechtigungen', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Zugewiesene Benutzer', 'roles_permissions_provided' => 'Genutzte Berechtigungen', 'role_create' => 'Neue Rolle anlegen', - 'role_create_success' => 'Rolle erfolgreich angelegt', 'role_delete' => 'Rolle löschen', 'role_delete_confirm' => 'Sie möchten die Rolle ":roleName" löschen.', 'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Sie können unten eine neue Rolle auswählen, die Sie diesen Benutzern zuordnen möchten.', 'role_delete_no_migration' => "Den Benutzern keine andere Rolle zuordnen", 'role_delete_sure' => 'Sind Sie sicher, dass Sie diese Rolle löschen möchten?', - 'role_delete_success' => 'Rolle erfolgreich gelöscht', 'role_edit' => 'Rolle bearbeiten', 'role_details' => 'Rollendetails', 'role_name' => 'Rollenname', @@ -176,7 +174,6 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', 'role_save' => 'Rolle speichern', - 'role_update_success' => 'Rolle erfolgreich gespeichert', 'role_users' => 'Dieser Rolle zugeordnete Benutzer', 'role_users_none' => 'Bisher sind dieser Rolle keine Benutzer zugeordnet', @@ -253,7 +250,7 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse im System auftreten, was eine ereignisbasierte Integration mit externen Plattformen wie Messaging- oder Benachrichtigungssystemen ermöglicht.', - 'webhooks_x_trigger_events' => '1 Triggerereignis|:count Triggerereignisse', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Neuen Webhook erstellen', 'webhooks_none_created' => 'Es wurden noch keine Webhooks erstellt.', 'webhooks_edit' => 'Webhook bearbeiten', diff --git a/lang/de_informal/activities.php b/lang/de_informal/activities.php index 8e3ce7515..05ce9ee3c 100644 --- a/lang/de_informal/activities.php +++ b/lang/de_informal/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Benutzer erfolgreich aktualisiert', 'user_delete_notification' => 'Benutzer erfolgreich entfernt', + // Roles + 'role_create_notification' => 'Rolle erfolgreich angelegt', + 'role_update_notification' => 'Rolle erfolgreich aktualisiert', + 'role_delete_notification' => 'Rolle erfolgreich gelöscht', + // Other 'commented_on' => 'kommentiert', 'permissions_update' => 'aktualisierte Berechtigungen', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 959e8673b..8f9ce1c15 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'Dieses Buch durchsuchen', 'books_navigation' => 'Buchnavigation', 'books_sort' => 'Buchinhalte sortieren', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'Kapitel und Seiten innerhalb eines Buches verschieben, um dessen Inhalt zu reorganisieren. Andere Bücher können hinzugefügt werden, was das Verschieben von Kapiteln und Seiten zwischen Büchern erleichtert.', 'books_sort_named' => 'Buch ":bookName" sortieren', 'books_sort_name' => 'Sortieren nach Namen', 'books_sort_created' => 'Sortieren nach Erstellungsdatum', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'Kapitel zuletzt', 'books_sort_show_other' => 'Andere Bücher anzeigen', 'books_sort_save' => 'Neue Reihenfolge speichern', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'Füge hier weitere Bücher hinzu, um sie in die Sortierung einzubinden und ermögliche so eine einfache und übergreifende Reorganisation.', + 'books_sort_move_up' => 'Nach oben bewegen', + 'books_sort_move_down' => 'Nach unten bewegen', + 'books_sort_move_prev_book' => 'Zum vorherigen Buch verschieben', + 'books_sort_move_next_book' => 'Zum nächsten Buch verschieben', + 'books_sort_move_prev_chapter' => 'In das vorherige Kapitel verschieben', + 'books_sort_move_next_chapter' => 'In nächstes Kapitel verschieben', + 'books_sort_move_book_start' => 'Zum Buchbeginn verschieben', + 'books_sort_move_book_end' => 'Zum Ende des Buches verschieben', + 'books_sort_move_before_chapter' => 'Vor Kapitel verschieben', + 'books_sort_move_after_chapter' => 'Nach Kapitel verschieben', 'books_copy' => 'Buch kopieren', 'books_copy_success' => 'Buch erfolgreich kopiert', diff --git a/lang/de_informal/errors.php b/lang/de_informal/errors.php index c18076ae6..d7222bd33 100644 --- a/lang/de_informal/errors.php +++ b/lang/de_informal/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Beim Hochladen des Bildes trat ein Fehler auf.', 'image_upload_type_error' => 'Der Bildtyp der hochgeladenen Datei ist ungültig.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Zeichnungsdaten konnten nicht geladen werden. Die Zeichnungsdatei existiert möglicherweise nicht mehr oder Sie haben nicht die Berechtigung, darauf zuzugreifen.', // Attachments 'attachment_not_found' => 'Anhang konnte nicht gefunden werden.', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index 7a51746a4..9ff7605e0 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -139,18 +139,16 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 'roles' => 'Rollen', 'role_user_roles' => 'Benutzer-Rollen', 'roles_index_desc' => 'Rollen werden verwendet, um Benutzer zu gruppieren und System-Berechtigungen für ihre Mitglieder zuzuweisen. Wenn ein Benutzer Mitglied mehrerer Rollen ist, stapeln die gewährten Berechtigungen und der Benutzer wird alle Fähigkeiten erben.', - 'roles_x_users_assigned' => '1 Benutzer zugewiesen|:count Benutzer zugewiesen', - 'roles_x_permissions_provided' => '1 Berechtigung|:count Berechtigungen', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Zugewiesene Benutzer', 'roles_permissions_provided' => 'Genutzte Berechtigungen', 'role_create' => 'Neue Rolle anlegen', - 'role_create_success' => 'Rolle erfolgreich angelegt', 'role_delete' => 'Rolle löschen', 'role_delete_confirm' => 'Dies wird 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_no_migration' => "Den Benutzern keine andere Rolle zuordnen", 'role_delete_sure' => 'Bist du sicher, dass du diese Rolle löschen möchtest?', - 'role_delete_success' => 'Rolle erfolgreich gelöscht', 'role_edit' => 'Rolle bearbeiten', 'role_details' => 'Rollendetails', 'role_name' => 'Rollenname', @@ -176,7 +174,6 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 'role_own' => 'Eigene', 'role_controlled_by_asset' => 'Berechtigungen werden vom Uploadziel bestimmt', 'role_save' => 'Rolle speichern', - 'role_update_success' => 'Rolle erfolgreich gespeichert', 'role_users' => 'Dieser Rolle zugeordnete Benutzer', 'role_users_none' => 'Bisher sind dieser Rolle keine Benutzer zugeordnet', @@ -253,7 +250,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks sind eine Möglichkeit, Daten an externe URLs zu senden, wenn bestimmte Aktionen und Ereignisse im System auftreten, was eine ereignisbasierte Integration mit externen Plattformen wie Messaging- oder Benachrichtigungssystemen ermöglicht.', - 'webhooks_x_trigger_events' => '1 Triggerereignis|:count Triggerereignisse', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Neuen Webhook erstellen', 'webhooks_none_created' => 'Es wurden noch keine Webhooks erstellt.', 'webhooks_edit' => 'Webhook bearbeiten', diff --git a/lang/el/activities.php b/lang/el/activities.php index d63de883e..0be72fa71 100644 --- a/lang/el/activities.php +++ b/lang/el/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Ο Χρήστης ενημερώθηκε με επιτυχία', 'user_delete_notification' => 'Ο Χρήστης αφαιρέθηκε επιτυχώς', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'σχολίασε', 'permissions_update' => 'ενημερωμένα δικαιώματα', diff --git a/lang/el/entities.php b/lang/el/entities.php index 55ffc5795..04cfd817f 100644 --- a/lang/el/entities.php +++ b/lang/el/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Ενημερώθηκε :timeLength', 'meta_updated_name' => 'Ενημερώθηκε :timeLength by :user', 'meta_owned_name' => 'Ανήκει στον :user', - 'meta_reference_page_count' => 'Αναφορά σε 1 σελίδα"Αναφερόμενη στο :count σελίδες', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Επιλογή Οντότητας', 'entity_select_lack_permission' => 'Δεν έχετε τα απαιτούμενα δικαιώματα για να επιλέξετε αυτό το στοιχείο', 'images' => 'Εικόνες', diff --git a/lang/el/settings.php b/lang/el/settings.php index cacf7c855..922864ea9 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Ρόλοι', 'role_user_roles' => 'Ρόλοι Χρηστών', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Εκχωρημένοι χρήστες', 'roles_permissions_provided' => 'Παρεχόμενα Δικαιώματα', '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' => 'Όνομα Ρόλου', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Τα δικά του', 'role_controlled_by_asset' => 'Ελέγχονται από το στοιχείο στο οποίο ανεβαίνουν (Ράφια, Βιβλία)', 'role_save' => 'Αποθήκευση Ρόλου', - 'role_update_success' => 'Ο Ρόλος ενημερώθηκε με επιτυχία', 'role_users' => 'Χρήστες σε αυτόν τον Ρόλο', 'role_users_none' => 'Σε κανένα χρήστη δεν έχει ανατεθεί αυτήν τη στιγμή αυτός ο ρόλος.', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Δημιουργία νέου Webhook', 'webhooks_none_created' => 'Δεν έχουν δημιουργηθεί ακόμη webhook.', 'webhooks_edit' => 'Επεξεργασία Webhook', diff --git a/lang/es/activities.php b/lang/es/activities.php index 45dcca97e..51fa00da8 100644 --- a/lang/es/activities.php +++ b/lang/es/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Usuario actualizado correctamente', 'user_delete_notification' => 'Usuario eliminado correctamente', + // Roles + 'role_create_notification' => 'Rol creado correctamente', + 'role_update_notification' => 'Rol actualizado correctamente', + 'role_delete_notification' => 'Rol eliminado correctamente', + // Other 'commented_on' => 'comentada el', 'permissions_update' => 'permisos actualizados', diff --git a/lang/es/entities.php b/lang/es/entities.php index 1489d1867..8251bf345 100644 --- a/lang/es/entities.php +++ b/lang/es/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualizado :timeLength', 'meta_updated_name' => 'Actualizado :timeLength por :user', 'meta_owned_name' => 'Propiedad de :user', - 'meta_reference_page_count' => 'Referenciado en 1 página|Referenciado en :count páginas', + 'meta_reference_page_count' => 'Referido en :count página | Referido en :count paginas', 'entity_select' => 'Seleccione entidad', 'entity_select_lack_permission' => 'No tiene los permisos necesarios para seleccionar este elemento', 'images' => 'Imágenes', diff --git a/lang/es/settings.php b/lang/es/settings.php index b73687147..d07d41dc1 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roles', 'role_user_roles' => 'Roles de usuario', 'roles_index_desc' => 'Los roles se utilizan para agrupar usuarios y proporcionar permisos del sistema a sus miembros. Cuando un usuario es miembro de múltiples roles los privilegios otorgados se acumularán y el usuario heredará todas las habilidades.', - 'roles_x_users_assigned' => '1 usuario asignado|:count usuarios asignados', - 'roles_x_permissions_provided' => '1 permiso|:count permisos', + 'roles_x_users_assigned' => ':count usuario asignado|:count usuarios asignados', + 'roles_x_permissions_provided' => ':count permiso |:count permisos', 'roles_assigned_users' => 'Usuarios asignados', 'roles_permissions_provided' => 'Permisos proporcionados', 'role_create' => 'Crear nuevo rol', - 'role_create_success' => 'Rol creado satisfactoriamente', 'role_delete' => 'Borrar rol', 'role_delete_confirm' => 'Se borrará el rol con nombre \':roleName\'.', 'role_delete_users_assigned' => 'Este rol tiene :userCount usuarios asignados. Si quisiera migrar los usuarios de este rol, seleccione un nuevo rol a continuación.', 'role_delete_no_migration' => "No migrar usuarios", 'role_delete_sure' => 'Está seguro que desea borrar este rol?', - 'role_delete_success' => 'Rol borrado satisfactoriamente', 'role_edit' => 'Editar rol', 'role_details' => 'Detalles de rol', 'role_name' => 'Nombre de rol', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Propio', 'role_controlled_by_asset' => 'Controlado por el contenido al que ha sido subido', 'role_save' => 'Guardar rol', - 'role_update_success' => 'Rol actualizado éxitosamente', 'role_users' => 'Usuarios en este rol', 'role_users_none' => 'No hay usuarios asignados a este rol', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Los Webhooks son una forma de enviar datos a URLs externas cuando ciertas acciones y eventos ocurren dentro del sistema, lo que permite la integración basada en eventos con plataformas externas como mensajería o sistemas de notificación.', - 'webhooks_x_trigger_events' => '1 evento|:count eventos', + 'webhooks_x_trigger_events' => ':count disparador de eventos|:count disparadores de eventos', 'webhooks_create' => 'Crear webhook', 'webhooks_none_created' => 'No hay webhooks creados.', 'webhooks_edit' => 'Editar webhook', diff --git a/lang/es_AR/activities.php b/lang/es_AR/activities.php index 34f0675ea..d6ec1a51f 100644 --- a/lang/es_AR/activities.php +++ b/lang/es_AR/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Usuario actualizado correctamente', 'user_delete_notification' => 'El usuario fue eliminado correctamente', + // Roles + 'role_create_notification' => 'Rol creado correctamente', + 'role_update_notification' => 'Rol actualizado correctamente', + 'role_delete_notification' => 'Rol eliminado correctamente', + // Other 'commented_on' => 'comentado', 'permissions_update' => 'permisos actualizados', diff --git a/lang/es_AR/entities.php b/lang/es_AR/entities.php index de8dd2716..a0a9c2d54 100644 --- a/lang/es_AR/entities.php +++ b/lang/es_AR/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualizado el :timeLength', 'meta_updated_name' => 'Actualizado el :timeLength por :user', 'meta_owned_name' => 'Propiedad de :user', - 'meta_reference_page_count' => 'Referenciado en una página|Referenciado en :count páginas', + 'meta_reference_page_count' => 'Referido en :count página | Referido en :count paginas', 'entity_select' => 'Seleccione entidad', 'entity_select_lack_permission' => 'No tiene los permisos necesarios para seleccionar este elemento', 'images' => 'Imágenes', diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index 5ffbd3146..80acc34ab 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roles', 'role_user_roles' => 'Roles de usuario', 'roles_index_desc' => 'Los roles se utilizan para agrupar usuarios y proporcionar permisos del sistema a sus miembros. Cuando un usuario es miembro de múltiples roles los privilegios otorgados se acumularán y el usuario heredará todas las habilidades.', - 'roles_x_users_assigned' => '1 usuario assigned|:count usuarios asignados', - 'roles_x_permissions_provided' => '1 permission|:count permisos', + 'roles_x_users_assigned' => ':count usuario asignado|:count usuarios asignados', + 'roles_x_permissions_provided' => ':count permiso |:count permisos', 'roles_assigned_users' => 'Usuarios Asignados', 'roles_permissions_provided' => 'Permisos Proporcionados', 'role_create' => 'Crear nuevo rol', - 'role_create_success' => 'Rol creado satisfactoriamente', 'role_delete' => 'Borrar rol', 'role_delete_confirm' => 'Se borrará el rol con nombre \':roleName\'.', 'role_delete_users_assigned' => 'Este rol tiene :userCount usuarios asignados. Si ud. quisiera migrar los usuarios de este rol, seleccione un nuevo rol a continuación.', 'role_delete_no_migration' => "No migrar usuarios", 'role_delete_sure' => '¿Está seguro que desea borrar este rol?', - 'role_delete_success' => 'Rol borrado satisfactoriamente', 'role_edit' => 'Editar rol', 'role_details' => 'Detalles de rol', 'role_name' => 'Nombre de rol', @@ -176,7 +174,6 @@ return [ 'role_own' => 'Propio', 'role_controlled_by_asset' => 'Controlado por el activo al que ha sido subido', 'role_save' => 'Guardar rol', - 'role_update_success' => 'Rol actualizado exitosamente', 'role_users' => 'Usuarios en este rol', 'role_users_none' => 'No hay usuarios asignados a este rol', @@ -253,7 +250,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Los Webhooks son una forma de enviar datos a URLs externas cuando ciertas acciones y eventos ocurren dentro del sistema, lo que permite la integración basada en eventos con plataformas externas como mensajería o sistemas de notificación.', - 'webhooks_x_trigger_events' => '1 trigger event|:count evento desencadenante', + 'webhooks_x_trigger_events' => ':count disparador de eventos|:count disparadores de eventos', 'webhooks_create' => 'Crear nuevo Webhook', 'webhooks_none_created' => 'No hay webhooks creados.', 'webhooks_edit' => 'Editar Webhook', diff --git a/lang/et/activities.php b/lang/et/activities.php index 26e4e5ac0..edec4e03f 100644 --- a/lang/et/activities.php +++ b/lang/et/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Kasutaja on muudetud', 'user_delete_notification' => 'Kasutaja on kustutatud', + // Roles + 'role_create_notification' => 'Roll on lisatud', + 'role_update_notification' => 'Roll on muudetud', + 'role_delete_notification' => 'Roll on kustutatud', + // Other 'commented_on' => 'kommenteeris lehte', 'permissions_update' => 'muutis õiguseid', diff --git a/lang/et/entities.php b/lang/et/entities.php index 23bf0b268..9f356f95f 100644 --- a/lang/et/entities.php +++ b/lang/et/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Muudetud :timeLength', 'meta_updated_name' => 'Muudetud :timeLength kasutaja :user poolt', 'meta_owned_name' => 'Kuulub kasutajale :user', - 'meta_reference_page_count' => 'Viidatud 1 lehel|Viidatud :count lehel', + 'meta_reference_page_count' => 'Viidatud :count lehel|Viidatud :count lehel', 'entity_select' => 'Objekti valik', 'entity_select_lack_permission' => 'Sul pole õiguseid selle objekti valimiseks', 'images' => 'Pildid', diff --git a/lang/et/settings.php b/lang/et/settings.php index 718a8f847..0779f26e8 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Rollid', 'role_user_roles' => 'Kasutaja rollid', 'roles_index_desc' => 'Rolle saab kasutada kasutajate grupeerimiseks ja liikmetele süsteemsete õiguste andmiseks. Kui kasutaja on mitme rolli liige, siis õigused kombineeritakse ning kasutaja saab kõik õigused.', - 'roles_x_users_assigned' => '1 kasutaja|:count kasutajat', - 'roles_x_permissions_provided' => '1 õigus|:count õigust', + 'roles_x_users_assigned' => ':count kasutaja|:count kasutajat', + 'roles_x_permissions_provided' => ':count õigus|:count õigust', 'roles_assigned_users' => 'Määratud kasutajad', 'roles_permissions_provided' => 'Antud õigused', 'role_create' => 'Lisa uus roll', - 'role_create_success' => 'Roll on lisatud', 'role_delete' => 'Kustuta roll', 'role_delete_confirm' => 'See kustutab rolli nimega \':roleName\'.', 'role_delete_users_assigned' => 'Selle rolliga on seotud :userCount kasutajat. Kui soovid neile selle asemel uue rolli määrata, siis vali see allpool.', 'role_delete_no_migration' => "Ära määra uut rolli", 'role_delete_sure' => 'Kas oled kindel, et soovid selle rolli kustutada?', - 'role_delete_success' => 'Roll on kustutatud', 'role_edit' => 'Muuda rolli', 'role_details' => 'Rolli detailid', 'role_name' => 'Rolli nimi', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Enda omad', 'role_controlled_by_asset' => 'Õigused määratud seotud objekti kaudu', 'role_save' => 'Salvesta roll', - 'role_update_success' => 'Roll on muudetud', 'role_users' => 'Selle rolliga kasutajad', 'role_users_none' => 'Seda rolli ei ole hetkel ühelgi kasutajal', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Veebihaagid', 'webhooks_index_desc' => 'Veebihaakide abil saab teatud süsteemis toimunud tegevuste ja sündmuste puhul saata andmeid välistele URL-idele, mis võimaldab integreerida väliseid platvorme, nagu sõnumi- või teavitussüsteemid.', - 'webhooks_x_trigger_events' => '1 sündmus|:count sündmust', + 'webhooks_x_trigger_events' => ':count sündmus|:count sündmust', 'webhooks_create' => 'Lisa uus veebihaak', 'webhooks_none_created' => 'Ühtegi veebihaaki pole lisatud.', 'webhooks_edit' => 'Muuda veebihaaki', diff --git a/lang/eu/activities.php b/lang/eu/activities.php index 9dadac059..b3884187b 100644 --- a/lang/eu/activities.php +++ b/lang/eu/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Erabiltzailea egoki eguneratua', 'user_delete_notification' => 'Erabiltzailea egoki ezabatua', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'iruzkinak', 'permissions_update' => 'eguneratu baimenak', diff --git a/lang/eu/entities.php b/lang/eu/entities.php index 3df02a281..d8d98d917 100644 --- a/lang/eu/entities.php +++ b/lang/eu/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aldatua :timeLength', 'meta_updated_name' => ':timeLength aldatuta. Erabiltzailea :user', 'meta_owned_name' => ':user da jabea', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Aukeratutako entitatea', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Irudiak', diff --git a/lang/eu/settings.php b/lang/eu/settings.php index b47798e30..cf471947d 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Rolak', 'role_user_roles' => 'Erabiltzailearen rola', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Rol berria sortu', - 'role_create_success' => 'Rola ondo sortu da', 'role_delete' => 'Ezabatu Rol-a', 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.', 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', 'role_delete_no_migration' => "Ez migratu erabiltzaileak", 'role_delete_sure' => 'Ziur zaude rol hau ezabatu nahi duzula?', - 'role_delete_success' => 'Rola ezabatua', 'role_edit' => 'Editatu rola', 'role_details' => 'Ireki xehetasunak', 'role_name' => 'Rol izena', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Norberarenak', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', 'role_save' => 'Gorde rol-a', - 'role_update_success' => 'Rola ondo eguneratu da', 'role_users' => 'Rol honetako erabiltzaileak', 'role_users_none' => 'No users are currently assigned to this role', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/fa/activities.php b/lang/fa/activities.php index 122efaca6..86daadcf3 100644 --- a/lang/fa/activities.php +++ b/lang/fa/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'کاربر با موفقیت به روز شد', 'user_delete_notification' => 'کاربر با موفقیت حذف شد', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'ثبت دیدگاه', 'permissions_update' => 'به روزرسانی مجوزها', diff --git a/lang/fa/common.php b/lang/fa/common.php index 7aec33d8b..bd6b95857 100644 --- a/lang/fa/common.php +++ b/lang/fa/common.php @@ -25,7 +25,7 @@ return [ 'actions' => 'عملیات', 'view' => 'نمایش', 'view_all' => 'نمایش همه', - 'new' => 'New', + 'new' => 'جدید', 'create' => 'ایجاد', 'update' => 'بهروز رسانی', 'edit' => 'ويرايش', @@ -81,7 +81,7 @@ return [ 'none' => 'هیچکدام', // Header - 'homepage' => 'Homepage', + 'homepage' => 'صفحه اصلی', 'header_menu_expand' => 'گسترش منو', 'profile_menu' => 'منو پروفایل', 'view_profile' => 'مشاهده پروفایل', diff --git a/lang/fa/editor.php b/lang/fa/editor.php index 0e358c5e6..0109e6cad 100644 --- a/lang/fa/editor.php +++ b/lang/fa/editor.php @@ -144,11 +144,11 @@ return [ 'url' => 'آدرس', 'text_to_display' => 'متن جهت نمایش', 'title' => 'عنوان', - 'open_link' => 'Open link', - 'open_link_in' => 'Open link in...', + 'open_link' => 'بازکردن لینک', + 'open_link_in' => 'باز کردن لینک در ...', 'open_link_current' => 'پنجره کنونی', 'open_link_new' => 'پنجره جدید', - 'remove_link' => 'Remove link', + 'remove_link' => 'حذف لینک', 'insert_collapsible' => 'درج بلوک جمع شونده', 'collapsible_unwrap' => 'باز کردن', 'edit_label' => 'ویرایش برچسب', diff --git a/lang/fa/entities.php b/lang/fa/entities.php index a8ac2ef89..bc3961d4b 100644 --- a/lang/fa/entities.php +++ b/lang/fa/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'به روزرسانی شده :timeLength', 'meta_updated_name' => 'به روزرسانی شده :timeLength توسط :user', 'meta_owned_name' => 'توسط :user ایجاد شدهاست', - 'meta_reference_page_count' => 'در 1 صفحه به آن ارجاع داده شده|در :count صفحه به آن ارجاع داده شده', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'انتخاب موجودیت', 'entity_select_lack_permission' => 'شما مجوزهای لازم برای انتخاب این مورد را ندارید', 'images' => 'عکسها', @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'این کتاب را جستجو کنید', 'books_navigation' => 'ناوبری کتاب', 'books_sort' => 'مرتب سازی مطالب کتاب', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'فصلها و صفحات را در یک کتاب جابهجا کنید تا محتوای آن را مجددا سازماندهی کنید. کتابهای دیگری را میتوان اضافه کرد که امکان جابهجایی آسان فصلها و صفحات را بین کتابها فراهم میکند.', 'books_sort_named' => 'مرتبسازی کتاب:bookName', 'books_sort_name' => 'مرتب سازی بر اساس نام', 'books_sort_created' => 'مرتب سازی بر اساس تاریخ ایجاد', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'فصل آخر', 'books_sort_show_other' => 'نمایش کتاب های دیگر', 'books_sort_save' => 'ذخیره سفارش جدید', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'کتابهای دیگری را در اینجا اضافه کنید تا آنها را در عملیات مرتبسازی بگنجانید و به آسانی کتابها را مجددا سازماندهی کنید.', + 'books_sort_move_up' => 'انتقال به بالا', + 'books_sort_move_down' => 'انتقال به پایین', + 'books_sort_move_prev_book' => 'انتقال به کتاب قبلی', + 'books_sort_move_next_book' => 'انتقال به کتاب بعدی', + 'books_sort_move_prev_chapter' => 'انتقال به داخل فصل قبلی', + 'books_sort_move_next_chapter' => 'انتقال به داخل فصل بعدی', + 'books_sort_move_book_start' => 'انتقال به ابتدای کتاب', + 'books_sort_move_book_end' => 'انتقال به انتهای کتاب', + 'books_sort_move_before_chapter' => 'انتقال به قبل فصل', + 'books_sort_move_after_chapter' => 'انتقال به بعد فصل', 'books_copy' => 'کپی کتاب', 'books_copy_success' => 'کتاب با موفقیت کپی شد', @@ -248,14 +248,14 @@ return [ 'pages_permissions_success' => 'مجوزهای صفحه به روز شد', 'pages_revision' => 'تجدید نظر', 'pages_revisions' => 'ویرایش های صفحه', - 'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.', + 'pages_revisions_desc' => 'لیست زیر تمامی ویرایشهای قبلی این صفحه است. در صورت وجود مجوز دسترسی، میتوانید نسخههای قدیمی صفحه را مشاهده، مقایسه و بازیابی کنید. تاریخچه کامل صفحه ممکن است به طور کامل در اینجا منعکس نشود زیرا بسته به پیکربندی سیستم، ویرایش های قدیمی می توانند به طور خودکار حذف شوند.', 'pages_revisions_named' => 'بازبینی صفحه برای :pageName', 'pages_revision_named' => 'ویرایش صفحه برای :pageName', 'pages_revision_restored_from' => 'بازیابی شده از #:id; :summary', 'pages_revisions_created_by' => 'ایجاد شده توسط', 'pages_revisions_date' => 'تاریخ تجدید نظر', 'pages_revisions_number' => '#', - 'pages_revisions_sort_number' => 'Revision Number', + 'pages_revisions_sort_number' => 'شماره ویرایش', 'pages_revisions_numbered' => 'تجدید نظر #:id', 'pages_revisions_numbered_changes' => 'بازبینی #:id تغییرات', 'pages_revisions_editor' => 'نوع ویرایشگر', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index 0d947fe7e..ac235fa02 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -33,9 +33,9 @@ return [ 'app_custom_html_desc' => 'هر محتوای اضافه شده در اینجا در پایین بخش <head> هر صفحه درج می شود. این برای تغییر سبک ها یا اضافه کردن کد تجزیه و تحلیل مفید است.', 'app_custom_html_disabled_notice' => 'محتوای سر HTML سفارشی در این صفحه تنظیمات غیرفعال است تا اطمینان حاصل شود که هر گونه تغییر شکسته می تواند برگردانده شود.', 'app_logo' => 'لوگوی برنامه', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_logo_desc' => 'این مورد در نوار هدر برنامه و در میان سایر قسمتها استفاده میشود. این تصویر باید 86 پیکسل ارتفاع داشته باشد. تصاویر بزرگ، کوچک نمایش داده میشوند.', + 'app_icon' => 'آیکون برنامه', + 'app_icon_desc' => 'این آیکون برای تبهای مرورگر و نمادهای میانبر استفاده میشود. این مورد باید یک تصویر PNG مربعی ببه طول 256 پیکسل باشد.', 'app_homepage' => 'صفحه اصلی برنامه', 'app_homepage_desc' => 'به جای نمای پیشفرض، یک نمای را برای نمایش در صفحه اصلی انتخاب کنید. مجوزهای صفحه برای صفحات انتخابی نادیده گرفته می شود.', 'app_homepage_select' => 'یک صفحه را انتخاب کنید', @@ -49,7 +49,7 @@ return [ 'app_disable_comments_desc' => 'نظرات را در تمام صفحات برنامه غیرفعال می کند. <br> نظرات موجود نشان داده نمی شوند.', // Color settings - 'color_scheme' => 'Application Color Scheme', + 'color_scheme' => 'ترکیب رنگی برنامه', 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', 'app_color' => 'Primary Color', @@ -137,19 +137,17 @@ return [ // Role Settings 'roles' => 'نقش ها', 'role_user_roles' => 'نقش های کاربر', - 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', - 'roles_assigned_users' => 'Assigned Users', + 'roles_index_desc' => 'نقشها برای گروهبندی کاربران و ارائه مجوز سیستم به اعضای آنها استفاده میشوند. هنگامی که یک کاربر عضو چندین نقش باشد، امتیازات اعطا شده روی هم قرار میگیرند و کاربر تمام مجوزها را به ارث میبرد.', + 'roles_x_users_assigned' => ':count کاربر اختصاص داده شده|:count کاربر اختصاص داده شده', + 'roles_x_permissions_provided' => ':count مجوز|:count مجوز', + 'roles_assigned_users' => 'کاربران اختصاص داده شده', 'roles_permissions_provided' => 'Provided Permissions', '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' => 'اسم نقش', @@ -175,7 +173,6 @@ return [ 'role_own' => 'صاحب', 'role_controlled_by_asset' => 'توسط دارایی که در آن آپلود می شود کنترل می شود', 'role_save' => 'ذخیره نقش', - 'role_update_success' => 'نقش با موفقیت به روز شد', 'role_users' => 'کاربران در این نقش', 'role_users_none' => 'در حال حاضر هیچ کاربری به این نقش اختصاص داده نشده است', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'وبهوکها', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'ایجاد وب هوک جدید', 'webhooks_none_created' => 'هنوز هیچ وب هوکی ایجاد نشده است.', 'webhooks_edit' => 'ویرایش وب هوک', diff --git a/lang/fr/activities.php b/lang/fr/activities.php index 9a951968b..6abb57643 100644 --- a/lang/fr/activities.php +++ b/lang/fr/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Utilisateur mis à jour avec succès', 'user_delete_notification' => 'Utilisateur supprimé avec succès', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'a commenté', 'permissions_update' => 'a mis à jour les autorisations sur', diff --git a/lang/fr/entities.php b/lang/fr/entities.php index 504605e10..80536626f 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Mis à jour :timeLength', 'meta_updated_name' => 'Mis à jour :timeLength par :user', 'meta_owned_name' => 'Appartient à :user', - 'meta_reference_page_count' => 'Référencé sur 1 page|Référencé sur :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Sélectionner l\'entité', 'entity_select_lack_permission' => 'Vous n\'avez pas les permissions requises pour sélectionner cet élément', 'images' => 'Images', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 6971a016a..d058e829d 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Rôles', 'role_user_roles' => 'Rôles des utilisateurs', 'roles_index_desc' => 'Les rôles sont utilisés pour regrouper les utilisateurs et fournir une autorisation système à leurs membres. Lorsqu\'un utilisateur est membre de plusieurs rôles, les privilèges accordés se cumulent et l\'utilisateur hérite de tous les droits d\'accès.', - 'roles_x_users_assigned' => '1 utilisateur affecté|:count utilisateurs affectés', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Utilisateurs assignés', 'roles_permissions_provided' => 'Permissions accordées', 'role_create' => 'Créer un nouveau rôle', - 'role_create_success' => 'Rôle créé avec succès', 'role_delete' => 'Supprimer le rôle', 'role_delete_confirm' => 'Ceci va supprimer le rôle \':roleName\'.', 'role_delete_users_assigned' => 'Ce rôle a :userCount utilisateurs assignés. Vous pouvez choisir un rôle de remplacement pour ces utilisateurs.', 'role_delete_no_migration' => "Ne pas assigner de nouveau rôle", 'role_delete_sure' => 'Êtes-vous sûr de vouloir supprimer ce rôle ?', - 'role_delete_success' => 'Le rôle a été supprimé avec succès', 'role_edit' => 'Modifier le rôle', 'role_details' => 'Détails du rôle', 'role_name' => 'Nom du rôle', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Propres', 'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés', 'role_save' => 'Enregistrer le rôle', - 'role_update_success' => 'Rôle mis à jour avec succès', 'role_users' => 'Utilisateurs ayant ce rôle', 'role_users_none' => 'Aucun utilisateur avec ce rôle actuellement', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Les Webhooks sont un moyen d\'envoyer des données à des URL externes lorsque certaines actions et événements se produisent dans le système, ce qui permet une intégration basée sur des événements avec des plates-formes externes telles que les systèmes de messagerie ou de notification.', - 'webhooks_x_trigger_events' => '1 événement déclencheur|:count événements déclencheurs', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Créer un nouveau Webhook', 'webhooks_none_created' => 'Aucun webhook n\'a encore été créé.', 'webhooks_edit' => 'Éditer le Webhook', diff --git a/lang/he/activities.php b/lang/he/activities.php index 45f65aec5..e1e4d423e 100644 --- a/lang/he/activities.php +++ b/lang/he/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'commented on', 'permissions_update' => 'updated permissions', diff --git a/lang/he/entities.php b/lang/he/entities.php index daef1adb4..3c3b9b9c7 100644 --- a/lang/he/entities.php +++ b/lang/he/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'עודכן :timeLength', 'meta_updated_name' => 'עודכן :timeLength על ידי :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'בחר יישות', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'תמונות', diff --git a/lang/he/settings.php b/lang/he/settings.php index ade3f20e0..d14aa7d66 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'תפקידים', 'role_user_roles' => 'תפקידי משתמשים', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'צור תפקיד משתמש חדש', - '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' => 'שם התפקיד', @@ -175,7 +173,6 @@ return [ 'role_own' => 'שלי', 'role_controlled_by_asset' => 'נשלטים על ידי המשאב אליו הועלו', 'role_save' => 'שמור תפקיד', - 'role_update_success' => 'התפקיד עודכן בהצלחה', 'role_users' => 'משתמשים משוייכים לתפקיד זה', 'role_users_none' => 'אין משתמשים המשוייכים לתפקיד זה', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/hr/activities.php b/lang/hr/activities.php index 3af21813e..e00cb11a9 100644 --- a/lang/hr/activities.php +++ b/lang/hr/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Korisnik je uspješno ažuriran', 'user_delete_notification' => 'Korisnik je uspješno uklonjen', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'komentirano', 'permissions_update' => 'ažurirana dopuštenja', diff --git a/lang/hr/entities.php b/lang/hr/entities.php index 963e087fa..9b861e5f3 100644 --- a/lang/hr/entities.php +++ b/lang/hr/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Ažurirano :timeLength', 'meta_updated_name' => 'Ažurirano :timeLength od :user', 'meta_owned_name' => 'Vlasništvo :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Odaberi subjekt', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Slike', diff --git a/lang/hr/settings.php b/lang/hr/settings.php index 8a9524ed2..54e0251be 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Uloge', 'role_user_roles' => 'Uloge korisnika', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Stvori novu ulogu', - 'role_create_success' => 'Uloga uspješno stvorena', 'role_delete' => 'Izbriši ulogu', 'role_delete_confirm' => 'Ovo će izbrisati ulogu povezanu s imenom \':roleName\'.', 'role_delete_users_assigned' => 'Ova uloga dodijeljena je :userCount. Ako želite premjestiti korisnike iz ove uloge odaberite novu ulogu u nastavku.', 'role_delete_no_migration' => "Don't migrate users", 'role_delete_sure' => 'Jeste li sigurni da želite obrisati ovu ulogu?', - 'role_delete_success' => 'Uloga je uspješno izbrisana', 'role_edit' => 'Uredi ulogu', 'role_details' => 'Detalji uloge', 'role_name' => 'Ime uloge', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Vlastito', 'role_controlled_by_asset' => 'Kontrolirano od strane vlasnika', 'role_save' => 'Spremi ulogu', - 'role_update_success' => 'Uloga uspješno ažurirana', 'role_users' => 'Korisnici u ovoj ulozi', 'role_users_none' => 'Trenutno nijedan korisnik nije u ovoj ulozi', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/hu/activities.php b/lang/hu/activities.php index 2968fb92f..9fe95db99 100644 --- a/lang/hu/activities.php +++ b/lang/hu/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Felhasználó sikeresen frissítve', 'user_delete_notification' => 'Felhasználó sikeresen eltávolítva', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'megjegyzést fűzött hozzá:', 'permissions_update' => 'updated permissions', diff --git a/lang/hu/entities.php b/lang/hu/entities.php index a0442e28e..149d4a7d1 100644 --- a/lang/hu/entities.php +++ b/lang/hu/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Frissítve :timeLength', 'meta_updated_name' => ':user frissítette :timeLength', 'meta_owned_name' => ':user tulajdona', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Entitás kiválasztása', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Képek', diff --git a/lang/hu/settings.php b/lang/hu/settings.php index 8674d98a6..8533becbc 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Szerepkörök', 'role_user_roles' => 'Felhasználói szerepkörök', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Hozzárendelt felhasználók', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Új szerepkör létrehozása', - 'role_create_success' => 'Szerepkör sikeresen létrehozva', 'role_delete' => 'Szerepkör törlése', 'role_delete_confirm' => 'Ez törölni fogja \':roleName\' szerepkört.', 'role_delete_users_assigned' => 'Ehhez a szerepkörhöz :userCount felhasználó van hozzárendelve. Ha a felhasználókat át kell helyezni ebből a szerepkörből, akkor ki kell választani egy új szerepkört.', 'role_delete_no_migration' => "Nincs felhasználó áthelyezés", 'role_delete_sure' => 'Biztosan törölhető ez a szerepkör?', - 'role_delete_success' => 'Szerepkör sikeresen törölve', 'role_edit' => 'Szerepkör szerkesztése', 'role_details' => 'Szerepkör részletei', 'role_name' => 'Szerepkör neve', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Saját', 'role_controlled_by_asset' => 'Az általuk feltöltött eszköz által ellenőrzött', 'role_save' => 'Szerepkör mentése', - 'role_update_success' => 'Szerepkör sikeresen frissítve', 'role_users' => 'Felhasználók ebben a szerepkörben', 'role_users_none' => 'Jelenleg nincsenek felhasználók hozzárendelve ehhez a szerepkörhöz', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhook-ok', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/id/activities.php b/lang/id/activities.php index 253824eb4..3af0e5afc 100644 --- a/lang/id/activities.php +++ b/lang/id/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'berkomentar pada', 'permissions_update' => 'izin diperbarui', diff --git a/lang/id/entities.php b/lang/id/entities.php index 71229e620..9353c3af9 100644 --- a/lang/id/entities.php +++ b/lang/id/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Diperbaharui :timeLength', 'meta_updated_name' => 'Diperbaharui :timeLength oleh :user', 'meta_owned_name' => 'Dimiliki oleh :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Pilihan Entitas', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Gambar-gambar', diff --git a/lang/id/settings.php b/lang/id/settings.php index 1e3b51b18..5b28a184e 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Peran', 'role_user_roles' => 'Peran Pengguna', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Buat Peran Baru', - 'role_create_success' => 'Peran berhasil dibuat', 'role_delete' => 'Hapus Peran', 'role_delete_confirm' => 'Ini akan menghapus peran dengan nama \':roleName\'.', 'role_delete_users_assigned' => 'Peran ini memiliki :userCount pengguna yang ditugaskan padanya. Jika Anda ingin memindahkan pengguna dari peran ini pilih peran baru di bawah.', 'role_delete_no_migration' => "Jangan migrasikan pengguna", 'role_delete_sure' => 'Anda yakin ingin menghapus peran ini?', - 'role_delete_success' => 'Peran berhasil dihapus', 'role_edit' => 'Edit Peran', 'role_details' => 'Detail Peran', 'role_name' => 'Nama peran', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Sendiri', 'role_controlled_by_asset' => 'Dikendalikan oleh aset tempat mereka diunggah', 'role_save' => 'Simpan Peran', - 'role_update_success' => 'Peran berhasil diperbarui', 'role_users' => 'Peran berhasil diperbarui', 'role_users_none' => 'Saat ini tidak ada pengguna yang ditugaskan untuk peran ini', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/it/activities.php b/lang/it/activities.php index 975b747b9..adb512f3b 100644 --- a/lang/it/activities.php +++ b/lang/it/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Utente aggiornato con successo', 'user_delete_notification' => 'Utente rimosso con successo', + // Roles + 'role_create_notification' => 'Ruolo creato con successo', + 'role_update_notification' => 'Ruolo aggiornato con successo', + 'role_delete_notification' => 'Ruolo eliminato con successo', + // Other 'commented_on' => 'ha commentato in', 'permissions_update' => 'autorizzazioni aggiornate', diff --git a/lang/it/entities.php b/lang/it/entities.php index 32e3b7b16..6273e8a0f 100644 --- a/lang/it/entities.php +++ b/lang/it/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aggiornato :timeLength', 'meta_updated_name' => 'Aggiornato :timeLength da :user', 'meta_owned_name' => 'Creati da :user', - 'meta_reference_page_count' => 'Referenziato su 1 pagina|Referenziato su :count pagine', + 'meta_reference_page_count' => 'Referenziato su :count page|Referenziato su :count pages', 'entity_select' => 'Selezione Entità', 'entity_select_lack_permission' => 'Non hai i permessi necessari per selezionare questo elemento', 'images' => 'Immagini', diff --git a/lang/it/settings.php b/lang/it/settings.php index 27a0db8e0..2c2803957 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Ruoli', 'role_user_roles' => 'Ruoli Utente', 'roles_index_desc' => 'I ruoli sono utilizzati per raggruppare gli utenti e fornire ai loro membri i permessi di sistema. Quando un utente è membro di più ruoli, i privilegi concessi si sovrappongono e l\'utente eredita tutte le abilità.', - 'roles_x_users_assigned' => '1 utente assegnato|:count utenti assegnati', - 'roles_x_permissions_provided' => '1 permesso|:count permessi', + 'roles_x_users_assigned' => ':count utente assegnato|:count utenti assegnati', + 'roles_x_permissions_provided' => ':count permesso|:count permessi', 'roles_assigned_users' => 'Utenti Assegnati', 'roles_permissions_provided' => 'Autorizzazioni fornite', 'role_create' => 'Crea Nuovo Ruolo', - 'role_create_success' => 'Ruolo creato correttamente', 'role_delete' => 'Elimina Ruolo', 'role_delete_confirm' => 'Questo eliminerà il ruolo con il nome \':roleName\'.', 'role_delete_users_assigned' => 'Questo ruolo ha :userCount utenti assegnati. Se vuoi migrare gli utenti da questo ruolo selezionane uno nuovo sotto.', 'role_delete_no_migration' => "Non migrare gli utenti", 'role_delete_sure' => 'Sei sicuro di voler eliminare questo ruolo?', - 'role_delete_success' => 'Ruolo eliminato correttamente', 'role_edit' => 'Modifica Ruolo', 'role_details' => 'Dettagli Ruolo', 'role_name' => 'Nome Ruolo', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Propri', 'role_controlled_by_asset' => 'Controllato dall\'entità in cui sono caricati', 'role_save' => 'Salva Ruolo', - 'role_update_success' => 'Ruolo aggiornato correttamente', 'role_users' => 'Utenti in questo ruolo', 'role_users_none' => 'Nessun utente assegnato a questo ruolo', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'I webhook sono un modo per inviare dati a URL esterne quando si verificano determinate azioni ed eventi all\'interno del sistema, consentendo l\'integrazione basata sugli eventi con piattaforme esterne, come sistemi di messaggistica o di notifica.', - 'webhooks_x_trigger_events' => '1 evento trigger|:count eventi trigger', + 'webhooks_x_trigger_events' => ':count evento trigger|:count eventi trigger', 'webhooks_create' => 'Crea Nuovo Webhook', 'webhooks_none_created' => 'Nessun webhook è stato creato.', 'webhooks_edit' => 'Modifica Webhook', diff --git a/lang/ja/activities.php b/lang/ja/activities.php index dd187014a..05b08b9a1 100644 --- a/lang/ja/activities.php +++ b/lang/ja/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'ユーザーを更新しました', 'user_delete_notification' => 'ユーザーを削除しました', + // Roles + 'role_create_notification' => '役割を作成しました', + 'role_update_notification' => '役割を更新しました', + 'role_delete_notification' => '役割を削除しました', + // Other 'commented_on' => 'がコメント:', 'permissions_update' => 'が権限を更新:', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index 71f4ac9c6..fc6be5b50 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新: :timeLength', 'meta_updated_name' => '更新: :timeLength (:user)', 'meta_owned_name' => '所有者: :user', - 'meta_reference_page_count' => '1 つのページで参照|:count つのページで参照', + 'meta_reference_page_count' => '{0}ページの参照はありません|[1,9]:count つのページから参照|[10,*]:count ページから参照', 'entity_select' => 'エンティティ選択', 'entity_select_lack_permission' => 'この項目を選択するために必要な権限がありません', 'images' => '画像', @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'このブックから検索', 'books_navigation' => '目次', 'books_sort' => '並び順を変更', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'ブック内のチャプターとページを移動して、その内容を再編成します。 他のブックを追加することで、チャプターやページをブック間で簡単に移動することができます。', 'books_sort_named' => 'ブック「:bookName」を並べ替え', 'books_sort_name' => '名前で並べ替え', 'books_sort_created' => '作成日で並べ替え', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'チャプターを後に', 'books_sort_show_other' => '他のブックを表示', 'books_sort_save' => '並び順を保存', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'これらのブックを並べ替え操作に追加すると、簡単にブック間の再編成が可能です。', + 'books_sort_move_up' => '上に移動', + 'books_sort_move_down' => '下に移動', + 'books_sort_move_prev_book' => '前のブックに移動', + 'books_sort_move_next_book' => '次のブックに移動', + 'books_sort_move_prev_chapter' => '前のチャプター内に移動', + 'books_sort_move_next_chapter' => '次のチャプター内に移動', + 'books_sort_move_book_start' => '本の先頭に移動', + 'books_sort_move_book_end' => '本の末尾に移動', + 'books_sort_move_before_chapter' => 'チャプターの前に移動', + 'books_sort_move_after_chapter' => 'チャプターの後に移動', 'books_copy' => 'ブックをコピー', 'books_copy_success' => 'ブックが正常にコピーされました', diff --git a/lang/ja/errors.php b/lang/ja/errors.php index 2cb189929..f1d3871f1 100644 --- a/lang/ja/errors.php +++ b/lang/ja/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => '画像アップロード時にエラーが発生しました。', 'image_upload_type_error' => 'アップロード中の画像の種類が無効です', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => '描画データを読み込めませんでした。描画ファイルが存在しないか、アクセス権限がありません。', // Attachments 'attachment_not_found' => '添付ファイルが見つかりません', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index 5efaad137..7afdf546d 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -33,9 +33,9 @@ return [ 'app_custom_html_desc' => 'スタイルシートやアナリティクスコード追加したい場合、ここを編集します。これは<head>の最下部に挿入されます。', 'app_custom_html_disabled_notice' => '重大な変更を元に戻せるよう、この設定ページではカスタムのHTML headコンテンツが無効になっています。', 'app_logo' => 'ロゴ', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_logo_desc' => 'これはアプリケーションのヘッダーバー、およびその他のエリアで使用されます。この画像は高さが86pxであるべきです。大きな画像は縮小されます。', + 'app_icon' => 'アプリケーション アイコン', + 'app_icon_desc' => 'このアイコンはブラウザのタブとショートカットアイコンに使用されます。これは256pxの正方形PNG画像であるべきです。', 'app_homepage' => 'アプリケーションのホームページ', 'app_homepage_desc' => 'デフォルトのビューの代わりにホームページに表示するビューを選択します。選択したページの権限は無視されます。', 'app_homepage_select' => 'ページを選択', @@ -49,12 +49,12 @@ return [ 'app_disable_comments_desc' => 'アプリケーション内のすべてのページのコメントを無効にします。既存のコメントは表示されません。', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'color_scheme' => 'アプリケーションの配色', + 'color_scheme_desc' => 'BookStack インターフェイスで使用する色を設定します。 色はダークモードとライトモードで個別に設定することができ、テーマへの適合と読みやすさを確保することができます。', + 'ui_colors_desc' => 'BookStackのプライマリカラーとデフォルトリンクカラーを設定します。プライマリカラーは主にヘッダーバナー、ボタン、インターフェイスの装飾に使用されます。 デフォルトのリンク色はテキストベースのリンクとアクションに使用されます。これは作成されたコンテンツと Bookstack インターフェイスの両方に適用されます。', + 'app_color' => 'プライマリ色', + 'link_color' => 'デフォルトのリンク色', + 'content_colors_desc' => 'ページ構成階層の各要素に色を設定します。読みやすさを考慮して、デフォルトの色と同じような明るさの色を選ぶことをお勧めします。', 'bookshelf_color' => '本棚の色', 'book_color' => 'ブックの色', 'chapter_color' => 'チャプターの色', @@ -138,18 +138,16 @@ return [ 'roles' => '役割', 'role_user_roles' => '役割', 'roles_index_desc' => '役割は、ユーザーをグループ化しメンバーにシステム権限を与えるために使用されます。ユーザーが複数の役割のメンバーである場合、与えられた権限は積み重なり、ユーザーはすべての能力を継承します。', - 'roles_x_users_assigned' => '1人のユーザーに割り当て|:count人のユーザーに割り当て', - 'roles_x_permissions_provided' => '1件の権限|:count件の権限', - 'roles_assigned_users' => 'Assigned Users', - 'roles_permissions_provided' => 'Provided Permissions', + 'roles_x_users_assigned' => ':count人のユーザーに割り当て|:count人のユーザーに割り当て', + 'roles_x_permissions_provided' => ':count件の権限|:count件の権限', + 'roles_assigned_users' => '割り当てユーザ数', + 'roles_permissions_provided' => '提供される権限数', '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' => '役割名', @@ -175,7 +173,6 @@ return [ 'role_own' => '自身', 'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:', 'role_save' => '役割を保存', - 'role_update_success' => '役割を更新しました', 'role_users' => 'この役割を持つユーザー', 'role_users_none' => 'この役割が付与されたユーザーはいません', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhook', 'webhooks_index_desc' => 'Webhookは、システム内で特定のアクションやイベントが発生したときに外部URLにデータを送信する方法で、メッセージングシステムや通知システムなどの外部プラットフォームとのイベントベースの統合を可能にします。', - 'webhooks_x_trigger_events' => '1個のトリガーイベント|:count個のトリガーイベント', + 'webhooks_x_trigger_events' => ':count個のトリガーイベント|:count個のトリガーイベント', 'webhooks_create' => 'Webhookを作成', 'webhooks_none_created' => 'Webhookはまだ作成されていません。', 'webhooks_edit' => 'Webhookを編集', diff --git a/lang/ka/activities.php b/lang/ka/activities.php index f348bff1f..e89b8eab2 100644 --- a/lang/ka/activities.php +++ b/lang/ka/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'commented on', 'permissions_update' => 'updated permissions', diff --git a/lang/ka/entities.php b/lang/ka/entities.php index 8bf805774..9b02f3111 100644 --- a/lang/ka/entities.php +++ b/lang/ka/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', diff --git a/lang/ka/settings.php b/lang/ka/settings.php index 6f4376d42..76e689b6d 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roles', 'role_user_roles' => 'User Roles', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Create New Role', - 'role_create_success' => 'Role successfully created', 'role_delete' => 'Delete Role', 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.', 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', 'role_delete_no_migration' => "Don't migrate users", 'role_delete_sure' => 'Are you sure you want to delete this role?', - 'role_delete_success' => 'Role successfully deleted', 'role_edit' => 'Edit Role', 'role_details' => 'Role Details', 'role_name' => 'Role Name', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', 'role_save' => 'Save Role', - 'role_update_success' => 'Role successfully updated', 'role_users' => 'Users in this role', 'role_users_none' => 'No users are currently assigned to this role', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/ko/activities.php b/lang/ko/activities.php index ab7a46d0f..f095ce548 100644 --- a/lang/ko/activities.php +++ b/lang/ko/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => '사용자가 업데이트되었습니다', 'user_delete_notification' => '사용자가 삭제되었습니다', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => '댓글 쓰기', 'permissions_update' => '권한 수정함', diff --git a/lang/ko/entities.php b/lang/ko/entities.php index 7ae1313c6..2011cadb8 100644 --- a/lang/ko/entities.php +++ b/lang/ko/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '수정함 :timeLength', 'meta_updated_name' => '수정함 :timeLength, :user', 'meta_owned_name' => '소유함 :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => '항목 선택', 'entity_select_lack_permission' => '이 항목을 선택하기 위해 필요한 권한이 없습니다', 'images' => '이미지', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index e503e54d7..88ed5245f 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => '권한', 'role_user_roles' => '사용자 권한', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => '권한 만들기', - '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' => '권한 이름', @@ -175,7 +173,6 @@ return [ 'role_own' => '직접 만든 항목', 'role_controlled_by_asset' => '저마다 다름', 'role_save' => '저장', - 'role_update_success' => '권한 저장함', 'role_users' => '이 권한을 가진 사용자들', 'role_users_none' => '그런 사용자가 없습니다.', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => '웹 훅', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => '웹 훅 만들기', 'webhooks_none_created' => '웹 훅이 없습니다.', 'webhooks_edit' => '웹 훅 수정', diff --git a/lang/lt/activities.php b/lang/lt/activities.php index 5644a9487..a0d4e9d8a 100644 --- a/lang/lt/activities.php +++ b/lang/lt/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'pakomentavo', 'permissions_update' => 'atnaujinti leidimai', diff --git a/lang/lt/entities.php b/lang/lt/entities.php index 1d1ee1f81..fb823e8dc 100644 --- a/lang/lt/entities.php +++ b/lang/lt/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atnaujintas :timeLength', 'meta_updated_name' => 'Atnaujinta :timeLength naudotojo :user', 'meta_owned_name' => 'Priklauso :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Pasirinkti subjektą', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Nuotraukos', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index 796cbfb84..e47607473 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Vaidmenys', 'role_user_roles' => 'Naudotojo vaidmenys', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Sukurti naują vaidmenį', - 'role_create_success' => 'Vaidmuo sukurtas sėkmingai', 'role_delete' => 'Ištrinti vaidmenį', 'role_delete_confirm' => 'Tai ištrins vaidmenį vardu\':roleName\'.', 'role_delete_users_assigned' => 'Šis vaidmuo turi :userCount naudotojus priskirtus prie jo. Jeigu norite naudotojus perkelti iš šio vaidmens, pasirinkite naują vaidmenį apačioje.', 'role_delete_no_migration' => "Don't migrate users", 'role_delete_sure' => 'Ar esate tikri, jog norite ištrinti šį vaidmenį?', - 'role_delete_success' => 'Vaidmuo ištrintas sėkmingai', 'role_edit' => 'Redaguoti vaidmenį', 'role_details' => 'Vaidmens detalės', 'role_name' => 'Vaidmens pavadinimas', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Nuosavi', 'role_controlled_by_asset' => 'Kontroliuojami nuosavybės, į kurią yra įkelti', 'role_save' => 'Išsaugoti vaidmenį', - 'role_update_success' => 'Vaidmuo atnaujintas sėkmingai', 'role_users' => 'Naudotojai šiame vaidmenyje', 'role_users_none' => 'Šiuo metu prie šio vaidmens nėra priskirta naudotojų', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/lv/activities.php b/lang/lv/activities.php index 356d4890f..6f0936e3f 100644 --- a/lang/lv/activities.php +++ b/lang/lv/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Lietotājs veiksmīgi atjaunināts', 'user_delete_notification' => 'Lietotājs veiksmīgi dzēsts', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'komentēts', 'permissions_update' => 'atjaunoja atļaujas', diff --git a/lang/lv/entities.php b/lang/lv/entities.php index 62c3379bf..3a8eea839 100644 --- a/lang/lv/entities.php +++ b/lang/lv/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atjaunināts :timeLength', 'meta_updated_name' => ':user atjauninājis pirms :timeLength', 'meta_owned_name' => 'Īpašnieks :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Izvēlēties vienumu', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Attēli', diff --git a/lang/lv/settings.php b/lang/lv/settings.php index 8148228d2..947f23735 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Grupas', 'role_user_roles' => 'Lietotāju grupas', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Izveidot jaunu grupu', - 'role_create_success' => 'Grupa veiksmīgi izveidota', 'role_delete' => 'Dzēst grupu', 'role_delete_confirm' => 'Loma \':roleName\' tiks dzēsta.', 'role_delete_users_assigned' => 'Šajā grupā ir pievienoti :userCount lietotāji. Ja vēlaties pārvietot lietotājus no šīs grupas, tad izvēlaties kādu no zemāk redzamajām grupām.', 'role_delete_no_migration' => "Nepārvietot lietotājus", 'role_delete_sure' => 'Vai tiešām vēlaties dzēst grupu?', - 'role_delete_success' => 'Grupa veiksmīgi dzēsta', 'role_edit' => 'Rediģēt grupu', 'role_details' => 'Informācija par grupu', 'role_name' => 'Grupas nosaukums', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Savi', 'role_controlled_by_asset' => 'Kontrolē resurss, uz ko tie ir augšupielādēti', 'role_save' => 'Saglabāt grupu', - 'role_update_success' => 'Grupa veiksmīgi atjaunināta', 'role_users' => 'Lietotāji šajā grupā', 'role_users_none' => 'Pagaidām neviens lietotājs nav pievienots šai grupai', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhook', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Izveidot jaunu webhook', 'webhooks_none_created' => 'Nav izveidots neviens webhook.', 'webhooks_edit' => 'Labot webhook', diff --git a/lang/nb/activities.php b/lang/nb/activities.php index ca99ff8ff..bbbdf8940 100644 --- a/lang/nb/activities.php +++ b/lang/nb/activities.php @@ -36,17 +36,17 @@ return [ 'book_delete' => 'slettet bok', 'book_delete_notification' => 'Boken ble slettet', 'book_sort' => 'sorterte bok', - 'book_sort_notification' => 'Boken ble gjenopprettet', + 'book_sort_notification' => 'Boken ble omsortert', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', + 'bookshelf_create' => 'opprettet hylle', + 'bookshelf_create_notification' => 'Hylllen ble opprettet', + 'bookshelf_create_from_book' => 'endret fra bok til hylle', 'bookshelf_create_from_book_notification' => 'Boken ble konvertert til en bokhylle', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_update' => 'oppdatert hylle', + 'bookshelf_update_notification' => 'Hyllen ble oppdatert', + 'bookshelf_delete' => 'slettet hylle', + 'bookshelf_delete_notification' => 'Hyllen ble slettet', // Favourites 'favourite_add_notification' => '«:name» ble lagt til i dine favoritter', @@ -57,16 +57,21 @@ return [ 'mfa_remove_method_notification' => 'Flerfaktor-metoden ble fjernet', // Webhooks - 'webhook_create' => 'created webhook', - 'webhook_create_notification' => 'Webhook successfully created', - 'webhook_update' => 'updated webhook', - 'webhook_update_notification' => 'Webhook successfully updated', - 'webhook_delete' => 'deleted webhook', - 'webhook_delete_notification' => 'Webhook successfully deleted', + 'webhook_create' => 'opprettet webhook', + 'webhook_create_notification' => 'Webhook ble opprettet', + 'webhook_update' => 'oppdatert webhook', + 'webhook_update_notification' => 'Webhook ble oppdatert', + 'webhook_delete' => 'slettet webhook', + 'webhook_delete_notification' => 'Webhook ble slettet', // Users - 'user_update_notification' => 'User successfully updated', - 'user_delete_notification' => 'User successfully removed', + 'user_update_notification' => 'Brukeren ble oppdatert', + 'user_delete_notification' => 'Brukeren ble fjernet', + + // Roles + 'role_create_notification' => 'Rollen ble opprettet', + 'role_update_notification' => 'Rollen ble oppdatert', + 'role_delete_notification' => 'Rollen ble fjernet', // Other 'commented_on' => 'kommenterte på', diff --git a/lang/nb/auth.php b/lang/nb/auth.php index 968ec2302..1db179b5f 100644 --- a/lang/nb/auth.php +++ b/lang/nb/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'E-post', 'password' => 'Passord', 'password_confirm' => 'Bekreft passord', - 'password_hint' => 'Must be at least 8 characters', + 'password_hint' => 'Må være minst 8 tegn', 'forgot_password' => 'Glemt passord?', 'remember_me' => 'Husk meg', 'ldap_email_hint' => 'Oppgi en e-post for denne kontoen.', @@ -39,9 +39,9 @@ return [ 'register_success' => 'Takk for registreringen! Du kan nå logge inn på tjenesten.', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', - 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', - 'auto_init_start_link' => 'Proceed with authentication', + 'auto_init_starting' => 'Forsøker innlogging', + 'auto_init_starting_desc' => 'Vi kontakter autentiseringssystemet ditt for å påbegynne innloggingsprosessen. Dersom det ikke er noe fremdrift i løpet av fem sekunder kan du trykke på lenken under.', + 'auto_init_start_link' => 'Fortsett med autentisering', // Password Reset 'reset_password' => 'Nullstille passord', @@ -59,10 +59,10 @@ return [ 'email_confirm_text' => 'Bekreft e-posten din ved å trykke på knappen nedenfor:', 'email_confirm_action' => 'Bekreft e-post', 'email_confirm_send_error' => 'Bekreftelse er krevd av systemet, men systemet kan ikke sende disse. Kontakt admin for å løse problemet.', - 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', + 'email_confirm_success' => 'Epost-adressen din er verifisert! Du kan nå logge inn ved å bruke denne ved innlogging.', 'email_confirm_resent' => 'Bekreftelsespost ble sendt, sjekk innboksen din.', - 'email_confirm_thanks' => 'Thanks for confirming!', - 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', + 'email_confirm_thanks' => 'Takk for verifiseringen!', + 'email_confirm_thanks_desc' => 'Vent et øyeblikk mens verifiseringen blir utført. Om du ikke blir videresendt i løpet av tre sekunder kan du trykke «Fortsett» nedenfor.', 'email_not_confirmed' => 'E-posten er ikke bekreftet.', 'email_not_confirmed_text' => 'Epost-adressen er ennå ikke bekreftet.', @@ -78,7 +78,7 @@ return [ 'user_invite_page_welcome' => 'Velkommen til :appName!', 'user_invite_page_text' => 'For å fullføre prosessen må du oppgi et passord som sikrer din konto på :appName for fremtidige besøk.', 'user_invite_page_confirm_button' => 'Bekreft passord', - 'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!', + 'user_invite_success_login' => 'Passordet ble satt, du skal nå kunne logge inn med ditt nye passord for å få tilgang til :appName!', // Multi-factor Authentication 'mfa_setup' => 'Konfigurer flerfaktor-autentisering', diff --git a/lang/nb/common.php b/lang/nb/common.php index af7d0635d..2c8ccf6ec 100644 --- a/lang/nb/common.php +++ b/lang/nb/common.php @@ -25,11 +25,11 @@ return [ 'actions' => 'Handlinger', 'view' => 'Vis', 'view_all' => 'Vis alle', - 'new' => 'New', + 'new' => 'Ny', 'create' => 'Opprett', 'update' => 'Oppdater', 'edit' => 'Rediger', - 'sort' => 'Sorter', + 'sort' => 'Sortér', 'move' => 'Flytt', 'copy' => 'Kopier', 'reply' => 'Svar', @@ -46,10 +46,10 @@ return [ 'unfavourite' => 'Avfavorisér', 'next' => 'Neste', 'previous' => 'Forrige', - 'filter_active' => 'Active Filter:', - 'filter_clear' => 'Clear Filter', - 'download' => 'Download', - 'open_in_tab' => 'Open in Tab', + 'filter_active' => 'Aktivt filter:', + 'filter_clear' => 'Tøm filter', + 'download' => 'Last ned', + 'open_in_tab' => 'Åpne i fane', // Sort Options 'sort_options' => 'Sorteringsalternativer', @@ -75,20 +75,20 @@ return [ 'default' => 'Standard', 'breadcrumb' => 'Brødsmuler', 'status' => 'Status', - 'status_active' => 'Active', - 'status_inactive' => 'Inactive', - 'never' => 'Never', - 'none' => 'None', + 'status_active' => 'Aktiv', + 'status_inactive' => 'Inaktiv', + 'never' => 'Aldri', + 'none' => 'Ingen', // Header - 'homepage' => 'Homepage', + 'homepage' => 'Hjemmeside', 'header_menu_expand' => 'Utvid toppmeny', 'profile_menu' => 'Profilmeny', 'view_profile' => 'Vis profil', 'edit_profile' => 'Endre Profile', 'dark_mode' => 'Kveldsmodus', 'light_mode' => 'Dagmodus', - 'global_search' => 'Global Search', + 'global_search' => 'Globalt søk', // Layout tabs 'tab_info' => 'Informasjon', diff --git a/lang/nb/editor.php b/lang/nb/editor.php index 670c1c5e1..2f3b22d8b 100644 --- a/lang/nb/editor.php +++ b/lang/nb/editor.php @@ -7,168 +7,168 @@ */ return [ // General editor terms - 'general' => 'General', - 'advanced' => 'Advanced', - 'none' => 'None', - 'cancel' => 'Cancel', - 'save' => 'Save', - 'close' => 'Close', - 'undo' => 'Undo', - 'redo' => 'Redo', - 'left' => 'Left', - 'center' => 'Center', - 'right' => 'Right', - 'top' => 'Top', - 'middle' => 'Middle', - 'bottom' => 'Bottom', - 'width' => 'Width', - 'height' => 'Height', - 'More' => 'More', - 'select' => 'Select...', + 'general' => 'Generelt', + 'advanced' => 'Avansert', + 'none' => 'Ingen', + 'cancel' => 'Avbryt', + 'save' => 'Lagre', + 'close' => 'Lukk', + 'undo' => 'Angre', + 'redo' => 'Gjør om', + 'left' => 'Venstre', + 'center' => 'Sentrert', + 'right' => 'Høyre', + 'top' => 'Topp', + 'middle' => 'Sentrert', + 'bottom' => 'Bunn', + 'width' => 'Bredde', + 'height' => 'Høyde', + 'More' => 'Mer', + 'select' => 'Velg …', // Toolbar - 'formats' => 'Formats', - 'header_large' => 'Large Header', - 'header_medium' => 'Medium Header', - 'header_small' => 'Small Header', - 'header_tiny' => 'Tiny Header', - 'paragraph' => 'Paragraph', - 'blockquote' => 'Blockquote', - 'inline_code' => 'Inline code', - 'callouts' => 'Callouts', - 'callout_information' => 'Information', - 'callout_success' => 'Success', - 'callout_warning' => 'Warning', - 'callout_danger' => 'Danger', - 'bold' => 'Bold', - 'italic' => 'Italic', - 'underline' => 'Underline', - 'strikethrough' => 'Strikethrough', - 'superscript' => 'Superscript', - 'subscript' => 'Subscript', - 'text_color' => 'Text color', - 'custom_color' => 'Custom color', - 'remove_color' => 'Remove color', - 'background_color' => 'Background color', - 'align_left' => 'Align left', - 'align_center' => 'Align center', - 'align_right' => 'Align right', - 'align_justify' => 'Justify', - 'list_bullet' => 'Bullet list', - 'list_numbered' => 'Numbered list', - 'list_task' => 'Task list', - 'indent_increase' => 'Increase indent', - 'indent_decrease' => 'Decrease indent', - 'table' => 'Table', - 'insert_image' => 'Insert image', - 'insert_image_title' => 'Insert/Edit Image', - 'insert_link' => 'Insert/edit link', - 'insert_link_title' => 'Insert/Edit Link', - 'insert_horizontal_line' => 'Insert horizontal line', - 'insert_code_block' => 'Insert code block', - 'edit_code_block' => 'Edit code block', - 'insert_drawing' => 'Insert/edit drawing', - 'drawing_manager' => 'Drawing manager', - 'insert_media' => 'Insert/edit media', - 'insert_media_title' => 'Insert/Edit Media', - 'clear_formatting' => 'Clear formatting', - 'source_code' => 'Source code', - 'source_code_title' => 'Source Code', - 'fullscreen' => 'Fullscreen', - 'image_options' => 'Image options', + 'formats' => 'Formater', + 'header_large' => 'Stor overskrift', + 'header_medium' => 'Medium overskrift', + 'header_small' => 'Liten overskrift', + 'header_tiny' => 'Bitteliten overskrift', + 'paragraph' => 'Avsnitt', + 'blockquote' => 'Blokksitat', + 'inline_code' => 'Kodesetning', + 'callouts' => 'Notabene', + 'callout_information' => 'Informasjon', + 'callout_success' => 'Positiv', + 'callout_warning' => 'Advarsel', + 'callout_danger' => 'Negativ', + 'bold' => 'Fet', + 'italic' => 'Kursiv', + 'underline' => 'Understrek', + 'strikethrough' => 'Strek over', + 'superscript' => 'Hevet skrift', + 'subscript' => 'Senket skrift', + 'text_color' => 'Tekstfarge', + 'custom_color' => 'Egenvalgt farge', + 'remove_color' => 'Fjern farge', + 'background_color' => 'Bakgrunnsfarge', + 'align_left' => 'Venstrejustering', + 'align_center' => 'Midtstilling', + 'align_right' => 'Høyrejustering', + 'align_justify' => 'Blokkjustering', + 'list_bullet' => 'Punktliste', + 'list_numbered' => 'Nummerert liste', + 'list_task' => 'Oppgaveliste', + 'indent_increase' => 'Øk innrykk', + 'indent_decrease' => 'Redusér innrykk', + 'table' => 'Tabell', + 'insert_image' => 'Sett inn bilde', + 'insert_image_title' => 'Sett inn/redigér bilde', + 'insert_link' => 'Sett inn/redigér lenke', + 'insert_link_title' => 'Sett inn/redigér lenke', + 'insert_horizontal_line' => 'Sett inn horisontal linje', + 'insert_code_block' => 'Sett inn kodeblokk', + 'edit_code_block' => 'Redigér kodeblokk', + 'insert_drawing' => 'Sett inn/redigér tegning', + 'drawing_manager' => 'Tegningsbehandling', + 'insert_media' => 'Sett inn/redigér media', + 'insert_media_title' => 'Sett inn/redigér media', + 'clear_formatting' => 'Rens formattering', + 'source_code' => 'Kildekode', + 'source_code_title' => 'Kildekode', + 'fullscreen' => 'Fullskjerm', + 'image_options' => 'Bildealternativer', // Tables - 'table_properties' => 'Table properties', - 'table_properties_title' => 'Table Properties', - 'delete_table' => 'Delete table', - 'insert_row_before' => 'Insert row before', - 'insert_row_after' => 'Insert row after', - 'delete_row' => 'Delete row', - 'insert_column_before' => 'Insert column before', - 'insert_column_after' => 'Insert column after', - 'delete_column' => 'Delete column', - 'table_cell' => 'Cell', - 'table_row' => 'Row', - 'table_column' => 'Column', - 'cell_properties' => 'Cell properties', - 'cell_properties_title' => 'Cell Properties', - 'cell_type' => 'Cell type', - 'cell_type_cell' => 'Cell', - 'cell_scope' => 'Scope', - 'cell_type_header' => 'Header cell', - 'merge_cells' => 'Merge cells', - 'split_cell' => 'Split cell', - 'table_row_group' => 'Row Group', - 'table_column_group' => 'Column Group', - 'horizontal_align' => 'Horizontal align', - 'vertical_align' => 'Vertical align', - 'border_width' => 'Border width', - 'border_style' => 'Border style', - 'border_color' => 'Border color', - 'row_properties' => 'Row properties', - 'row_properties_title' => 'Row Properties', - 'cut_row' => 'Cut row', - 'copy_row' => 'Copy row', - 'paste_row_before' => 'Paste row before', - 'paste_row_after' => 'Paste row after', - 'row_type' => 'Row type', - 'row_type_header' => 'Header', - 'row_type_body' => 'Body', - 'row_type_footer' => 'Footer', - 'alignment' => 'Alignment', - 'cut_column' => 'Cut column', - 'copy_column' => 'Copy column', - 'paste_column_before' => 'Paste column before', - 'paste_column_after' => 'Paste column after', - 'cell_padding' => 'Cell padding', - 'cell_spacing' => 'Cell spacing', - 'caption' => 'Caption', - 'show_caption' => 'Show caption', - 'constrain' => 'Constrain proportions', - 'cell_border_solid' => 'Solid', - 'cell_border_dotted' => 'Dotted', - 'cell_border_dashed' => 'Dashed', - 'cell_border_double' => 'Double', - 'cell_border_groove' => 'Groove', - 'cell_border_ridge' => 'Ridge', - 'cell_border_inset' => 'Inset', - 'cell_border_outset' => 'Outset', - 'cell_border_none' => 'None', - 'cell_border_hidden' => 'Hidden', + 'table_properties' => 'Tabellegenskaper', + 'table_properties_title' => 'Tabellegenskaper', + 'delete_table' => 'Slett tabell', + 'insert_row_before' => 'Sett inn rad før', + 'insert_row_after' => 'Sett inn rad etter', + 'delete_row' => 'Slett rad', + 'insert_column_before' => 'Sett inn kolonne før', + 'insert_column_after' => 'Sett inn kolonne etter', + 'delete_column' => 'Slett kolonne', + 'table_cell' => 'Celle', + 'table_row' => 'Rad', + 'table_column' => 'Kolonne', + 'cell_properties' => 'Celle-egenskaper', + 'cell_properties_title' => 'Celle-egenskaper', + 'cell_type' => 'Celletype', + 'cell_type_cell' => 'Celle', + 'cell_scope' => 'Omfang', + 'cell_type_header' => 'Topptekst-celle', + 'merge_cells' => 'Slå sammen celler', + 'split_cell' => 'Del celle', + 'table_row_group' => 'Radgruppe', + 'table_column_group' => 'Kolonnegruppe', + 'horizontal_align' => 'Horisontal justering', + 'vertical_align' => 'Vertikal justering', + 'border_width' => 'Kantbredde', + 'border_style' => 'Kantstil', + 'border_color' => 'Kantfarge', + 'row_properties' => 'Radegenskaper', + 'row_properties_title' => 'Radegenskaper', + 'cut_row' => 'Klipp ut rad', + 'copy_row' => 'Kopiér rad', + 'paste_row_before' => 'Lim rad inn før', + 'paste_row_after' => 'Lim rad inn etter', + 'row_type' => 'Radtype', + 'row_type_header' => 'Topptekst', + 'row_type_body' => 'Hovedtekst', + 'row_type_footer' => 'Bunntekst', + 'alignment' => 'Justering', + 'cut_column' => 'Klipp ut kolonne', + 'copy_column' => 'Kopiér kolonne', + 'paste_column_before' => 'Lim kolonne inn før', + 'paste_column_after' => 'Lim kolonne inn etter', + 'cell_padding' => 'Celleutfylling', + 'cell_spacing' => 'Celleavstand', + 'caption' => 'Overskrift', + 'show_caption' => 'Vis overskrift', + 'constrain' => 'Behold proporsjoner', + 'cell_border_solid' => 'Heltrukket', + 'cell_border_dotted' => 'Prikker', + 'cell_border_dashed' => 'Stipler', + 'cell_border_double' => 'Dobbel', + 'cell_border_groove' => 'Rille', + 'cell_border_ridge' => 'Kant', + 'cell_border_inset' => 'Nedsenk', + 'cell_border_outset' => 'Uthev', + 'cell_border_none' => 'Ingen', + 'cell_border_hidden' => 'Skjult bredde', // Images, links, details/summary & embed - 'source' => 'Source', - 'alt_desc' => 'Alternative description', - 'embed' => 'Embed', - 'paste_embed' => 'Paste your embed code below:', - 'url' => 'URL', - 'text_to_display' => 'Text to display', - 'title' => 'Title', - 'open_link' => 'Open link', - 'open_link_in' => 'Open link in...', - 'open_link_current' => 'Current window', - 'open_link_new' => 'New window', - 'remove_link' => 'Remove link', - 'insert_collapsible' => 'Insert collapsible block', - 'collapsible_unwrap' => 'Unwrap', - 'edit_label' => 'Edit label', - 'toggle_open_closed' => 'Toggle open/closed', - 'collapsible_edit' => 'Edit collapsible block', - 'toggle_label' => 'Toggle label', + 'source' => 'Kilde', + 'alt_desc' => 'Alternativ beskrivelse', + 'embed' => 'Bygg inn', + 'paste_embed' => 'Lim inn koden din her:', + 'url' => 'Nettlenke', + 'text_to_display' => 'Synlig tekst', + 'title' => 'Tittel', + 'open_link' => 'Åpne lenke', + 'open_link_in' => 'Åpne i ...', + 'open_link_current' => 'Samme vindu', + 'open_link_new' => 'Nytt vindu', + 'remove_link' => 'Fjern lenke', + 'insert_collapsible' => 'Sett inn sammenleggbar blokk', + 'collapsible_unwrap' => 'Pakk ut', + 'edit_label' => 'Rediger etikett', + 'toggle_open_closed' => 'Veksle åpen/lukket', + 'collapsible_edit' => 'Rediger sammenleggbar blokk', + 'toggle_label' => 'Veksle etikettsynlighet', // About view - 'about' => 'About the editor', - 'about_title' => 'About the WYSIWYG Editor', - 'editor_license' => 'Editor License & Copyright', - 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.', - 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', - 'save_continue' => 'Save Page & Continue', - 'callouts_cycle' => '(Keep pressing to toggle through types)', - 'link_selector' => 'Link to content', - 'shortcuts' => 'Shortcuts', - 'shortcut' => 'Shortcut', - 'shortcuts_intro' => 'The following shortcuts are available in the editor:', + 'about' => 'Om tekstredigeringsprogrammet', + 'about_title' => 'Om HDSEHDF-tekstredigeringsprogrammet', + 'editor_license' => 'Tekstbehandlerlisens og opphavsrett', + 'editor_tiny_license' => 'Denne tekstredigereren er laget med :tinyLink som er lisensiert under MIT.', + 'editor_tiny_license_link' => 'Informasjon om opphavsrett og lisens for TinyMCE finnes her.', + 'save_continue' => 'Lagre side og fortsett', + 'callouts_cycle' => '(Fortsett å trykke for å veksle mellom typer)', + 'link_selector' => 'Lenke til innhold', + 'shortcuts' => 'Snarveier', + 'shortcut' => 'Snarvei', + 'shortcuts_intro' => 'Følgende snarveier er tilgjengelige i tekstredigeringsverktøyet:', 'windows_linux' => '(Windows/Linux)', - 'mac' => '(Mac)', - 'description' => 'Description', + 'mac' => '(MacOS)', + 'description' => 'Beskrivelse', ]; diff --git a/lang/nb/entities.php b/lang/nb/entities.php index dd34bbeba..2adfb02ae 100644 --- a/lang/nb/entities.php +++ b/lang/nb/entities.php @@ -23,9 +23,9 @@ return [ 'meta_updated' => 'Oppdatert :timeLength', 'meta_updated_name' => 'Oppdatert :timeLength av :user', 'meta_owned_name' => 'Eies av :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Sitert på :count side|Sitert på :count sider', 'entity_select' => 'Velg entitet', - 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', + 'entity_select_lack_permission' => 'Do har ikke tilgang til å velge dette elementet', 'images' => 'Bilder', 'my_recent_drafts' => 'Mine nylige utkast', 'my_recently_viewed' => 'Mine nylige visninger', @@ -42,15 +42,15 @@ return [ // Permissions and restrictions 'permissions' => 'Tilganger', - 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', - 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', - 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', + 'permissions_desc' => 'Endringer gjort her vil overstyre standardrettigheter gitt via brukerroller.', + 'permissions_book_cascade' => 'Rettigheter satt på bøker vil automatisk arves ned til sidenivå. Du kan overstyre arv ved å definere egne rettigheter på kapitler eller sider.', + 'permissions_chapter_cascade' => 'Rettigheter satt på kapitler vi automatisk arves ned til sider. Du kan overstyre arv ved å definere rettigheter på enkeltsider.', 'permissions_save' => 'Lagre tillatelser', 'permissions_owner' => 'Eier', - 'permissions_role_everyone_else' => 'Everyone Else', - 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', - 'permissions_role_override' => 'Override permissions for role', - 'permissions_inherit_defaults' => 'Inherit defaults', + 'permissions_role_everyone_else' => 'Alle andre', + 'permissions_role_everyone_else_desc' => 'Angi rettigheter for alle roller som ikke blir overstyrt (arvede rettigheter).', + 'permissions_role_override' => 'Overstyr rettigheter for rolle', + 'permissions_inherit_defaults' => 'Arv standardrettigheter', // Search 'search_results' => 'Søkeresultater', @@ -93,23 +93,23 @@ return [ 'shelves_save' => 'Lagre hylle', 'shelves_books' => 'Bøker på denne hyllen', 'shelves_add_books' => 'Legg til bøker på denne hyllen', - 'shelves_drag_books' => 'Drag books below to add them to this shelf', + 'shelves_drag_books' => 'Dra og slipp bøker nedenfor for å legge dem til i denne hyllen', 'shelves_empty_contents' => 'INgen bøker er stablet i denne hylla', 'shelves_edit_and_assign' => 'Endre hylla for å legge til bøker', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", - 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', - 'shelves_permissions' => 'Shelf Permissions', - 'shelves_permissions_updated' => 'Shelf Permissions Updated', - 'shelves_permissions_active' => 'Shelf Permissions Active', - 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', + 'shelves_edit_named' => 'Rediger :name (hylle)', + 'shelves_edit' => 'Rediger hylle', + 'shelves_delete' => 'Fjern hylle', + 'shelves_delete_named' => 'Fjern :name (hylle)', + 'shelves_delete_explain' => "Dette vil fjerne hyllen «:name». Bøkene på hyllen vil ikke bli slettet fra systemet.", + 'shelves_delete_confirmation' => 'Er du sikker på at du vil fjerne denne hyllen?', + 'shelves_permissions' => 'Hyllerettigheter', + 'shelves_permissions_updated' => 'Oppdaterte hyllerettigheter', + 'shelves_permissions_active' => 'Aktiverte hyllerettigheter', + 'shelves_permissions_cascade_warning' => 'Rettigheter på en hylle blir ikke automatisk arvet av bøker på hylla. Dette er fordi en bok kan finnes på flere hyller samtidig. Rettigheter kan likevel kopieres til bøker på hylla ved å bruke alternativene under.', 'shelves_copy_permissions_to_books' => 'Kopier tilganger til bøkene på hylla', 'shelves_copy_permissions' => 'Kopier tilganger', - 'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.', - 'shelves_copy_permission_success' => 'Shelf permissions copied to :count books', + 'shelves_copy_permissions_explain' => 'Dette vil kopiere rettighetene på denne hylla til alle bøkene som er plassert på den. Før du starter kopieringen bør du sjekke at rettighetene på hylla er lagret først.', + 'shelves_copy_permission_success' => 'Rettighetene ble kopiert til :count bøker', // Books 'book' => 'Bok', @@ -141,8 +141,8 @@ return [ 'books_search_this' => 'Søk i boken', 'books_navigation' => 'Boknavigasjon', 'books_sort' => 'Sorter bokinnhold', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', - 'books_sort_named' => 'Sorter boken :bookName', + 'books_sort_desc' => 'Flytt kapitler og sider inni en bok for å omorganisere dem. Andre bøker kan bli lagt til slik at det er enklere å flytte frem og tilbake mellom dem.', + 'books_sort_named' => 'Omorganisér :bookName (bok)', 'books_sort_name' => 'Sorter på navn', 'books_sort_created' => 'Sorter på opprettet dato', 'books_sort_updated' => 'Sorter på oppdatert dato', @@ -150,45 +150,45 @@ return [ 'books_sort_chapters_last' => 'Kapitler sist', 'books_sort_show_other' => 'Vis andre bøker', 'books_sort_save' => 'Lagre sortering', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', - 'books_copy' => 'Copy Book', - 'books_copy_success' => 'Book successfully copied', + 'books_sort_show_other_desc' => 'Legg til andre bøker her for å inkludere dem i omorganiseringen og muliggjør enkel flytting på tvers av dem.', + 'books_sort_move_up' => 'Flytt opp', + 'books_sort_move_down' => 'Flytt ned', + 'books_sort_move_prev_book' => 'Flytt til forrige bok', + 'books_sort_move_next_book' => 'Flytt til neste bok', + 'books_sort_move_prev_chapter' => 'Flytt inn i forrige kapittel', + 'books_sort_move_next_chapter' => 'Flytt inn i neste kapittel', + 'books_sort_move_book_start' => 'Flytt til starten av boken', + 'books_sort_move_book_end' => 'Flytt til slutten av boken', + 'books_sort_move_before_chapter' => 'Flytt før kapittel', + 'books_sort_move_after_chapter' => 'Flytt etter kapittel', + 'books_copy' => 'Kopiér bok', + 'books_copy_success' => 'Boken ble kopiert', // Chapters 'chapter' => 'Kapittel', 'chapters' => 'Kapitler', - 'x_chapters' => ':count Kapittel|:count Kapitler', - 'chapters_popular' => 'Populære kapittler', + 'x_chapters' => ':count kapittel|:count kapitler', + 'chapters_popular' => 'Populære kapitler', 'chapters_new' => 'Nytt kapittel', 'chapters_create' => 'Skriv nytt kapittel', 'chapters_delete' => 'Riv ut kapittel', - 'chapters_delete_named' => 'Riv ut kapittelet :chapterName', - 'chapters_delete_explain' => 'Du ønsker å rive ut kapittelet «:chapterName». Alle sidene vil bli flyttet ut av kapittelet og vil ligge direkte i boka.', - 'chapters_delete_confirm' => 'Er du sikker på at du vil rive ut dette kapittelet?', - 'chapters_edit' => 'Endre kapittel', - 'chapters_edit_named' => 'Endre kapittelet :chapterName', + 'chapters_delete_named' => 'Slett :chapterName (kapittel)', + 'chapters_delete_explain' => 'Dette vil slette «:chapterName» (kapittel). Alle sider i kapittelet vil også slettes.', + 'chapters_delete_confirm' => 'Er du sikker på at du vil slette dette kapittelet?', + 'chapters_edit' => 'Redigér kapittel', + 'chapters_edit_named' => 'Redigér :chapterName (kapittel)', 'chapters_save' => 'Lagre kapittel', 'chapters_move' => 'Flytt kapittel', - 'chapters_move_named' => 'Flytt kapittelet :chapterName', - 'chapter_move_success' => 'Kapittelet ble flyttet til :bookName', - 'chapters_copy' => 'Copy Chapter', - 'chapters_copy_success' => 'Chapter successfully copied', + 'chapters_move_named' => 'Flytt :chapterName (kapittel)', + 'chapter_move_success' => 'Kapittelet ble flyttet til :bookName (bok)', + 'chapters_copy' => 'Kopiér kapittel', + 'chapters_copy_success' => 'Kapitelet ble kopiert', 'chapters_permissions' => 'Kapitteltilganger', 'chapters_empty' => 'Det finnes ingen sider i dette kapittelet.', 'chapters_permissions_active' => 'Kapitteltilganger er aktivert', 'chapters_permissions_success' => 'Kapitteltilgager er oppdatert', 'chapters_search_this' => 'Søk i dette kapittelet', - 'chapter_sort_book' => 'Sort Book', + 'chapter_sort_book' => 'Omorganisér bok', // Pages 'page' => 'Side', @@ -198,50 +198,50 @@ return [ 'pages_new' => 'Ny side', 'pages_attachments' => 'Vedlegg', 'pages_navigation' => 'Sidenavigasjon', - 'pages_delete' => 'Riv ut side', - 'pages_delete_named' => 'Riv ut siden :pageName', - 'pages_delete_draft_named' => 'Kast sideutkast :pageName', - 'pages_delete_draft' => 'Kast sideutkast', - 'pages_delete_success' => 'Siden er revet ut', - 'pages_delete_draft_success' => 'Sideutkast er kastet', - 'pages_delete_confirm' => 'Er du sikker på at du vil rive ut siden?', - 'pages_delete_draft_confirm' => 'Er du sikker på at du vil forkaste utkastet?', - 'pages_editing_named' => 'Endrer :pageName', + 'pages_delete' => 'Slett side', + 'pages_delete_named' => 'Slett :pageName (side)', + 'pages_delete_draft_named' => 'Slett utkastet :pageName (side)', + 'pages_delete_draft' => 'Slett utkastet', + 'pages_delete_success' => 'Siden er slettet', + 'pages_delete_draft_success' => 'Sideutkastet ble slettet', + 'pages_delete_confirm' => 'Er du sikker på at du vil slette siden?', + 'pages_delete_draft_confirm' => 'Er du sikker på at du vil slette utkastet?', + 'pages_editing_named' => 'Redigerer :pageName (side)', 'pages_edit_draft_options' => 'Utkastsalternativer', 'pages_edit_save_draft' => 'Lagre utkast', - 'pages_edit_draft' => 'Endre utkast', + 'pages_edit_draft' => 'Redigér utkast', 'pages_editing_draft' => 'Redigerer utkast', 'pages_editing_page' => 'Redigerer side', - 'pages_edit_draft_save_at' => 'Ukast lagret under ', - 'pages_edit_delete_draft' => 'Forkast utkast', - 'pages_edit_discard_draft' => 'Gi opp utkast', - 'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor', - 'pages_edit_switch_to_markdown_clean' => '(Clean Content)', - 'pages_edit_switch_to_markdown_stable' => '(Stable Content)', - 'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor', + 'pages_edit_draft_save_at' => 'Sist lagret ', + 'pages_edit_delete_draft' => 'Slett utkast', + 'pages_edit_discard_draft' => 'Tilbakestill endring', + 'pages_edit_switch_to_markdown' => 'Bytt til Markdown tekstredigering', + 'pages_edit_switch_to_markdown_clean' => '(Renset innhold)', + 'pages_edit_switch_to_markdown_stable' => '(Urørt innhold)', + 'pages_edit_switch_to_wysiwyg' => 'Bytt til WYSIWYG tekstredigering', 'pages_edit_set_changelog' => 'Angi endringslogg', 'pages_edit_enter_changelog_desc' => 'Gi en kort beskrivelse av endringene dine', 'pages_edit_enter_changelog' => 'Se endringslogg', - 'pages_editor_switch_title' => 'Switch Editor', - 'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?', - 'pages_editor_switch_consider_following' => 'Consider the following when changing editors:', - 'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.', - 'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.', - 'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.', + 'pages_editor_switch_title' => 'Bytt tekstredigeringsprogram', + 'pages_editor_switch_are_you_sure' => 'Er du sikker på at du vil bytte tekstredigeringsprogram for denne siden?', + 'pages_editor_switch_consider_following' => 'Husk dette når du bytter tekstredigeringsprogram:', + 'pages_editor_switch_consideration_a' => 'Når du bytter, vil den nye tekstredigereren bli satt for alle fremtidige redaktører. Dette inkluderer alle redaktører som ikke kan endre type selv.', + 'pages_editor_switch_consideration_b' => 'Dette kan potensielt føre til tap av formatdetaljer eller syntaks i noen tilfeller.', + 'pages_editor_switch_consideration_c' => 'Etikett- eller redigeringslogg-endringer loggført siden siste lagring vil ikke føres videre etter endringen.', 'pages_save' => 'Lagre side', 'pages_title' => 'Sidetittel', 'pages_name' => 'Sidenavn', 'pages_md_editor' => 'Tekstbehandler', 'pages_md_preview' => 'Forhåndsvisning', - 'pages_md_insert_image' => 'Lim inn bilde', - 'pages_md_insert_link' => 'Lim in lenke', - 'pages_md_insert_drawing' => 'Lim inn tegning', - 'pages_md_show_preview' => 'Show preview', + 'pages_md_insert_image' => 'Sett inn bilde', + 'pages_md_insert_link' => 'Sett inn lenke', + 'pages_md_insert_drawing' => 'Sett inn tegning', + 'pages_md_show_preview' => 'Forhåndsvisning', 'pages_md_sync_scroll' => 'Sync preview scroll', 'pages_not_in_chapter' => 'Siden tilhører ingen kapittel', 'pages_move' => 'Flytt side', - 'pages_move_success' => 'Siden ble flyttet til ":parentName"', - 'pages_copy' => 'Kopier side', + 'pages_move_success' => 'Siden ble flyttet til «:parentName»', + 'pages_copy' => 'Kopiér side', 'pages_copy_desination' => 'Destinasjon', 'pages_copy_success' => 'Siden ble flyttet', 'pages_permissions' => 'Sidetilganger', @@ -255,10 +255,10 @@ return [ 'pages_revisions_created_by' => 'Skrevet av', 'pages_revisions_date' => 'Revideringsdato', 'pages_revisions_number' => '#', - 'pages_revisions_sort_number' => 'Revision Number', + 'pages_revisions_sort_number' => 'Revisjonsnummer', 'pages_revisions_numbered' => 'Revisjon #:id', 'pages_revisions_numbered_changes' => 'Endringer på revisjon #:id', - 'pages_revisions_editor' => 'Editor Type', + 'pages_revisions_editor' => 'Tekstredigeringstype', 'pages_revisions_changelog' => 'Endringslogg', 'pages_revisions_changes' => 'Endringer', 'pages_revisions_current' => 'Siste versjon', @@ -269,7 +269,7 @@ return [ 'pages_edit_content_link' => 'Endre innhold', 'pages_permissions_active' => 'Sidetilganger er aktive', 'pages_initial_revision' => 'Første publisering', - 'pages_references_update_revision' => 'System auto-update of internal links', + 'pages_references_update_revision' => 'Automatisk oppdatering av interne lenker', 'pages_initial_name' => 'Ny side', 'pages_editing_draft_notification' => 'Du skriver på et utkast som sist ble lagret :timeDiff.', 'pages_draft_edited_notification' => 'Siden har blitt endret siden du startet. Det anbefales at du forkaster dine endringer.', @@ -303,11 +303,11 @@ return [ 'tags_assigned_chapters' => 'Assigned to Chapters', 'tags_assigned_books' => 'Assigned to Books', 'tags_assigned_shelves' => 'Assigned to Shelves', - 'tags_x_unique_values' => ':count unique values', - 'tags_all_values' => 'All values', - 'tags_view_tags' => 'View Tags', - 'tags_view_existing_tags' => 'View existing tags', - 'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.', + 'tags_x_unique_values' => ':count unike verdier', + 'tags_all_values' => 'Alle verdier', + 'tags_view_tags' => 'Vis etiketter', + 'tags_view_existing_tags' => 'Vis eksisterende etiketter', + 'tags_list_empty_hint' => 'Etiketter kan tilordnes via sidepanelet, eller mens du redigerer detaljene for en hylle, bok eller kapittel.', 'attachments' => 'Vedlegg', 'attachments_explain' => 'Last opp vedlegg eller legg til lenker for å berike innholdet. Disse vil vises i sidestolpen på siden.', 'attachments_explain_instant_save' => 'Endringer her blir lagret med en gang.', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index 6f903706d..3b76a40c7 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roller', 'role_user_roles' => 'Kontoroller', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Opprett ny rolle', - 'role_create_success' => 'Rolle opprettet', 'role_delete' => 'Rolle slettet', 'role_delete_confirm' => 'Dette vil slette rollen «:roleName».', 'role_delete_users_assigned' => 'Denne rollen har :userCount kontoer koblet opp mot seg. Velg hvilke rolle du vil flytte disse til.', 'role_delete_no_migration' => "Ikke flytt kontoer", 'role_delete_sure' => 'Er du sikker på at du vil slette rollen?', - 'role_delete_success' => 'Rollen ble slettet', 'role_edit' => 'Endre rolle', 'role_details' => 'Rolledetaljer', 'role_name' => 'Rollenavn', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Egne', 'role_controlled_by_asset' => 'Kontrollert av eiendelen de er lastet opp til', 'role_save' => 'Lagre rolle', - 'role_update_success' => 'Rollen ble oppdatert', 'role_users' => 'Kontoholdere med denne rollen', 'role_users_none' => 'Ingen kontoholdere er gitt denne rollen', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/nl/activities.php b/lang/nl/activities.php index 9b896b2ae..73c41df9d 100644 --- a/lang/nl/activities.php +++ b/lang/nl/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Gebruiker succesvol bijgewerkt', 'user_delete_notification' => 'Gebruiker succesvol verwijderd', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'reageerde op', 'permissions_update' => 'wijzigde machtigingen', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index 4652d1773..777452850 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Bijgewerkt: :timeLength', 'meta_updated_name' => 'Bijgewerkt: :timeLength door :user', 'meta_owned_name' => 'Eigendom van :user', - 'meta_reference_page_count' => 'Naartoe verwezen op 1 pagina|Naartoe verwezen op :count pagina\'s', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Entiteit selecteren', 'entity_select_lack_permission' => 'Je hebt niet de vereiste machtiging om dit item te selecteren', 'images' => 'Afbeeldingen', @@ -174,7 +174,7 @@ return [ 'chapters_delete' => 'Hoofdstuk verwijderen', 'chapters_delete_named' => 'Verwijder hoofdstuk :chapterName', 'chapters_delete_explain' => 'Dit verwijdert het hoofdstuk met de naam \':chapterName\'. Alle pagina\'s die binnen dit hoofdstuk staan, worden ook verwijderd.', - 'chapters_delete_confirm' => 'Weet je zeker dat je dit boek wilt verwijderen?', + 'chapters_delete_confirm' => 'Weet je zeker dat je dit hoofdstuk wilt verwijderen?', 'chapters_edit' => 'Hoofdstuk aanpassen', 'chapters_edit_named' => 'Hoofdstuk :chapterName aanpassen', 'chapters_save' => 'Hoofdstuk opslaan', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 5b05d5be2..644beb4b1 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Rollen', 'role_user_roles' => 'Gebruikersrollen', 'roles_index_desc' => 'Rollen worden gebruikt om gebruikers te groeperen en systeemrechten te geven. Wanneer een gebruiker lid is van meerdere rollen worden de toegekende rechten samengevoegd en erft de gebruiker alle mogelijkheden.', - 'roles_x_users_assigned' => '1 gebruiker toegewezen|:count gebruikers toegewezen', - 'roles_x_permissions_provided' => '1 machtiging|:count machtigingen', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Toegewezen Gebruikers', 'roles_permissions_provided' => 'Verleende Machtigingen', 'role_create' => 'Nieuwe Rol Maken', - 'role_create_success' => 'Rol succesvol aangemaakt', 'role_delete' => 'Rol Verwijderen', 'role_delete_confirm' => 'Dit verwijdert de rol met naam: \':roleName\'.', 'role_delete_users_assigned' => 'Er zijn :userCount gebruikers met deze rol. Selecteer hieronder een nieuwe rol als je deze gebruikers een andere rol wilt geven.', 'role_delete_no_migration' => "Geen gebruikers migreren", 'role_delete_sure' => 'Weet je zeker dat je deze rol wilt verwijderen?', - 'role_delete_success' => 'Rol succesvol verwijderd', 'role_edit' => 'Rol Bewerken', 'role_details' => 'Rol Details', 'role_name' => 'Rolnaam', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Eigen', 'role_controlled_by_asset' => 'Gecontroleerd door de asset waar deze is geüpload', 'role_save' => 'Rol Opslaan', - 'role_update_success' => 'Rol succesvol bijgewerkt', 'role_users' => 'Gebruikers in deze rol', 'role_users_none' => 'Geen enkele gebruiker heeft deze rol', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks zijn een manier om gegevens naar externe URL\'s te sturen wanneer bepaalde acties en gebeurtenissen in het systeem plaatsvinden, wat op gebeurtenissen gebaseerde integratie met externe platforms zoals berichten- of notificatiesystemen mogelijk maakt.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Nieuwe Webhook Maken', 'webhooks_none_created' => 'Er zijn nog geen webhooks aangemaakt.', 'webhooks_edit' => 'Bewerk Webhook', diff --git a/lang/pl/activities.php b/lang/pl/activities.php index 880dc219b..dd50c34ad 100644 --- a/lang/pl/activities.php +++ b/lang/pl/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Użytkownik zaktualizowany pomyślnie', 'user_delete_notification' => 'Użytkownik pomyślnie usunięty', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'skomentował', 'permissions_update' => 'zaktualizował uprawnienia', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index 0063af6d9..af4df68fc 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zaktualizowano :timeLength', 'meta_updated_name' => 'Zaktualizowano :timeLength przez :user', 'meta_owned_name' => 'Właściciel: :user', - 'meta_reference_page_count' => 'Odniesienie na 1 stronie|Odniesienie na :count stronach', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Wybór obiektu', 'entity_select_lack_permission' => 'Nie masz wymaganych uprawnień do wybrania tej pozycji', 'images' => 'Obrazki', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 3a7bd2cbb..1b84a6e16 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Role', 'role_user_roles' => 'Role użytkowników', 'roles_index_desc' => 'Role są używane do grupowania użytkowników i udzielania uprawnień systemowych ich członkom. Gdy użytkownik jest członkiem wielu ról, przyznane uprawnienia będą gromadzone, a użytkownik odziedziczy wszystkie możliwości.', - 'roles_x_users_assigned' => '1 użytkownik przypisany|:count użytkowników przypisanych', - 'roles_x_permissions_provided' => '1 uprawnienie|:count uprawnień', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Przypisani Użytkownicy', 'roles_permissions_provided' => 'Przyznawane Uprawnienia', 'role_create' => 'Utwórz nową rolę', - 'role_create_success' => 'Rola utworzona pomyślnie', 'role_delete' => 'Usuń rolę', 'role_delete_confirm' => 'To spowoduje usunięcie roli \':roleName\'.', 'role_delete_users_assigned' => 'Tę rolę ma przypisanych :userCount użytkowników. Jeśli chcesz zmigrować użytkowników z tej roli, wybierz nową poniżej.', 'role_delete_no_migration' => "Nie migruj użytkowników", 'role_delete_sure' => 'Czy na pewno chcesz usunąć tę rolę?', - 'role_delete_success' => 'Rola usunięta pomyślnie', 'role_edit' => 'Edytuj rolę', 'role_details' => 'Szczegóły roli', 'role_name' => 'Nazwa roli', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Własne', 'role_controlled_by_asset' => 'Kontrolowane przez zasób, do którego zostały udostępnione', 'role_save' => 'Zapisz rolę', - 'role_update_success' => 'Rola zapisana pomyślnie', 'role_users' => 'Użytkownicy w tej roli', 'role_users_none' => 'Brak użytkowników zapisanych do tej roli', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooki', 'webhooks_index_desc' => 'Webhooki to sposób na wysyłanie danych do zewnętrznych adresów URL, gdy pewne działania i zdarzenia zachodzą w ramach systemu, co umożliwia integrację zdarzeń w systemie z zewnętrznymi platformami, takimi jak systemy wysyłania wiadomości lub powiadamiania.', - 'webhooks_x_trigger_events' => '1 zdarzenie wyzwalacza|:count zdarzeń wyzwalacza', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Utwórz nowy Webhook', 'webhooks_none_created' => 'Nie utworzono jeszcze żadnych webhooków.', 'webhooks_edit' => 'Edytuj Webhook', diff --git a/lang/pt/activities.php b/lang/pt/activities.php index ac4cdb1d5..a362ae4bd 100644 --- a/lang/pt/activities.php +++ b/lang/pt/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Utilizador atualizado com sucesso', 'user_delete_notification' => 'Utilizador removido com sucesso', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'comentado a', 'permissions_update' => 'permissões atualizadas', diff --git a/lang/pt/auth.php b/lang/pt/auth.php index a88bf6ab8..a21eb701b 100644 --- a/lang/pt/auth.php +++ b/lang/pt/auth.php @@ -89,7 +89,7 @@ return [ 'mfa_setup_action' => 'Configuração', 'mfa_backup_codes_usage_limit_warning' => 'Você tem menos de 5 códigos de backup restantes, Por favor, gere e armazene um novo conjunto antes de esgotar os códigos para evitar estar bloqueado para fora da sua conta.', 'mfa_option_totp_title' => 'Aplicação móvel', - 'mfa_option_totp_desc' => 'Para usar a autenticação multi-fator, você precisará de um aplicativo móvel que suporte TOTP como o Autenticador do Google, Authy ou o autenticador Microsoft.', + 'mfa_option_totp_desc' => 'Para usar a autenticação multi-fator, você precisa de uma aplicação móvel que suporte TOTP como o Autenticador do Google, Authy ou o autenticador Microsoft.', 'mfa_option_backup_codes_title' => 'Códigos de Backup', 'mfa_option_backup_codes_desc' => 'Armazene com segurança um conjunto de códigos de backup únicos que você pode inserir para verificar sua identidade.', 'mfa_gen_confirm_and_enable' => 'Confirmar e ativar', @@ -97,21 +97,21 @@ return [ 'mfa_gen_backup_codes_desc' => 'Armazene a lista de códigos abaixo em um lugar seguro. Ao acessar o sistema você poderá usar um dos códigos como um segundo mecanismo de autenticação.', 'mfa_gen_backup_codes_download' => 'Transferir códigos', 'mfa_gen_backup_codes_usage_warning' => 'Cada código só pode ser usado uma vez', - 'mfa_gen_totp_title' => 'Configuração de aplicativo móvel', - 'mfa_gen_totp_desc' => 'Para usar a autenticação multi-fator, você precisará de um aplicativo móvel que suporte TOTP como o Autenticador do Google, Authy ou o autenticador Microsoft.', - 'mfa_gen_totp_scan' => 'Leia o código QR abaixo usando seu aplicativo de autenticação preferido para começar.', + 'mfa_gen_totp_title' => 'Configuração da aplicação móvel', + 'mfa_gen_totp_desc' => 'Para usar a autenticação multi-fator, precisará de uma aplicação móvel que suporte TOTP como o Autenticador do Google, Authy ou o autenticador Microsoft.', + 'mfa_gen_totp_scan' => 'Leia o código QR abaixo usando a sua aplicação de autenticação preferida para começar.', 'mfa_gen_totp_verify_setup' => 'Verificar configuração', - 'mfa_gen_totp_verify_setup_desc' => 'Verifique se tudo está funcionando digitando um código, gerado dentro do seu aplicativo de autenticação, na caixa de entrada abaixo:', - 'mfa_gen_totp_provide_code_here' => 'Forneça o código gerado pelo aplicativo aqui', + 'mfa_gen_totp_verify_setup_desc' => 'Verifique se funciona tudo, digitando um código, gerado dentro da sua aplicação de autenticação, na caixa de entrada abaixo:', + 'mfa_gen_totp_provide_code_here' => 'Forneça aqui, o código gerado pela sua aplicação', 'mfa_verify_access' => 'Verificar Acesso', 'mfa_verify_access_desc' => 'Sua conta de usuário requer que você confirme sua identidade por meio de um nível adicional de verificação antes de conceder o acesso. Verifique o uso de um dos métodos configurados para continuar.', 'mfa_verify_no_methods' => 'Nenhum método configurado', 'mfa_verify_no_methods_desc' => 'Nenhum método de autenticação de vários fatores foi encontrado para a sua conta. Você precisará configurar pelo menos um método antes de ganhar acesso.', - 'mfa_verify_use_totp' => 'Verificar usando um aplicativo móvel', + 'mfa_verify_use_totp' => 'Verificar usando uma aplicação móvel', 'mfa_verify_use_backup_codes' => 'Verificar usando código de backup', 'mfa_verify_backup_code' => 'Código de backup', 'mfa_verify_backup_code_desc' => 'Insira um dos seus códigos de backup restantes abaixo:', 'mfa_verify_backup_code_enter_here' => 'Insira o código de backup aqui', - 'mfa_verify_totp_desc' => 'Digite o código, gerado através do seu aplicativo móvel, abaixo:', + 'mfa_verify_totp_desc' => 'Digite abaixo, o código gerado através da sua aplicação móvel:', 'mfa_setup_login_notification' => 'Método de multi-fatores configurado, por favor faça login novamente usando o método configurado.', ]; diff --git a/lang/pt/entities.php b/lang/pt/entities.php index bc92ea47e..4c8f87faf 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atualizado :timeLength', 'meta_updated_name' => 'Atualizado :timeLength por :user', 'meta_owned_name' => 'Propriedade de :user', - 'meta_reference_page_count' => 'Referenciado em 1 página|Referenciado em :count páginas', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Seleção de Entidade', 'entity_select_lack_permission' => 'Não tem as permissões necessárias para selecionar este item', 'images' => 'Imagens', @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'Pesquisar neste livro', 'books_navigation' => 'Navegação do Livro', 'books_sort' => 'Ordenar Conteúdos do Livro', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'Mova capítulos e páginas de um livro para reorganizar o seu conteúdo. É possível acrescentar outros livros, o que permite uma movimentação fácil de capítulos e páginas entre livros.', 'books_sort_named' => 'Ordenar Livro :bookName', 'books_sort_name' => 'Ordenar por Nome', 'books_sort_created' => 'Ordenar por Data de Criação', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'Capítulos por Último', 'books_sort_show_other' => 'Mostrar Outros Livros', 'books_sort_save' => 'Guardar Nova Ordenação', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'Adicione outros livros aqui para incluí-los na operação de classificação e permitir a reorganização fácil de todos os livros.', + 'books_sort_move_up' => 'Mover para Cima', + 'books_sort_move_down' => 'Mover para baixo', + 'books_sort_move_prev_book' => 'Mover para o Livro Anterior', + 'books_sort_move_next_book' => 'Mover para o próximo livro', + 'books_sort_move_prev_chapter' => 'Mover para o Capítulo Anterior', + 'books_sort_move_next_chapter' => 'Mover para o próximo Capítulo', + 'books_sort_move_book_start' => 'Mover para o início do livro', + 'books_sort_move_book_end' => 'Mover para o final do livro', + 'books_sort_move_before_chapter' => 'Mover para Antes do Capítulo', + 'books_sort_move_after_chapter' => 'Mover para Depois do Capítulo', 'books_copy' => 'Copiar livro', 'books_copy_success' => 'Livro criado com sucesso', diff --git a/lang/pt/errors.php b/lang/pt/errors.php index 604450866..66dc0c49d 100644 --- a/lang/pt/errors.php +++ b/lang/pt/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Ocorreu um erro no carregamento da imagem', 'image_upload_type_error' => 'O tipo de imagem enviada é inválida', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Dados de desenho não puderam ser carregados. Talvez o arquivo de desenho não exista mais ou não tenha permissão para aceder-lhe.', // Attachments 'attachment_not_found' => 'Anexo não encontrado', diff --git a/lang/pt/settings.php b/lang/pt/settings.php index 9b43c46ed..3c678b8da 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -33,9 +33,9 @@ return [ 'app_custom_html_desc' => 'Quaisquer conteúdos aqui adicionados serão inseridos no final da secção <head> de cada página. Esta é uma maneira útil de sobrescrever estilos e adicionar códigos de análise de site.', 'app_custom_html_disabled_notice' => 'O conteúdo personalizado do <head> HTML está desativado nesta página de configurações, para garantir que quaisquer alterações que acabem maliciosas possam ser revertidas.', 'app_logo' => 'Logo da Aplicação', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_logo_desc' => 'Isto é usado na barra de cabeçalho da aplicação, entre outras áreas. Esta imagem deve ter 86px de altura. Imagens grandes serão redimensionadas.', + 'app_icon' => 'Ícone da aplicação', + 'app_icon_desc' => 'Este ícone é usado para guias e ícones de atalhos do navegador. A imagem para o ícone deve ser quadrada, de lado 256px e com o formato PNG.', 'app_homepage' => 'Página Inicial', 'app_homepage_desc' => 'Selecione uma opção para ser exibida como página inicial em vez da padrão. Permissões de página serão ignoradas para as páginas selecionadas.', 'app_homepage_select' => 'Selecione uma página', @@ -46,15 +46,15 @@ return [ 'app_footer_links_add' => 'Adicionar Link de Rodapé', 'app_disable_comments' => 'Desativar Comentários', 'app_disable_comments_toggle' => 'Desativar comentários', - 'app_disable_comments_desc' => 'Desativar comentários em todas as páginas no aplicativo.<br> Comentários existentes não serão exibidos.', + 'app_disable_comments_desc' => 'Desativar comentários em todas as páginas da aplicação.<br> Comentários existentes não serão exibidos.', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'color_scheme' => 'Esquema de cores da aplicação', + 'color_scheme_desc' => 'Define as cores a serem usadas na interface do BookStack. As cores podem ser configuradas separadamente para modos escuro e claro para melhor se adequar ao tema e garantir a legibilidade.', + 'ui_colors_desc' => 'Defina a cor primária e a cor padrão do link para o BookStack. A cor primária é usada principalmente para o banner do cabeçalho, botões e decorações da interface. A cor padrão do link é usada para links e ações baseados em texto, tanto no conteúdo escrito quanto na interface do BookStack.', + 'app_color' => 'Cor primária', + 'link_color' => 'Cor padrão do link', + 'content_colors_desc' => 'Definir cores para todos os elementos na hierarquia da organização da página. Escolher cores com um brilho semelhante às cores padrão é recomendado para a legibilidade.', 'bookshelf_color' => 'Cor da Prateleira', 'book_color' => 'Cor do Livro', 'chapter_color' => 'Cor do Capítulo', @@ -62,11 +62,11 @@ return [ 'page_draft_color' => 'Cor do Rascunho', // Registration Settings - 'reg_settings' => 'Cadastro', - 'reg_enable' => 'Habilitar Cadastro', - 'reg_enable_toggle' => 'Habilitar cadastro', - 'reg_enable_desc' => 'Quando o cadastro é habilitado, visitantes poderão cadastrar-se como usuários do aplicativo. Realizado o cadastro, recebem um único cargo padrão.', - 'reg_default_role' => 'Cargo padrão para usuários após o cadastro', + 'reg_settings' => 'Inscrição', + 'reg_enable' => 'Permitir inscrições', + 'reg_enable_toggle' => 'Permitir inscrições', + 'reg_enable_desc' => 'Quando o registo é ativado, os visitantes poderão registar se como utilizadores padrão da aplicação.', + 'reg_default_role' => 'Papel por omissão apôs registo', 'reg_enable_external_warning' => 'A opção acima é ignorada enquanto a autenticação externa LDAP ou SAML estiver ativa. Contas de usuários para membros não existentes serão criadas automaticamente se a autenticação pelo sistema externo em uso for bem sucedida.', 'reg_email_confirmation' => 'Confirmação de E-mail', 'reg_email_confirmation_toggle' => 'Requerer confirmação de e-mail', @@ -138,18 +138,16 @@ return [ 'roles' => 'Cargos', 'role_user_roles' => 'Cargos de Utilizador', 'roles_index_desc' => 'Papéis são usados para agrupar utilizadores & fornecer permissão ao sistema para os seus membros. Quando um utilizador é membro de múltiplas funções, os privilégios concedidos irão acumular e o utilizador herdará todas as habilidades.', - 'roles_x_users_assigned' => '1 utilizador atribuído|:count utilizadores atribuídos', - 'roles_x_permissions_provided' => '1 permissão|:count permissões', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Utilizadores atribuídos', 'roles_permissions_provided' => 'Permissões fornecidas', 'role_create' => 'Criar novo Cargo', - 'role_create_success' => 'Cargo criado com sucesso', 'role_delete' => 'Excluir Cargo', 'role_delete_confirm' => 'A ação vai eliminar o cargo de nome \':roleName\'.', 'role_delete_users_assigned' => 'Esse cargo tem :userCount utilizadores vinculados nele. Se quiser migrar utilizadores deste cargo para outro, selecione um novo cargo.', 'role_delete_no_migration' => "Não migrar utilizadores", 'role_delete_sure' => 'Tem certeza que deseja excluir este cargo?', - 'role_delete_success' => 'Cargo excluído com sucesso', 'role_edit' => 'Editar Cargo', 'role_details' => 'Detalhes do Cargo', 'role_name' => 'Nome do Cargo', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelo ativo para o qual eles são enviados', 'role_save' => 'Guardar Cargo', - 'role_update_success' => 'Cargo atualizado com sucesso', 'role_users' => 'Utilizadores com este cargo', 'role_users_none' => 'Nenhum utilizador está atualmente vinculado a este cargo', @@ -198,7 +195,7 @@ return [ 'users_external_auth_id' => 'ID de Autenticação Externa', 'users_external_auth_id_desc' => 'Este ID é utilizado para relacionar um utilizador ao comunicar com um sistema de autenticação externo.', 'users_password_warning' => 'Apenas preencha os dados abaixo caso queira modificar a sua palavra-passe.', - 'users_system_public' => 'Este utilizador representa quaisquer convidados que visitam a aplicação. Não pode ser utilizado para efetuar autenticação mas é automaticamente atribuído.', + 'users_system_public' => 'Este utilizador representa quaisquer convidados que visitam a aplicação. Não pode ser utilizado para efetuar autenticação, mas é automaticamente atribuído.', 'users_delete' => 'Eliminar Utilizador', 'users_delete_named' => 'Eliminar :userName', 'users_delete_warning' => 'A ação vai eliminar completamente o utilizador de nome \':userName\' do sistema.', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks são uma maneira de enviar dados para URLs externas quando certas ações e eventos ocorrem no sistema. Isto permite uma integração baseada em eventos com plataformas externas como mensagens ou sistemas de notificação.', - 'webhooks_x_trigger_events' => '1 acionador|:count acionadores', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Criar um novo webhook', 'webhooks_none_created' => 'Ainda nenhum webhooks foi criado.', 'webhooks_edit' => 'Editar Webhook', diff --git a/lang/pt_BR/activities.php b/lang/pt_BR/activities.php index 3debcc4e6..870f5ab73 100644 --- a/lang/pt_BR/activities.php +++ b/lang/pt_BR/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Usuário atualizado com sucesso', 'user_delete_notification' => 'Usuário removido com sucesso', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'comentou em', 'permissions_update' => 'atualizou permissões', diff --git a/lang/pt_BR/entities.php b/lang/pt_BR/entities.php index 216d8a41d..6654f9d6f 100644 --- a/lang/pt_BR/entities.php +++ b/lang/pt_BR/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atualizado :timeLength', 'meta_updated_name' => 'Atualizado :timeLength por :user', 'meta_owned_name' => 'De :user', - 'meta_reference_page_count' => 'Referenciado em 1 página|Referenciado em :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Seleção de Entidade', 'entity_select_lack_permission' => 'Você não tem as permissões necessárias para selecionar este item', 'images' => 'Imagens', diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index e82d8f4b0..3242316e2 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Cargos', 'role_user_roles' => 'Cargos de Usuário', 'roles_index_desc' => 'As funções são usadas para agrupar usuários & fornecer permissão de sistema a seus membros. Quando um usuário é membro de várias funções, os privilégios concedidos serão acumulados e o usuário herdará todas as habilidades.', - 'roles_x_users_assigned' => '1 usuário atribuído|:count usuários atribuídos', - 'roles_x_permissions_provided' => '1 permissão|:count permissões', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Usuários atribuídos', 'roles_permissions_provided' => 'Permissões fornecidas', 'role_create' => 'Criar novo Cargo', - 'role_create_success' => 'Cargo criado com sucesso', 'role_delete' => 'Excluir Cargo', 'role_delete_confirm' => 'A ação vai excluír o cargo de nome \':roleName\'.', 'role_delete_users_assigned' => 'Esse cargo tem :userCount usuários vinculados a ele. Se quiser migrar usuários desse cargo para outro, selecione um novo cargo.', 'role_delete_no_migration' => "Não migre os usuários", 'role_delete_sure' => 'Tem certeza que deseja excluir esse cargo?', - 'role_delete_success' => 'Cargo excluído com sucesso', 'role_edit' => 'Editar Cargo', 'role_details' => 'Detalhes do Cargo', 'role_name' => 'Nome do Cargo', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Próprio', 'role_controlled_by_asset' => 'Controlado pelos ativos nos quais o upload foi realizado', 'role_save' => 'Salvar Cargo', - 'role_update_success' => 'Cargo atualizado com sucesso', 'role_users' => 'Usuários com este cargo', 'role_users_none' => 'Nenhum usuário está atualmente vinculado a este cargo', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Os webhooks são uma maneira de enviar dados para URLs externos quando certas ações e eventos ocorrem dentro do sistema, o que permite a integração baseada em eventos com plataformas externas, como sistemas de mensagens ou notificação.', - 'webhooks_x_trigger_events' => '1 evento de gatilho|:count evento de gatilho', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Criar novo webhook', 'webhooks_none_created' => 'Nenhum webhooks foi criado ainda.', 'webhooks_edit' => 'Editar webhook', diff --git a/lang/ro/activities.php b/lang/ro/activities.php index 86221fce5..488f7062d 100644 --- a/lang/ro/activities.php +++ b/lang/ro/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Utilizator actualizat cu succes', 'user_delete_notification' => 'Utilizator eliminat cu succes', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'a comentat la', 'permissions_update' => 'a actualizat permisiunile', diff --git a/lang/ro/entities.php b/lang/ro/entities.php index 9f0ca3571..5b8ffd473 100644 --- a/lang/ro/entities.php +++ b/lang/ro/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualizat :timeLungime', 'meta_updated_name' => 'Actualizat :timeLength de :user', 'meta_owned_name' => 'Deținut de :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Selectare entitate', 'entity_select_lack_permission' => 'Nu ai drepturile necesare pentru a selecta acest element', 'images' => 'Imagini', diff --git a/lang/ro/settings.php b/lang/ro/settings.php index feb57a31b..d90cae4a9 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roluri', 'role_user_roles' => 'Roluri utilizator', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Crează rol nou', - 'role_create_success' => 'Rol creat cu succes', 'role_delete' => 'Șterge rolul', 'role_delete_confirm' => 'Aceasta va șterge rolul cu numele \':roleName\'.', 'role_delete_users_assigned' => 'Acest rol are :userCount utilizatori asociați. Dacă vrei să migrezi utilizatorii din acest rol, selectează un nou rol mai jos.', 'role_delete_no_migration' => "Nu migra utilizatorii", 'role_delete_sure' => 'Ești sigur că vrei să ștergi acest rol?', - 'role_delete_success' => 'Rolul a fost șters', 'role_edit' => 'Editează Rol', 'role_details' => 'Detalii rol', 'role_name' => 'Nume rol', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Propriu', 'role_controlled_by_asset' => 'Controlat de activele pe care sunt încărcate', 'role_save' => 'Salvare rol', - 'role_update_success' => 'Rol actualizat cu succes', 'role_users' => 'Utilizatori cu acest rol', 'role_users_none' => 'Nici un utilizator nu este asociat acestui rol', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhook-uri', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Creează un nou Webhook', 'webhooks_none_created' => 'Nu au fost create webhook-uri.', 'webhooks_edit' => 'Editare Webhook', diff --git a/lang/ru/activities.php b/lang/ru/activities.php index 3290ad36b..19ca6ba61 100644 --- a/lang/ru/activities.php +++ b/lang/ru/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Пользователь успешно обновлен', 'user_delete_notification' => 'Пользователь успешно удален', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'прокомментировал', 'permissions_update' => 'обновил разрешения', diff --git a/lang/ru/entities.php b/lang/ru/entities.php index 052a317bb..21ae4fdb9 100644 --- a/lang/ru/entities.php +++ b/lang/ru/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Обновлено :timeLength', 'meta_updated_name' => ':user обновил :timeLength', 'meta_owned_name' => 'Владелец :user', - 'meta_reference_page_count' => 'Ссылается на 1 страницу|Ссылается на :count страниц', + 'meta_reference_page_count' => 'Ссылается на :count страницу|Ссылается на :count страниц', 'entity_select' => 'Выбор объекта', 'entity_select_lack_permission' => 'У вас нет разрешения на выбор этого элемента', 'images' => 'Изображения', @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'Поиск в этой книге', 'books_navigation' => 'Навигация по книге', 'books_sort' => 'Сортировка содержимого книги', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'Переместите разделы и страницы в книге, чтобы изменить содержание. Могут быть добавлены другие книги, что позволяет легко перемещать разделы и страницы между книгами.', 'books_sort_named' => 'Сортировка книги :bookName', 'books_sort_name' => 'По имени', 'books_sort_created' => 'По дате создания', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'Главы в конце', 'books_sort_show_other' => 'Показать другие книги', 'books_sort_save' => 'Сохранить новый порядок', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'Добавьте другие книги здесь, чтобы включить их в сортировку, и позволить легко реорганизовать книгу.', + 'books_sort_move_up' => 'Переместить вверх', + 'books_sort_move_down' => 'Переместить вниз', + 'books_sort_move_prev_book' => 'Переместить в предыдущую книгу', + 'books_sort_move_next_book' => 'Переместить в следующую книгу', + 'books_sort_move_prev_chapter' => 'Переместить в предыдущую главу', + 'books_sort_move_next_chapter' => 'Переместить в следующую главу', + 'books_sort_move_book_start' => 'Переместить в начало книги', + 'books_sort_move_book_end' => 'Переместить в конец книги', + 'books_sort_move_before_chapter' => 'Переместить перед главой', + 'books_sort_move_after_chapter' => 'Переместить после главы', 'books_copy' => 'Копировать книгу', 'books_copy_success' => 'Книга успешно скопирована', diff --git a/lang/ru/errors.php b/lang/ru/errors.php index 23a43c126..8aa231d42 100644 --- a/lang/ru/errors.php +++ b/lang/ru/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Произошла ошибка при загрузке изображения', 'image_upload_type_error' => 'Неправильный тип загружаемого изображения', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Данные чертежа не могут быть загружены. Возможно, файл чертежа больше не существует или у вас нет разрешения на доступ к нему.', // Attachments 'attachment_not_found' => 'Вложение не найдено', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index 912c92b80..43cc8a05c 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -33,9 +33,9 @@ return [ 'app_custom_html_desc' => 'Любой контент, добавленный здесь, будет вставлен в нижнюю часть раздела <head> каждой страницы. Это удобно для переопределения стилей или добавления кода аналитики.', 'app_custom_html_disabled_notice' => 'Пользовательский контент заголовка HTML отключен на этой странице, чтобы гарантировать отмену любых критических изменений.', 'app_logo' => 'Логотип приложения', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_logo_desc' => 'Используется в строке заголовка приложения, среди прочих областей. Это изображение должно быть 86px в высоте. Большие изображения будут масштабироваться вниз.', + 'app_icon' => 'Иконка приложения', + 'app_icon_desc' => 'Эта иконка используется для браузерных вкладок и иконок ярлыков. Должно быть 256px квадратное PNG изображение.', 'app_homepage' => 'Стартовая страница приложения', 'app_homepage_desc' => 'Выберите страницу, которая будет отображаться на главной странице вместо стандартной. Права на страницы игнорируются для выбранных страниц.', 'app_homepage_select' => 'Выберите страницу', @@ -49,12 +49,12 @@ return [ 'app_disable_comments_desc' => 'Отключение комментариев на всех страницах. Существующие комментарии будут скрыты.', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'color_scheme' => 'Цветовая схема приложения', + 'color_scheme_desc' => 'Установите цвета для использования в интерфейсе BookStack. Цвета могут быть настроены отдельно для темных и светлых режимов, чтобы наилучшим образом соответствовать теме и обеспечить разборчивость.', + 'ui_colors_desc' => 'Установите основной цвет и цвет ссылок по умолчанию для BookStack. Основной цвет в основном используется для заголовка баннера, кнопок и декораций интерфейса. Цвет ссылок по умолчанию используется для текстовых ссылок и действий как в письменном содержании, так и в интерфейсе Bookstack.', + 'app_color' => 'Основной цвет', + 'link_color' => 'Цвет ссылки', + 'content_colors_desc' => 'Задает цвета для всех элементов организационной иерархии страницы. Для удобства чтения рекомендуется выбирать цвета, яркость которых близка к цветам по умолчанию.', 'bookshelf_color' => 'Цвет полки', 'book_color' => 'Цвет книги', 'chapter_color' => 'Цвет главы', @@ -138,18 +138,16 @@ return [ 'roles' => 'Роли', 'role_user_roles' => 'Роли пользователей', 'roles_index_desc' => 'Роли используются для группировки пользователей и предоставления системных разрешений их участникам. Когда пользователь является членом нескольких ролей, предоставленные разрешения объединяются, и пользователь наследует все возможности.', - 'roles_x_users_assigned' => '1 пользователь назначен|:count назначенных пользователей', - 'roles_x_permissions_provided' => '1 разрешение|:count разрешений', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count разрешение|:count разрешений', 'roles_assigned_users' => 'Назначенные пользователи', 'roles_permissions_provided' => 'Предоставленные разрешения', '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' => 'Название роли', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Владелец', 'role_controlled_by_asset' => 'Контролируется активом, в который они загружены', 'role_save' => 'Сохранить роль', - 'role_update_success' => 'Роль успешно обновлена', 'role_users' => 'Пользователи с данной ролью', 'role_users_none' => 'Нет пользователей с данной ролью', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Вебхуки', 'webhooks_index_desc' => 'Webhooks - это способ посылать данные на внешние URL-адреса при возникновении определенных действий и событий в системе, которые позволяют интегрировать события с внешними платформами, такими как системы обмена сообщениями или уведомлениями.', - 'webhooks_x_trigger_events' => '1 событие триггер|:count событий триггера', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Создать вебхук', 'webhooks_none_created' => 'Вебхуки еще не созданы.', 'webhooks_edit' => 'Редактировать вебхук', diff --git a/lang/sk/activities.php b/lang/sk/activities.php index 9524b68dd..388312d36 100644 --- a/lang/sk/activities.php +++ b/lang/sk/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Používateľ úspešne upravený', 'user_delete_notification' => 'Používateľ úspešne zmazaný', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'komentoval(a)', 'permissions_update' => 'aktualizované oprávnenia', diff --git a/lang/sk/entities.php b/lang/sk/entities.php index a6e203920..a8b897ab2 100644 --- a/lang/sk/entities.php +++ b/lang/sk/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aktualizované :timeLength', 'meta_updated_name' => 'Aktualizované :timeLength používateľom :user', 'meta_owned_name' => 'Vlastník :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Entita vybraná', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Obrázky', diff --git a/lang/sk/settings.php b/lang/sk/settings.php index 9116933df..a3852438a 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roly', 'role_user_roles' => 'Používateľské roly', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Vytvoriť novú rolu', - 'role_create_success' => 'Rola úspešne vytvorená', 'role_delete' => 'Zmazať rolu', 'role_delete_confirm' => 'Toto zmaže rolu menom \':roleName\'.', 'role_delete_users_assigned' => 'Túto rolu má priradenú :userCount používateľov. Ak chcete premigrovať používateľov z tejto roly, vyberte novú rolu nižšie.', 'role_delete_no_migration' => "Nemigrovať používateľov", 'role_delete_sure' => 'Ste si istý, že chcete zmazať túto rolu?', - 'role_delete_success' => 'Rola úspešne zmazaná', 'role_edit' => 'Upraviť rolu', 'role_details' => 'Detaily roly', 'role_name' => 'Názov roly', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Vlastné', 'role_controlled_by_asset' => 'Regulované zdrojom, do ktorého sú nahrané', 'role_save' => 'Uložiť rolu', - 'role_update_success' => 'Roly úspešne aktualizované', 'role_users' => 'Používatelia s touto rolou', 'role_users_none' => 'Žiadni používatelia nemajú priradenú túto rolu', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/sl/activities.php b/lang/sl/activities.php index b0bf156cd..e612b5c1b 100644 --- a/lang/sl/activities.php +++ b/lang/sl/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'User successfully updated', 'user_delete_notification' => 'User successfully removed', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'komentar na', 'permissions_update' => 'pravice so posodobljene', diff --git a/lang/sl/entities.php b/lang/sl/entities.php index 471c2cc3e..087a13330 100644 --- a/lang/sl/entities.php +++ b/lang/sl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Posodobljeno :timeLength', 'meta_updated_name' => 'Posodobil :timeLength uporabnik :user', 'meta_owned_name' => 'V lasti :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Izbira entitete', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Slike', diff --git a/lang/sl/settings.php b/lang/sl/settings.php index 201bf53f2..042a945dc 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Vloge', 'role_user_roles' => 'Vloge uporabnika', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Ustvari novo vlogo', - 'role_create_success' => 'Vloga uspešno ustvarjena', 'role_delete' => 'Brisanje vloge', 'role_delete_confirm' => 'Izbrisana bo vloga z imenom \':roleName\'.', 'role_delete_users_assigned' => 'Ta vloga ima dodeljenih :userCount uporabnikov. V kolikor želite uporabnike preseliti iz te vloge, spodaj izberite novo vlogo.', 'role_delete_no_migration' => "Ne prenašaj uporabnikov", 'role_delete_sure' => 'Ali ste prepričani, da želite izbrisati to vlogo?', - 'role_delete_success' => 'Vloga uspešno izbrisana', 'role_edit' => 'Uredi vlogo', 'role_details' => 'Podrobnosti vloge', 'role_name' => 'Naziv vloge', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Lasten', 'role_controlled_by_asset' => 'Nadzira ga sredstvo, v katerega so naloženi', 'role_save' => 'Shrani vlogo', - 'role_update_success' => 'Vloga uspešno posodobljena', 'role_users' => 'Uporabniki v tej vlogi', 'role_users_none' => 'Tej vlogi trenutno ni dodeljen noben uporabnik', @@ -253,7 +250,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/sv/activities.php b/lang/sv/activities.php index f730bf5ae..9e355ee74 100644 --- a/lang/sv/activities.php +++ b/lang/sv/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Användaren har uppdaterats', 'user_delete_notification' => 'Användaren har tagits bort', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'kommenterade', 'permissions_update' => 'uppdaterade behörigheter', diff --git a/lang/sv/entities.php b/lang/sv/entities.php index 78f3211ed..1aa3a56c3 100644 --- a/lang/sv/entities.php +++ b/lang/sv/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Uppdaterad :timeLength', 'meta_updated_name' => 'Uppdaterad :timeLength av :user', 'meta_owned_name' => 'Ägs av :user', - 'meta_reference_page_count' => 'Referas till på en sida|Refereras till på :count sidor', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Välj enhet', 'entity_select_lack_permission' => 'Du har inte den behörighet som krävs för att välja det här objektet', 'images' => 'Bilder', diff --git a/lang/sv/settings.php b/lang/sv/settings.php index 0e4b99e89..c5c91692c 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roller', 'role_user_roles' => 'Användarroller', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Skapa ny roll', - 'role_create_success' => 'Rollen har skapats', 'role_delete' => 'Ta bort roll', 'role_delete_confirm' => 'Rollen med namn \':roleName\' kommer att tas bort.', 'role_delete_users_assigned' => 'Det finns :userCount användare som tillhör den här rollen. Om du vill migrera användarna från den här rollen, välj en ny roll nedan.', 'role_delete_no_migration' => "Migrera inte användare", 'role_delete_sure' => 'Är du säker på att du vill ta bort den här rollen?', - 'role_delete_success' => 'Rollen har tagits bort', 'role_edit' => 'Redigera roll', 'role_details' => 'Om rollen', 'role_name' => 'Rollens namn', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Egna', 'role_controlled_by_asset' => 'Kontrolleras av den sida de laddas upp till', 'role_save' => 'Spara roll', - 'role_update_success' => 'Rollen har uppdaterats', 'role_users' => 'Användare med denna roll', 'role_users_none' => 'Inga användare tillhör den här rollen', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Skapa ny webhook', 'webhooks_none_created' => 'Inga webhooks har skapats än.', 'webhooks_edit' => 'Redigera webhook', diff --git a/lang/tr/activities.php b/lang/tr/activities.php index dddd111a7..f06117eef 100644 --- a/lang/tr/activities.php +++ b/lang/tr/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Kullanıcı başarıyla güncellendi', 'user_delete_notification' => 'Kullanıcı başarıyla silindi', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'yorum yaptı', 'permissions_update' => 'güncellenmiş izinler', diff --git a/lang/tr/entities.php b/lang/tr/entities.php index b0bb66d34..03c344610 100644 --- a/lang/tr/entities.php +++ b/lang/tr/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => ':timeLength güncellendi', 'meta_updated_name' => ':user tarafından :timeLength güncellendi', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Öge Seçimi', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Görseller', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index 9c70e8070..a9b112164 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roller', 'role_user_roles' => 'Kullanıcı Rolleri', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Yeni Rol Oluştur', - 'role_create_success' => 'Rol, başarıyla oluşturuldu', 'role_delete' => 'Rolü Sil', 'role_delete_confirm' => 'Bu işlem, \':roleName\' adlı rolü silecektir.', 'role_delete_users_assigned' => 'Bu role atanmış :userCount adet kullanıcı var. Eğer bu kullanıcıların rollerini değiştirmek istiyorsanız, aşağıdan yeni bir rol seçin.', 'role_delete_no_migration' => "Kullanıcıları taşıma", 'role_delete_sure' => 'Bu rolü silmek istediğinize emin misiniz?', - 'role_delete_success' => 'Rol başarıyla silindi', 'role_edit' => 'Rolü Düzenle', 'role_details' => 'Rol Detayları', 'role_name' => 'Rol Adı', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Kendine Ait', 'role_controlled_by_asset' => 'Yüklendikleri varlık tarafından kontrol ediliyor', 'role_save' => 'Rolü Kaydet', - 'role_update_success' => 'Rol başarıyla güncellendi', 'role_users' => 'Bu roldeki kullanıcılar', 'role_users_none' => 'Bu role henüz bir kullanıcı atanmadı', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/uk/activities.php b/lang/uk/activities.php index e80396ca6..54ef3b7cf 100644 --- a/lang/uk/activities.php +++ b/lang/uk/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Користувача було успішно оновлено', 'user_delete_notification' => 'Користувача успішно видалено', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'прокоментував', 'permissions_update' => 'оновив дозволи', diff --git a/lang/uk/entities.php b/lang/uk/entities.php index b165ce7d3..4d56a879c 100644 --- a/lang/uk/entities.php +++ b/lang/uk/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Оновлено :timeLength', 'meta_updated_name' => ':user оновив :timeLength', 'meta_owned_name' => 'Власник :user', - 'meta_reference_page_count' => 'Посилання на 1 сторінку|Посилання на :count сторінок', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Вибір об\'єкта', 'entity_select_lack_permission' => 'У вас немає необхідних прав для вибору цього елемента', 'images' => 'Зображення', diff --git a/lang/uk/settings.php b/lang/uk/settings.php index 49301b68a..e22eb6c8c 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Ролі', 'role_user_roles' => 'Ролі користувача', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Створити нову роль', - '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' => 'Назва ролі', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Власне', 'role_controlled_by_asset' => 'Контролюється за об\'єктом, до якого вони завантажуються', 'role_save' => 'Зберегти роль', - 'role_update_success' => 'Роль успішно оновлена', 'role_users' => 'Користувачі в цій ролі', 'role_users_none' => 'Наразі жоден користувач не призначений для цієї ролі', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Веб-хуки', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Створити новий Веб-хук', 'webhooks_none_created' => 'Немає створених Веб-хуків.', 'webhooks_edit' => 'Редагувати Веб-хук', diff --git a/lang/uz/activities.php b/lang/uz/activities.php index 31de6af35..bd024aae7 100644 --- a/lang/uz/activities.php +++ b/lang/uz/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Foydalanuvchi muvaffaqiyatli yangilandi', 'user_delete_notification' => 'Foydalanuvchi muvaffaqiyatli olib tashlandi', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'fikr qoldirdi', 'permissions_update' => 'yangilangan huquqlar', diff --git a/lang/uz/entities.php b/lang/uz/entities.php index 73486a8ed..30a415b86 100644 --- a/lang/uz/entities.php +++ b/lang/uz/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => ':timeLength da yangilangan', 'meta_updated_name' => ':user tomonidan :timeLength da yangilangan', 'meta_owned_name' => 'Egasi :user', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Rasmlar', diff --git a/lang/uz/settings.php b/lang/uz/settings.php index 6f4376d42..76e689b6d 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Roles', 'role_user_roles' => 'User Roles', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Create New Role', - 'role_create_success' => 'Role successfully created', 'role_delete' => 'Delete Role', 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.', 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', 'role_delete_no_migration' => "Don't migrate users", 'role_delete_sure' => 'Are you sure you want to delete this role?', - 'role_delete_success' => 'Role successfully deleted', 'role_edit' => 'Edit Role', 'role_details' => 'Role Details', 'role_name' => 'Role Name', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Own', 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', 'role_save' => 'Save Role', - 'role_update_success' => 'Role successfully updated', 'role_users' => 'Users in this role', 'role_users_none' => 'No users are currently assigned to this role', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Create New Webhook', 'webhooks_none_created' => 'No webhooks have yet been created.', 'webhooks_edit' => 'Edit Webhook', diff --git a/lang/vi/activities.php b/lang/vi/activities.php index cacb38786..23ae35f7f 100644 --- a/lang/vi/activities.php +++ b/lang/vi/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => 'Người dùng được cập nhật thành công', 'user_delete_notification' => 'Người dùng đã được xóa thành công', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => 'đã bình luận về', 'permissions_update' => 'các quyền đã được cập nhật', diff --git a/lang/vi/entities.php b/lang/vi/entities.php index a48a4f88e..bb401b1d3 100644 --- a/lang/vi/entities.php +++ b/lang/vi/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Được cập nhật :timeLength', 'meta_updated_name' => 'Được cập nhật :timeLength bởi :user', 'meta_owned_name' => 'Được sở hữu bởi :user', - 'meta_reference_page_count' => 'Được tham chiếu trên 1 trang | Được tham chiếu trên :count trang', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => 'Chọn thực thể', 'entity_select_lack_permission' => 'Bạn không có quyền để chọn mục này', 'images' => 'Ảnh', diff --git a/lang/vi/settings.php b/lang/vi/settings.php index b357feec0..9f5348973 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => 'Quyền', 'role_user_roles' => 'Quyền người dùng', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => 'Tạo quyền mới', - 'role_create_success' => 'Quyền mới đã được tạo thành công', 'role_delete' => 'Xóa quyền', 'role_delete_confirm' => 'Chức năng này sẽ xóa quyền với tên \':roleName\'.', 'role_delete_users_assigned' => 'Quyền này có :userCount người dùng được gán. Nếu bạn muốn di dời các người dùng từ quyền này hãy chọn một quyền mới bên dưới.', 'role_delete_no_migration' => "Không di dời các người dùng", 'role_delete_sure' => 'Bạn có chắc rằng muốn xóa quyền này?', - 'role_delete_success' => 'Quyền đã được xóa thành công', 'role_edit' => 'Sửa quyền', 'role_details' => 'Thông tin chi tiết Quyền', 'role_name' => 'Tên quyền', @@ -175,7 +173,6 @@ return [ 'role_own' => 'Sở hữu', 'role_controlled_by_asset' => 'Kiểm soát các tài sản (asset) người dùng tải lên', 'role_save' => 'Lưu Quyền', - 'role_update_success' => 'Quyền đã được cập nhật thành công', 'role_users' => 'Người dùng được gán quyền này', 'role_users_none' => 'Không có người dùng nào hiện được gán quyền này', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => 'Tạo Webhook mới', 'webhooks_none_created' => 'Chưa có webhooks nào được tạo.', 'webhooks_edit' => 'Chỉnh sửa Webhook', diff --git a/lang/zh_CN/activities.php b/lang/zh_CN/activities.php index a0b717cc2..faad21918 100644 --- a/lang/zh_CN/activities.php +++ b/lang/zh_CN/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => '用户更新成功', 'user_delete_notification' => '成功移除用户', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => '评论', 'permissions_update' => '权限已更新', diff --git a/lang/zh_CN/common.php b/lang/zh_CN/common.php index 11d7144ab..1d56c4371 100644 --- a/lang/zh_CN/common.php +++ b/lang/zh_CN/common.php @@ -86,8 +86,8 @@ return [ 'profile_menu' => '个人资料', 'view_profile' => '查看个人资料', 'edit_profile' => '编辑个人资料', - 'dark_mode' => '夜间模式', - 'light_mode' => '日间模式', + 'dark_mode' => '深色模式', + 'light_mode' => '浅色模式', 'global_search' => '全局搜索', // Layout tabs diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index 21830e808..dce9e7e14 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新于 :timeLength', 'meta_updated_name' => '由 :user 更新于 :timeLength', 'meta_owned_name' => '拥有者 :user', - 'meta_reference_page_count' => '被 1 个页面引用|被 :count 个页面引用', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => '选择项目', 'entity_select_lack_permission' => '您没有选择此项目所需的权限', 'images' => '图片', @@ -141,7 +141,7 @@ return [ 'books_search_this' => '搜索这本书', 'books_navigation' => '图书导航', 'books_sort' => '排序图书内容', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => '移动并重新排序书中的章节和页面。你也可以添加其他图书,这样就可以方便地在图书之间移动章节和页面。', 'books_sort_named' => '排序图书「:bookName」', 'books_sort_name' => '按名称排序', 'books_sort_created' => '创建时间排序', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => '章节倒序', 'books_sort_show_other' => '显示其他图书', 'books_sort_save' => '保存新顺序', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => '在此添加其他图书进入排序界面,这样就可以轻松跨图书重新排序。', + 'books_sort_move_up' => '上移', + 'books_sort_move_down' => '下移', + 'books_sort_move_prev_book' => '移动到上一图书', + 'books_sort_move_next_book' => '移动到下一图书', + 'books_sort_move_prev_chapter' => '移动到上一章节', + 'books_sort_move_next_chapter' => '移动到下一章节', + 'books_sort_move_book_start' => '移动到图书开头', + 'books_sort_move_book_end' => '移动到图书结尾', + 'books_sort_move_before_chapter' => '移动到章节前', + 'books_sort_move_after_chapter' => '移至章节后', 'books_copy' => '复制图书', 'books_copy_success' => '图书已成功复制', diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php index 1d2147fa3..4145d8e38 100644 --- a/lang/zh_CN/errors.php +++ b/lang/zh_CN/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => '上传图片时发生错误', 'image_upload_type_error' => '上传的图像类型无效', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => '无法加载绘图数据。绘图文件可能不再存在,或者您可能没有权限访问它。', // Attachments 'attachment_not_found' => '找不到附件', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index 889635bd7..36dd21eb1 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -33,9 +33,9 @@ return [ 'app_custom_html_desc' => '此处添加的任何内容都将插入到每个页面的<head>部分的底部,这对于覆盖样式或添加分析代码很方便。', 'app_custom_html_disabled_notice' => '在此设置页面上禁用了自定义HTML标题内容,以确保可以恢复所有重大更改。', 'app_logo' => '站点Logo', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_logo_desc' => '这会在应用程序标题栏等区域使用。此图片的高度应为 86 像素。大图像将按比例缩小。', + 'app_icon' => '应用程序图标', + 'app_icon_desc' => '此图标用于浏览器选项卡和快捷方式图标。这应该是一个 256 像素的正方形 PNG 图片。', 'app_homepage' => '站点主页', 'app_homepage_desc' => '选择要在主页上显示的页面来替换默认的页面,选定页面的访问权限将被忽略。', 'app_homepage_select' => '选择一个页面', @@ -49,12 +49,12 @@ return [ 'app_disable_comments_desc' => '在站点的所有页面上禁用评论,现有评论也不会显示出来。', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'color_scheme' => '应用程序配色方案', + 'color_scheme_desc' => '设置要在 BookStack 界面中使用的颜色。 可以为深色和浅色模式分别配置颜色,以适合主题并确保易读性。', + 'ui_colors_desc' => '设置 BookStack 的主颜色和默认链接颜色。主颜色主要用于页眉横幅、按钮和界面装饰。默认链接颜色用于基于文本的链接和操作,包括编写界面和 Bookstack 界面。', + 'app_color' => '主颜色', + 'link_color' => '默认链接颜色', + 'content_colors_desc' => '为页面组织层次结构中的所有元素设置颜色。为了便于阅读,建议选择与默认颜色亮度相似的颜色。', 'bookshelf_color' => '书架颜色', 'book_color' => '图书颜色', 'chapter_color' => '章节颜色', @@ -138,18 +138,16 @@ return [ 'roles' => '角色', 'role_user_roles' => '用户角色', 'roles_index_desc' => '角色用于对用户进行分组并为其成员提供系统权限。当一个用户是多个角色的成员时,授予的权限将叠加,用户将继承所有角色的能力。', - 'roles_x_users_assigned' => '1 位用户已分配|:count 位用户已分配', - 'roles_x_permissions_provided' => '1 个权限|:count 个权限', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => '已分配用户', 'roles_permissions_provided' => '已提供权限', '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' => '角色名', @@ -175,7 +173,6 @@ return [ 'role_own' => '拥有的', 'role_controlled_by_asset' => '由其所在的资源来控制', 'role_save' => '保存角色', - 'role_update_success' => '角色更新成功', 'role_users' => '此角色的用户', 'role_users_none' => '目前没有用户被分配到这个角色', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhook 是一种在系统内发生某些操作和事件时将数据发送到外部 URL 的方法,它允许与外部平台(例如消息传递或通知系统)进行基于事件的集成。', - 'webhooks_x_trigger_events' => '1 个触发事件 |:count 个触发事件', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => '新建 Webhook', 'webhooks_none_created' => '尚未创建任何 Webhook。', 'webhooks_edit' => '编辑 Webhook', diff --git a/lang/zh_TW/activities.php b/lang/zh_TW/activities.php index a131f4a6c..1a04caa2f 100644 --- a/lang/zh_TW/activities.php +++ b/lang/zh_TW/activities.php @@ -67,6 +67,11 @@ return [ 'user_update_notification' => '使用者已成功更新。', 'user_delete_notification' => '使用者移除成功', + // Roles + 'role_create_notification' => 'Role successfully created', + 'role_update_notification' => 'Role successfully updated', + 'role_delete_notification' => 'Role successfully deleted', + // Other 'commented_on' => '評論', 'permissions_update' => '更新權限', diff --git a/lang/zh_TW/entities.php b/lang/zh_TW/entities.php index 350b9f942..00100cbf6 100644 --- a/lang/zh_TW/entities.php +++ b/lang/zh_TW/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新於 :timeLength', 'meta_updated_name' => '由 :user 更新於 :timeLength', 'meta_owned_name' => ':user 所擁有', - 'meta_reference_page_count' => 'Referenced on 1 page|Referenced on :count pages', + 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', 'entity_select' => '選取項目', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => '圖片', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index 4777184e5..d1673a597 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -138,18 +138,16 @@ return [ 'roles' => '角色', 'role_user_roles' => '使用者角色', 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => '1 user assigned|:count users assigned', - 'roles_x_permissions_provided' => '1 permission|:count permissions', + 'roles_x_users_assigned' => ':count user assigned|:count users assigned', + 'roles_x_permissions_provided' => ':count permission|:count permissions', 'roles_assigned_users' => 'Assigned Users', 'roles_permissions_provided' => 'Provided Permissions', 'role_create' => '建立新角色', - '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' => '角色名稱', @@ -175,7 +173,6 @@ return [ 'role_own' => '擁有', 'role_controlled_by_asset' => '依據隸屬的資源來決定', 'role_save' => '儲存角色', - 'role_update_success' => '角色更新成功', 'role_users' => '屬於此角色的使用者', 'role_users_none' => '目前沒有使用者被分配到此角色', @@ -252,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => '1 trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', 'webhooks_create' => '建立 Webhook', 'webhooks_none_created' => '沒有已建立的 Webhook', 'webhooks_edit' => '設置 Webhook', From c7e33d198151d0468b693831e28551bd2c8e8c66 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Sun, 26 Feb 2023 10:50:14 +0000 Subject: [PATCH 17/27] Fixed caching issue when running tests --- tests/Helpers/UserRoleProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Helpers/UserRoleProvider.php b/tests/Helpers/UserRoleProvider.php index a06112189..8c2718bc3 100644 --- a/tests/Helpers/UserRoleProvider.php +++ b/tests/Helpers/UserRoleProvider.php @@ -18,7 +18,7 @@ class UserRoleProvider { if (is_null($this->admin)) { $adminRole = Role::getSystemRole('admin'); - $this->admin = $adminRole->users->first(); + $this->admin = $adminRole->users()->first(); } return $this->admin; From 7c27d261616205644e5822061cca412ee4ba2d0a Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Mon, 27 Feb 2023 19:09:20 +0000 Subject: [PATCH 18/27] Fixed language locale setting issue Attempted to access an array that had been filtered and therefore could have holes within, including as position 0 which would then be accessed. Also added cs language to internal map Related to #4068 --- app/Util/LanguageManager.php | 5 +++-- tests/LanguageTest.php | 11 ++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/Util/LanguageManager.php b/app/Util/LanguageManager.php index 0cbf3f397..93c992fcc 100644 --- a/app/Util/LanguageManager.php +++ b/app/Util/LanguageManager.php @@ -24,6 +24,7 @@ class LanguageManager 'bg' => ['iso' => 'bg_BG', 'windows' => 'Bulgarian'], 'bs' => ['iso' => 'bs_BA', 'windows' => 'Bosnian (Latin)'], 'ca' => ['iso' => 'ca', 'windows' => 'Catalan'], + 'cs' => ['iso' => 'cs_CZ', 'windows' => 'Czech'], 'da' => ['iso' => 'da_DK', 'windows' => 'Danish'], 'de' => ['iso' => 'de_DE', 'windows' => 'German'], 'de_informal' => ['iso' => 'de_DE', 'windows' => 'German'], @@ -120,14 +121,14 @@ class LanguageManager $isoLang = $this->localeMap[$language]['iso'] ?? ''; $isoLangPrefix = explode('_', $isoLang)[0]; - $locales = array_filter([ + $locales = array_values(array_filter([ $isoLang ? $isoLang . '.utf8' : false, $isoLang ?: false, $isoLang ? str_replace('_', '-', $isoLang) : false, $isoLang ? $isoLangPrefix . '.UTF-8' : false, $this->localeMap[$language]['windows'] ?? false, $language, - ]); + ])); if (!empty($locales)) { setlocale(LC_TIME, $locales[0], ...array_slice($locales, 1)); diff --git a/tests/LanguageTest.php b/tests/LanguageTest.php index e5c3c0bff..b65227dd8 100644 --- a/tests/LanguageTest.php +++ b/tests/LanguageTest.php @@ -4,7 +4,7 @@ namespace Tests; class LanguageTest extends TestCase { - protected $langs; + protected array $langs; /** * LanguageTest constructor. @@ -81,4 +81,13 @@ class LanguageTest extends TestCase $this->get('/'); $this->assertTrue(config('app.rtl'), 'App RTL config should have been set to true by middleware'); } + + public function test_unknown_lang_does_not_break_app() + { + config()->set('app.locale', 'zz'); + + $loginReq = $this->get('/login', ['Accept-Language' => 'zz']); + $loginReq->assertOk(); + $loginReq->assertSee('Log In'); + } } From 3464f5e9612c774d02f6b1fd8810c3c3ba5273dd Mon Sep 17 00:00:00 2001 From: Dan Brown <email@danb.me> Date: Mon, 27 Feb 2023 19:19:03 +0000 Subject: [PATCH 19/27] Updated translations with latest Crowdin changes (#4066) --- lang/pl/activities.php | 6 +- lang/pl/entities.php | 2 +- lang/pl/settings.php | 6 +- lang/sk/activities.php | 20 ++-- lang/sk/auth.php | 16 +-- lang/sk/common.php | 20 ++-- lang/sk/editor.php | 218 +++++++++++++++++++------------------- lang/sk/errors.php | 4 +- lang/sk/preferences.php | 20 ++-- lang/sk/validation.php | 2 +- lang/uk/activities.php | 8 +- lang/uk/auth.php | 4 +- lang/uk/editor.php | 6 +- lang/uk/entities.php | 38 +++---- lang/uk/errors.php | 2 +- lang/uk/preferences.php | 20 ++-- lang/uk/settings.php | 34 +++--- lang/zh_CN/activities.php | 6 +- lang/zh_CN/entities.php | 2 +- lang/zh_CN/settings.php | 6 +- 20 files changed, 220 insertions(+), 220 deletions(-) diff --git a/lang/pl/activities.php b/lang/pl/activities.php index dd50c34ad..a4f05aac2 100644 --- a/lang/pl/activities.php +++ b/lang/pl/activities.php @@ -68,9 +68,9 @@ return [ 'user_delete_notification' => 'Użytkownik pomyślnie usunięty', // Roles - 'role_create_notification' => 'Role successfully created', - 'role_update_notification' => 'Role successfully updated', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create_notification' => 'Rola utworzona pomyślnie', + 'role_update_notification' => 'Rola zaktualizowana pomyślnie', + 'role_delete_notification' => 'Rola usunięta pomyślnie', // Other 'commented_on' => 'skomentował', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index af4df68fc..eb46ed4eb 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zaktualizowano :timeLength', 'meta_updated_name' => 'Zaktualizowano :timeLength przez :user', 'meta_owned_name' => 'Właściciel: :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_page_count' => 'Odniesienie na :count stronie|Odniesienie na :count stronach', 'entity_select' => 'Wybór obiektu', 'entity_select_lack_permission' => 'Nie masz wymaganych uprawnień do wybrania tej pozycji', 'images' => 'Obrazki', diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 1b84a6e16..b644425fa 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -138,8 +138,8 @@ return [ 'roles' => 'Role', 'role_user_roles' => 'Role użytkowników', 'roles_index_desc' => 'Role są używane do grupowania użytkowników i udzielania uprawnień systemowych ich członkom. Gdy użytkownik jest członkiem wielu ról, przyznane uprawnienia będą gromadzone, a użytkownik odziedziczy wszystkie możliwości.', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', - 'roles_x_permissions_provided' => ':count permission|:count permissions', + 'roles_x_users_assigned' => ':count przypisany użytkownik|:count przypisanych użytkowników', + 'roles_x_permissions_provided' => ':1 uprawnienie|:count uprawnień', 'roles_assigned_users' => 'Przypisani Użytkownicy', 'roles_permissions_provided' => 'Przyznawane Uprawnienia', 'role_create' => 'Utwórz nową rolę', @@ -249,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooki', 'webhooks_index_desc' => 'Webhooki to sposób na wysyłanie danych do zewnętrznych adresów URL, gdy pewne działania i zdarzenia zachodzą w ramach systemu, co umożliwia integrację zdarzeń w systemie z zewnętrznymi platformami, takimi jak systemy wysyłania wiadomości lub powiadamiania.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count zdarzenie wyzwalacza|:count zdarzeń wyzwalacza', 'webhooks_create' => 'Utwórz nowy Webhook', 'webhooks_none_created' => 'Nie utworzono jeszcze żadnych webhooków.', 'webhooks_edit' => 'Edytuj Webhook', diff --git a/lang/sk/activities.php b/lang/sk/activities.php index 388312d36..bc0247ff9 100644 --- a/lang/sk/activities.php +++ b/lang/sk/activities.php @@ -38,14 +38,14 @@ return [ 'book_sort_notification' => 'Kniha úspešne znovu zoradená', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', + 'bookshelf_create' => 'vytvoril(a) policu', + 'bookshelf_create_notification' => 'Polica úspešne vytvorená', + 'bookshelf_create_from_book' => 'kniha bola prevedená na policu', 'bookshelf_create_from_book_notification' => 'Kniha úspešne konvertovaná na poličku', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_update' => 'aktualizoval(a) policu', + 'bookshelf_update_notification' => 'Polica bola úspešne aktualizovaná', + 'bookshelf_delete' => 'odstránená polica', + 'bookshelf_delete_notification' => 'Polica bola úspešne odstránená', // Favourites 'favourite_add_notification' => '":name" bol pridaný medzi obľúbené', @@ -68,9 +68,9 @@ return [ 'user_delete_notification' => 'Používateľ úspešne zmazaný', // Roles - 'role_create_notification' => 'Role successfully created', - 'role_update_notification' => 'Role successfully updated', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create_notification' => 'Rola úspešne vytvorená', + 'role_update_notification' => 'Rola úspešne aktualizovaná', + 'role_delete_notification' => 'Rola úspešne zmazaná', // Other 'commented_on' => 'komentoval(a)', diff --git a/lang/sk/auth.php b/lang/sk/auth.php index 2f5bd5c40..084de678e 100644 --- a/lang/sk/auth.php +++ b/lang/sk/auth.php @@ -21,7 +21,7 @@ return [ 'email' => 'E-mail', 'password' => 'Heslo', 'password_confirm' => 'Potvrdiť heslo', - 'password_hint' => 'Must be at least 8 characters', + 'password_hint' => 'Musí obsahovať aspoň 8 znakov', 'forgot_password' => 'Zabudli ste heslo?', 'remember_me' => 'Zapamätať si ma', 'ldap_email_hint' => 'Zadajte prosím e-mail, ktorý sa má použiť pre tento účet.', @@ -39,9 +39,9 @@ return [ 'register_success' => 'Ďakujeme za registráciu! Teraz ste registrovaný a prihlásený.', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', - 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', - 'auto_init_start_link' => 'Proceed with authentication', + 'auto_init_starting' => 'Pokus o prihlásenie', + 'auto_init_starting_desc' => 'Kontaktujeme váš overovací systém, aby sme spustili proces prihlásenia. Ak po 5 sekundách nedôjde k žiadnemu pokroku, môžete skúsiť kliknúť na odkaz nižšie.', + 'auto_init_start_link' => 'Pokračujte v autentifikácii', // Password Reset 'reset_password' => 'Resetovanie hesla', @@ -59,10 +59,10 @@ return [ 'email_confirm_text' => 'Prosím, potvrďte Vašu e-mailovú adresu kliknutím na tlačidlo nižšie:', 'email_confirm_action' => 'Potvrdiť e-mail', 'email_confirm_send_error' => 'Je požadované overenie e-mailu, ale systém nemohol e-mail odoslať. Kontaktujte administrátora, aby ste sa uistili, že je e-mail nastavený správne.', - 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', + 'email_confirm_success' => 'Váš email bol potvrdený! Teraz by ste sa mali vedieť prihlásiť pomocou tejto e-mailovej adresy.', 'email_confirm_resent' => 'Potvrdzujúci e-mail bol poslaný znovu, skontrolujte prosím svoju e-mailovú schránku.', - 'email_confirm_thanks' => 'Thanks for confirming!', - 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', + 'email_confirm_thanks' => 'Ďakujeme za potvrdenie!', + 'email_confirm_thanks_desc' => 'Počkajte chvíľu, kým sa spracuje vaše potvrdenie. Ak nebudete presmerovaní do 3 sekúnd, pokračujte kliknutím na odkaz „Pokračovať“ nižšie.', 'email_not_confirmed' => 'E-mailová adresa nebola overená', 'email_not_confirmed_text' => 'Vaša e-mailová adresa nebola zatiaľ overená.', @@ -78,7 +78,7 @@ return [ 'user_invite_page_welcome' => 'Vitajte v :appName!', 'user_invite_page_text' => 'Ak chcete dokončiť svoj účet a získať prístup, musíte nastaviť heslo, ktoré sa použije na prihlásenie do aplikácie :appName pri budúcich návštevách.', 'user_invite_page_confirm_button' => 'Potvrdiť heslo', - 'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!', + 'user_invite_success_login' => 'Heslo je nastavené, teraz by ste sa mali vedieť prihlásiť pomocou svojho nastaveného hesla na prístup k :appName!', // Multi-factor Authentication 'mfa_setup' => 'Nastaviť viacúrovňové prihlasovanie', diff --git a/lang/sk/common.php b/lang/sk/common.php index e4725b39b..81113baae 100644 --- a/lang/sk/common.php +++ b/lang/sk/common.php @@ -25,7 +25,7 @@ return [ 'actions' => 'Akcie', 'view' => 'Zobraziť', 'view_all' => 'Zobraziť všetko', - 'new' => 'New', + 'new' => 'Nový', 'create' => 'Vytvoriť', 'update' => 'Aktualizovať', 'edit' => 'Editovať', @@ -48,8 +48,8 @@ return [ 'previous' => 'Späť', 'filter_active' => 'Aktívny filter:', 'filter_clear' => 'Bez filtrovania', - 'download' => 'Download', - 'open_in_tab' => 'Open in Tab', + 'download' => 'Stiahnuť', + 'open_in_tab' => 'Otvoriť na novej karte', // Sort Options 'sort_options' => 'Možnosti triedenia', @@ -74,21 +74,21 @@ return [ 'list_view' => 'Zobraziť ako zoznam', 'default' => 'Predvolené', 'breadcrumb' => 'Breadcrumb', - 'status' => 'Status', - 'status_active' => 'Active', - 'status_inactive' => 'Inactive', - 'never' => 'Never', - 'none' => 'None', + 'status' => 'Stav', + 'status_active' => 'Aktívny', + 'status_inactive' => 'Neaktívny', + 'never' => 'Nikdy', + 'none' => 'Žiadne', // Header - 'homepage' => 'Homepage', + 'homepage' => 'Domovská stránka', 'header_menu_expand' => 'Rozbaliť menu v záhlaví', 'profile_menu' => 'Menu profilu', 'view_profile' => 'Zobraziť profil', 'edit_profile' => 'Upraviť profil', 'dark_mode' => 'Tmavý režim', 'light_mode' => 'Svetlý režim', - 'global_search' => 'Global Search', + 'global_search' => 'Globálne vyhľadávanie', // Layout tabs 'tab_info' => 'Informácie', diff --git a/lang/sk/editor.php b/lang/sk/editor.php index 670c1c5e1..e9423817e 100644 --- a/lang/sk/editor.php +++ b/lang/sk/editor.php @@ -7,68 +7,68 @@ */ return [ // General editor terms - 'general' => 'General', - 'advanced' => 'Advanced', - 'none' => 'None', - 'cancel' => 'Cancel', - 'save' => 'Save', - 'close' => 'Close', - 'undo' => 'Undo', - 'redo' => 'Redo', - 'left' => 'Left', - 'center' => 'Center', - 'right' => 'Right', - 'top' => 'Top', - 'middle' => 'Middle', - 'bottom' => 'Bottom', - 'width' => 'Width', - 'height' => 'Height', - 'More' => 'More', - 'select' => 'Select...', + 'general' => 'Všeobecné', + 'advanced' => 'Pokročilé', + 'none' => 'Žiadne', + 'cancel' => 'Zrušiť', + 'save' => 'Uložiť', + 'close' => 'Zavrieť', + 'undo' => 'Vrátiť späť', + 'redo' => 'Obnoviť', + 'left' => 'Vľavo', + 'center' => 'Na stred', + 'right' => 'Vpravo', + 'top' => 'Nahor', + 'middle' => 'Uprostred', + 'bottom' => 'Dole', + 'width' => 'Šírka', + 'height' => 'Výška', + 'More' => 'Viac', + 'select' => 'Vybrať...', // Toolbar - 'formats' => 'Formats', - 'header_large' => 'Large Header', - 'header_medium' => 'Medium Header', - 'header_small' => 'Small Header', - 'header_tiny' => 'Tiny Header', - 'paragraph' => 'Paragraph', - 'blockquote' => 'Blockquote', - 'inline_code' => 'Inline code', - 'callouts' => 'Callouts', - 'callout_information' => 'Information', - 'callout_success' => 'Success', - 'callout_warning' => 'Warning', - 'callout_danger' => 'Danger', - 'bold' => 'Bold', - 'italic' => 'Italic', - 'underline' => 'Underline', - 'strikethrough' => 'Strikethrough', - 'superscript' => 'Superscript', - 'subscript' => 'Subscript', - 'text_color' => 'Text color', - 'custom_color' => 'Custom color', - 'remove_color' => 'Remove color', - 'background_color' => 'Background color', - 'align_left' => 'Align left', - 'align_center' => 'Align center', - 'align_right' => 'Align right', - 'align_justify' => 'Justify', - 'list_bullet' => 'Bullet list', - 'list_numbered' => 'Numbered list', - 'list_task' => 'Task list', - 'indent_increase' => 'Increase indent', - 'indent_decrease' => 'Decrease indent', - 'table' => 'Table', - 'insert_image' => 'Insert image', - 'insert_image_title' => 'Insert/Edit Image', - 'insert_link' => 'Insert/edit link', - 'insert_link_title' => 'Insert/Edit Link', - 'insert_horizontal_line' => 'Insert horizontal line', - 'insert_code_block' => 'Insert code block', - 'edit_code_block' => 'Edit code block', - 'insert_drawing' => 'Insert/edit drawing', - 'drawing_manager' => 'Drawing manager', + 'formats' => 'Formáty', + 'header_large' => 'Veľká hlavička', + 'header_medium' => 'Stredná hlavička', + 'header_small' => 'Malá hlavička', + 'header_tiny' => 'Drobná hlavička', + 'paragraph' => 'Odstavec', + 'blockquote' => 'Citácia', + 'inline_code' => 'Vložený kód', + 'callouts' => 'Hlášky', + 'callout_information' => 'Informácie', + 'callout_success' => 'Úspech', + 'callout_warning' => 'Upozornenie', + 'callout_danger' => 'Nebezpečné', + 'bold' => 'Tučné', + 'italic' => 'Kurzíva', + 'underline' => 'Podčiarknutie', + 'strikethrough' => 'Prečiarknutie', + 'superscript' => 'Horný index', + 'subscript' => 'Dolný index', + 'text_color' => 'Farba textu', + 'custom_color' => 'Vlastná farba', + 'remove_color' => 'Odstrániť farbu', + 'background_color' => 'Farba pozadia', + 'align_left' => 'Zarovnať vľavo', + 'align_center' => 'Zarovnať na stred', + 'align_right' => 'Zarovnať vpravo', + 'align_justify' => 'Do bloku', + 'list_bullet' => 'Bodový zoznam', + 'list_numbered' => 'Číslovaný zoznam', + 'list_task' => 'Zoznam úloh', + 'indent_increase' => 'Zvýšiť odsadenie', + 'indent_decrease' => 'Zmenšiť odsadenie', + 'table' => 'Tabuľka', + 'insert_image' => 'Vložiť obrázok', + 'insert_image_title' => 'Vložiť/Upraviť obrázok', + 'insert_link' => 'Vložiť/Upraviť odkaz', + 'insert_link_title' => 'Vložiť/Upraviť link', + 'insert_horizontal_line' => 'Vložiť horizontálnu čiaru', + 'insert_code_block' => 'Vložte blok kódu', + 'edit_code_block' => 'Upraviť blok kódu', + 'insert_drawing' => 'Vložiť/upraviť výkres', + 'drawing_manager' => 'Manažér kreslenia', 'insert_media' => 'Insert/edit media', 'insert_media_title' => 'Insert/Edit Media', 'clear_formatting' => 'Clear formatting', @@ -111,60 +111,60 @@ return [ 'copy_row' => 'Copy row', 'paste_row_before' => 'Paste row before', 'paste_row_after' => 'Paste row after', - 'row_type' => 'Row type', - 'row_type_header' => 'Header', - 'row_type_body' => 'Body', - 'row_type_footer' => 'Footer', - 'alignment' => 'Alignment', - 'cut_column' => 'Cut column', - 'copy_column' => 'Copy column', - 'paste_column_before' => 'Paste column before', - 'paste_column_after' => 'Paste column after', - 'cell_padding' => 'Cell padding', - 'cell_spacing' => 'Cell spacing', - 'caption' => 'Caption', - 'show_caption' => 'Show caption', - 'constrain' => 'Constrain proportions', - 'cell_border_solid' => 'Solid', - 'cell_border_dotted' => 'Dotted', - 'cell_border_dashed' => 'Dashed', - 'cell_border_double' => 'Double', - 'cell_border_groove' => 'Groove', - 'cell_border_ridge' => 'Ridge', - 'cell_border_inset' => 'Inset', - 'cell_border_outset' => 'Outset', - 'cell_border_none' => 'None', - 'cell_border_hidden' => 'Hidden', + 'row_type' => 'Typ riadku', + 'row_type_header' => 'Hlavička', + 'row_type_body' => 'Telo', + 'row_type_footer' => 'Päta', + 'alignment' => 'Zarovnanie', + 'cut_column' => 'Vystrihnúť stĺpec', + 'copy_column' => 'Kopírovať stĺpec', + 'paste_column_before' => 'Pridať stĺpec pred', + 'paste_column_after' => 'Pridať stĺpec po', + 'cell_padding' => 'Cell Rozostup', + 'cell_spacing' => 'Cell Rozstup', + 'caption' => 'Titulok', + 'show_caption' => 'Zobraziť Titulok', + 'constrain' => 'Obmedziť rozmery', + 'cell_border_solid' => 'Plný', + 'cell_border_dotted' => 'Bodkovaný', + 'cell_border_dashed' => 'Prerušované', + 'cell_border_double' => 'Dvojité', + 'cell_border_groove' => 'Drážka', + 'cell_border_ridge' => 'Hrebeň', + 'cell_border_inset' => 'Príloha', + 'cell_border_outset' => 'Počiatok', + 'cell_border_none' => 'Žiadne', + 'cell_border_hidden' => 'Skryté', // Images, links, details/summary & embed - 'source' => 'Source', - 'alt_desc' => 'Alternative description', - 'embed' => 'Embed', - 'paste_embed' => 'Paste your embed code below:', + 'source' => 'Zdroj', + 'alt_desc' => 'Alternatívny popis', + 'embed' => 'Vložka', + 'paste_embed' => 'Vložte svoj kód na vloženie nižšie:', 'url' => 'URL', - 'text_to_display' => 'Text to display', - 'title' => 'Title', - 'open_link' => 'Open link', - 'open_link_in' => 'Open link in...', - 'open_link_current' => 'Current window', - 'open_link_new' => 'New window', - 'remove_link' => 'Remove link', - 'insert_collapsible' => 'Insert collapsible block', - 'collapsible_unwrap' => 'Unwrap', - 'edit_label' => 'Edit label', - 'toggle_open_closed' => 'Toggle open/closed', - 'collapsible_edit' => 'Edit collapsible block', - 'toggle_label' => 'Toggle label', + 'text_to_display' => 'Text na zobrazenie', + 'title' => 'Názov', + 'open_link' => 'Otvoriť odkaz', + 'open_link_in' => 'Otvoriť odkaz v...', + 'open_link_current' => 'Iba aktuálne okno', + 'open_link_new' => 'Nové okno', + 'remove_link' => 'Odstrániť odkaz', + 'insert_collapsible' => 'Vložte skladací blok', + 'collapsible_unwrap' => 'Rozbaliť', + 'edit_label' => 'Upraviť menovku', + 'toggle_open_closed' => 'Prepínač otvorené/zatvorené', + 'collapsible_edit' => 'Upraviť skladací blok', + 'toggle_label' => 'Prepnutie menovky', // About view - 'about' => 'About the editor', - 'about_title' => 'About the WYSIWYG Editor', - 'editor_license' => 'Editor License & Copyright', - 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.', - 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', - 'save_continue' => 'Save Page & Continue', - 'callouts_cycle' => '(Keep pressing to toggle through types)', - 'link_selector' => 'Link to content', + 'about' => 'O editore', + 'about_title' => 'O WYSIWYG Editore', + 'editor_license' => 'Licencia editora a autorské práva', + 'editor_tiny_license' => 'Tento editor je vytvorený pomocou :tinyLink, ktorý je poskytovaný pod licenciou MIT.', + 'editor_tiny_license_link' => 'Podrobnosti o autorských právach a licenciách TinyMCE nájdete tu.', + 'save_continue' => 'Uložiť a pokračovať', + 'callouts_cycle' => '(Podržte stlačené, aby ste prepínali medzi typmi)', + 'link_selector' => 'Prejsť na obsah', 'shortcuts' => 'Shortcuts', 'shortcut' => 'Shortcut', 'shortcuts_intro' => 'The following shortcuts are available in the editor:', diff --git a/lang/sk/errors.php b/lang/sk/errors.php index 93701bfe3..139633ca5 100644 --- a/lang/sk/errors.php +++ b/lang/sk/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Pri nahrávaní obrázka nastala chyba', 'image_upload_type_error' => 'Typ nahrávaného obrázka je neplatný', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Údaje výkresu sa nepodarilo načítať. Súbor výkresu už možno neexistuje alebo nemáte povolenie na prístup k nemu.', // Attachments 'attachment_not_found' => 'Príloha nenájdená', @@ -61,7 +61,7 @@ return [ // Entities 'entity_not_found' => 'Entita nenájdená', - 'bookshelf_not_found' => 'Shelf not found', + 'bookshelf_not_found' => 'Polica nenájdená', 'book_not_found' => 'Kniha nenájdená', 'page_not_found' => 'Stránka nenájdená', 'chapter_not_found' => 'Kapitola nenájdená', diff --git a/lang/sk/preferences.php b/lang/sk/preferences.php index e9a47461b..585207c1f 100644 --- a/lang/sk/preferences.php +++ b/lang/sk/preferences.php @@ -5,14 +5,14 @@ */ return [ - 'shortcuts' => 'Shortcuts', - 'shortcuts_interface' => 'Interface Keyboard Shortcuts', - 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', - 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', - 'shortcuts_toggle_label' => 'Keyboard shortcuts enabled', - 'shortcuts_section_navigation' => 'Navigation', - 'shortcuts_section_actions' => 'Common Actions', - 'shortcuts_save' => 'Save Shortcuts', - 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', - 'shortcuts_update_success' => 'Shortcut preferences have been updated!', + 'shortcuts' => 'Skratky', + 'shortcuts_interface' => 'Klávesové skratky rozhrania', + 'shortcuts_toggle_desc' => 'Tu môžete povoliť alebo zakázať klávesové skratky systémového rozhrania, ktoré sa používajú na navigáciu a akcie.', + 'shortcuts_customize_desc' => 'Každú z nižšie uvedených skratiek si môžete prispôsobiť. Po výbere vstupu pre skratku stačí stlačiť požadovanú kombináciu klávesov.', + 'shortcuts_toggle_label' => 'Klávesové skratky sú povolené', + 'shortcuts_section_navigation' => 'Navigácia', + 'shortcuts_section_actions' => 'Hlavné akcie', + 'shortcuts_save' => 'Uložiť skratky', + 'shortcuts_overlay_desc' => 'Poznámka: Keď sú zapnuté skratky, pomocné prekrytie je dostupné stlačením „?", ktoré zvýrazní dostupné skratky akcií,, ktoré sú momentálne viditeľné na obrazovke.', + 'shortcuts_update_success' => 'Predvoľby skratiek boli aktualizované!', ]; \ No newline at end of file diff --git a/lang/sk/validation.php b/lang/sk/validation.php index 20770e4b6..dfc821b7c 100644 --- a/lang/sk/validation.php +++ b/lang/sk/validation.php @@ -32,7 +32,7 @@ return [ 'digits_between' => ':attribute musí mať medzi :min a :max číslicami.', 'email' => ':attribute musí byť platná emailová adresa.', 'ends_with' => ':attribute musí končiť jednou z nasledujúcich hodnôt :values', - 'file' => 'The :attribute must be provided as a valid file.', + 'file' => ':attribute musí byť uvedený ako platný súbor.', 'filled' => 'Políčko :attribute je povinné.', 'gt' => [ 'numeric' => 'Hodnota :attribute musí byť väčšia ako :value.', diff --git a/lang/uk/activities.php b/lang/uk/activities.php index 54ef3b7cf..66882611a 100644 --- a/lang/uk/activities.php +++ b/lang/uk/activities.php @@ -6,7 +6,7 @@ return [ // Pages - 'page_create' => 'створив сторінку', + 'page_create' => 'створено сторінку', 'page_create_notification' => 'Сторінка успішно створена', 'page_update' => 'оновив сторінку', 'page_update_notification' => 'Сторінка успішно оновлена', @@ -68,9 +68,9 @@ return [ 'user_delete_notification' => 'Користувача успішно видалено', // Roles - 'role_create_notification' => 'Role successfully created', - 'role_update_notification' => 'Role successfully updated', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create_notification' => 'Роль успішно створена', + 'role_update_notification' => 'Роль успішно оновлена', + 'role_delete_notification' => 'Роль успішно видалена', // Other 'commented_on' => 'прокоментував', diff --git a/lang/uk/auth.php b/lang/uk/auth.php index 15aecaab5..649c6e8df 100644 --- a/lang/uk/auth.php +++ b/lang/uk/auth.php @@ -61,8 +61,8 @@ return [ 'email_confirm_send_error' => 'Необхідно підтвердження електронною поштою, але система не змогла надіслати електронний лист. Зверніться до адміністратора, щоб правильно налаштувати електронну пошту.', 'email_confirm_success' => 'Ваша адреса електронної пошти була підтверджена! Тепер ви можете увійти в систему, використовуючи цю адресу електронної пошти.', 'email_confirm_resent' => 'Лист з підтвердженням надіслано, перевірте свою пошту.', - 'email_confirm_thanks' => 'Thanks for confirming!', - 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', + 'email_confirm_thanks' => 'Дякуємо за підтвердження!', + 'email_confirm_thanks_desc' => 'Будь ласка, зачекайте деякий час, поки підтвердження буде оброблено. Якщо ви не перенаправлені через 3 секунди, натисніть посилання "Продовжити" нижче, щоб продовжити.', 'email_not_confirmed' => 'Адресу електронної скриньки не підтверджено', 'email_not_confirmed_text' => 'Ваша електронна адреса ще не підтверджена.', diff --git a/lang/uk/editor.php b/lang/uk/editor.php index f006bbfe3..412ff49cf 100644 --- a/lang/uk/editor.php +++ b/lang/uk/editor.php @@ -144,11 +144,11 @@ return [ 'url' => 'Адреса URL', 'text_to_display' => 'Текст для показу', 'title' => 'Назва', - 'open_link' => 'Open link', - 'open_link_in' => 'Open link in...', + 'open_link' => 'Відкрити посилання', + 'open_link_in' => 'Відкрити посилання в...', 'open_link_current' => 'Поточне вікно', 'open_link_new' => 'Нове вікно', - 'remove_link' => 'Remove link', + 'remove_link' => 'Видалити посилання', 'insert_collapsible' => 'Вставити згорнутий блок', 'collapsible_unwrap' => 'Розгорнути', 'edit_label' => 'Редагувати мітку', diff --git a/lang/uk/entities.php b/lang/uk/entities.php index 4d56a879c..27e6fcfcd 100644 --- a/lang/uk/entities.php +++ b/lang/uk/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Оновлено :timeLength', 'meta_updated_name' => ':user оновив :timeLength', 'meta_owned_name' => 'Власник :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_page_count' => 'Посилання на :count сторінки|Посилання на :count сторінок', 'entity_select' => 'Вибір об\'єкта', 'entity_select_lack_permission' => 'У вас немає необхідних прав для вибору цього елемента', 'images' => 'Зображення', @@ -50,7 +50,7 @@ return [ 'permissions_role_everyone_else' => 'Всі інші', 'permissions_role_everyone_else_desc' => 'Встановити дозвіл для всіх ролей не спеціально перевизначений.', 'permissions_role_override' => 'Змінити права доступу для ролі', - 'permissions_inherit_defaults' => 'Inherit defaults', + 'permissions_inherit_defaults' => 'Успадковувати за замовчуванням', // Search 'search_results' => 'Результати пошуку', @@ -141,7 +141,7 @@ return [ 'books_search_this' => 'Шукати цю книгу', 'books_navigation' => 'Навігація по книзі', 'books_sort' => 'Сортувати вміст книги', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'Перекладіть розділи та сторінки в межах книги, щоб реорганізувати вміст. Інші книги можна додати, що дозволяє легко переміщати глави та сторінки між книгами.', 'books_sort_named' => 'Сортувати книгу :bookName', 'books_sort_name' => 'Сортувати за назвою', 'books_sort_created' => 'Сортувати за датою створення', @@ -150,17 +150,17 @@ return [ 'books_sort_chapters_last' => 'Розділи в кінці', 'books_sort_show_other' => 'Показати інші книги', 'books_sort_save' => 'Зберегти нове замовлення', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => 'Додавайте інші книги, щоб включити їх у операцію сортування та дозволити легку повторну організацію перехресних книг.', + 'books_sort_move_up' => 'Перемістити вгору', + 'books_sort_move_down' => 'Перемістити нижче', + 'books_sort_move_prev_book' => 'Перейти до попередньої книги', + 'books_sort_move_next_book' => 'Перейти до наступної книги', + 'books_sort_move_prev_chapter' => 'Перейти до попереднього розділу', + 'books_sort_move_next_chapter' => 'Перейти до наступного розділу', + 'books_sort_move_book_start' => 'Перейти до початку книги', + 'books_sort_move_book_end' => 'Перейти до кінця книги', + 'books_sort_move_before_chapter' => 'Перейти до розділу', + 'books_sort_move_after_chapter' => 'Перехід в кінець розділу', 'books_copy' => 'Копіювати книгу', 'books_copy_success' => 'Сторінка успішно скопійована', @@ -236,8 +236,8 @@ return [ 'pages_md_insert_image' => 'Вставити зображення', 'pages_md_insert_link' => 'Вставити посилання на об\'єкт', 'pages_md_insert_drawing' => 'Вставити малюнок', - 'pages_md_show_preview' => 'Show preview', - 'pages_md_sync_scroll' => 'Sync preview scroll', + 'pages_md_show_preview' => 'Показати попередній перегляд', + 'pages_md_sync_scroll' => 'Синхронізація прокручування попереднього перегляду', 'pages_not_in_chapter' => 'Сторінка не знаходиться в розділі', 'pages_move' => 'Перемістити сторінку', 'pages_move_success' => 'Сторінку переміщено до ":parentName"', @@ -248,14 +248,14 @@ return [ 'pages_permissions_success' => 'Дозволи на сторінку оновлено', 'pages_revision' => 'Версія', 'pages_revisions' => 'Версія сторінки', - 'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.', + 'pages_revisions_desc' => 'Нижче наведено всі попередні версії цієї сторінки. Ви можете переглядати, порівнювати та відновлювати старі версії сторінок, якщо це дозволено. Повна історія сторінки може бути показана не повністю, оскільки, залежно від конфігурації системи, старі версії можуть автоматично видалятися.', 'pages_revisions_named' => 'Версії сторінки для :pageName', 'pages_revision_named' => 'Версія сторінки для :pageName', 'pages_revision_restored_from' => 'Відновлено з #:id; :summary', 'pages_revisions_created_by' => 'Створена', 'pages_revisions_date' => 'Дата версії', 'pages_revisions_number' => '#', - 'pages_revisions_sort_number' => 'Revision Number', + 'pages_revisions_sort_number' => 'Номер редакції', 'pages_revisions_numbered' => 'Версія #:id', 'pages_revisions_numbered_changes' => 'Зміни версії #:id', 'pages_revisions_editor' => 'Тип редактора', @@ -292,7 +292,7 @@ return [ 'shelf_tags' => 'Теги полиць', 'tag' => 'Тег', 'tags' => 'Теги', - 'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.', + 'tags_index_desc' => 'Теги можна застосовувати до вмісту в системі, щоб застосувати гнучку форму категоризації. Теги можуть мати як ключ, так і значення, при цьому значення є необов’язковим. Після застосування вміст можна запитувати за допомогою імені та значення тегу.', 'tag_name' => 'Назва тегу', 'tag_value' => 'Значення тегу (необов\'язково)', 'tags_explain' => "Додайте кілька тегів, щоб краще класифікувати ваш вміст. \n Ви можете присвоїти значення тегу для більш глибокої організації.", diff --git a/lang/uk/errors.php b/lang/uk/errors.php index 3cba88576..2b2cf1100 100644 --- a/lang/uk/errors.php +++ b/lang/uk/errors.php @@ -50,7 +50,7 @@ return [ // Drawing & Images 'image_upload_error' => 'Виникла помилка під час завантаження зображення', 'image_upload_type_error' => 'Тип завантаженого зображення недійсний', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'drawing_data_not_found' => 'Не вдалося завантажити дані малюнка. Файл малюнка може більше не існувати або у вас немає дозволу на доступ до нього.', // Attachments 'attachment_not_found' => 'Вкладення не знайдено', diff --git a/lang/uk/preferences.php b/lang/uk/preferences.php index e9a47461b..55934b4fe 100644 --- a/lang/uk/preferences.php +++ b/lang/uk/preferences.php @@ -5,14 +5,14 @@ */ return [ - 'shortcuts' => 'Shortcuts', - 'shortcuts_interface' => 'Interface Keyboard Shortcuts', - 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', - 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', - 'shortcuts_toggle_label' => 'Keyboard shortcuts enabled', - 'shortcuts_section_navigation' => 'Navigation', - 'shortcuts_section_actions' => 'Common Actions', - 'shortcuts_save' => 'Save Shortcuts', - 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', - 'shortcuts_update_success' => 'Shortcut preferences have been updated!', + 'shortcuts' => 'Ярлики', + 'shortcuts_interface' => 'Комбінації клавіш інтерфейсу', + 'shortcuts_toggle_desc' => 'Тут ви можете увімкнути або вимкнути ярлики інтерфейсу клавіатури, які використовуються для навігації та дій.', + 'shortcuts_customize_desc' => 'Ви можете налаштувати кожен з ярликів нижче. Просто натисніть на комбінацію бажаного ключа після вибору вводу для ярлика.', + 'shortcuts_toggle_label' => 'Клавіатурні скорочення увімкнено', + 'shortcuts_section_navigation' => 'Навігація', + 'shortcuts_section_actions' => 'Загальні дії', + 'shortcuts_save' => 'Зберегти ярлики', + 'shortcuts_overlay_desc' => 'Примітка: якщо ярлики ввімкнено, допоміжне накладання доступне, натиснувши "?" який виділить доступні ярлики для дій, які зараз видно на екрані.', + 'shortcuts_update_success' => 'Налаштування ярликів оновлено!', ]; \ No newline at end of file diff --git a/lang/uk/settings.php b/lang/uk/settings.php index e22eb6c8c..18b9dabfd 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -33,9 +33,9 @@ return [ 'app_custom_html_desc' => 'Будь-який доданий тут вміст буде вставлено в нижню частину розділу <head> кожної сторінки. Це зручно для перевизначення стилів, або додавання коду аналітики.', 'app_custom_html_disabled_notice' => 'На цій сторінці налаштувань відключений користувацький вміст заголовка HTML, щоб гарантувати, що будь-які невдалі зміни можна буде відновити.', 'app_logo' => 'Логотип програми', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_logo_desc' => 'Це використовується в панелі заголовка програми, серед інших областей. Це зображення має бути 86 пікселів у висоту. Великі зображення будуть зменшені.', + 'app_icon' => 'Значок додатка', + 'app_icon_desc' => 'Цей значок використовується для вкладок браузера та піктограм ярликів. Це має бути квадратне зображення PNG на 256px.', 'app_homepage' => 'Домашня сторінка програми', 'app_homepage_desc' => 'Виберіть сторінку, яка показуватиметься на домашній сторінці замість перегляду за замовчуванням. Права на сторінку не враховуються для вибраних сторінок.', 'app_homepage_select' => 'Вибрати сторінку', @@ -49,12 +49,12 @@ return [ 'app_disable_comments_desc' => 'Вимкнути коментарі на всіх сторінках програми. Існуючі коментарі не відображаються.', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'color_scheme' => 'Колірна схема застосунку', + 'color_scheme_desc' => 'Встановлює колір для інтерфейсу закладок. Кольори можуть бути налаштовані окремо для темних та світлих режимів, щоб пристосуватися до теми та забезпечити роздільну здатність.', + 'ui_colors_desc' => 'Установіть основний колір і колір посилання за замовчуванням для BookStack. Основний колір в основному використовується для банера заголовка, кнопок і декорацій інтерфейсу. Стандартний колір посилання використовується для текстових посилань і дій як у письмовому вмісті, так і в інтерфейсі Bookstack.', + 'app_color' => 'Головний колір', + 'link_color' => 'Колір посилання за замовчуванням', + 'content_colors_desc' => 'Установіть кольори для всіх елементів в ієрархії організації сторінки. Для зручності читання рекомендується вибирати кольори з такою ж яскравістю, як і кольори за замовчуванням.', 'bookshelf_color' => 'Колір полиці', 'book_color' => 'Колір книги', 'chapter_color' => 'Колір глави', @@ -137,11 +137,11 @@ return [ // Role Settings 'roles' => 'Ролі', 'role_user_roles' => 'Ролі користувача', - 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', - 'roles_x_permissions_provided' => ':count permission|:count permissions', - 'roles_assigned_users' => 'Assigned Users', - 'roles_permissions_provided' => 'Provided Permissions', + 'roles_index_desc' => 'Ролі використовуються для групування користувачів і надання системних дозволів їхнім учасникам. Якщо користувач є членом кількох ролей, надані привілеї сумуються, і користувач успадковує всі здібності.', + 'roles_x_users_assigned' => ':count користувач призначений|:count користувачів призначених', + 'roles_x_permissions_provided' => ':count дозвіл|:count дозволів', + 'roles_assigned_users' => 'Призначені користувачі', + 'roles_permissions_provided' => 'Надані доступи', 'role_create' => 'Створити нову роль', 'role_delete' => 'Видалити роль', 'role_delete_confirm' => 'Це призведе до видалення ролі з назвою \':roleName\'.', @@ -178,7 +178,7 @@ return [ // Users 'users' => 'Користувачі', - 'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.', + 'users_index_desc' => 'Створюйте та керуйте індивідуальними обліковими записами користувачів у системі. Облікові записи користувачів використовуються для входу та атрибуції вмісту та активності. Дозволи доступу в основному залежать від ролей, але право власності на вміст користувача, серед інших факторів, також може впливати на дозволи та доступ.', 'user_profile' => 'Профіль користувача', 'users_add_new' => 'Додати нового користувача', 'users_search' => 'Пошук користувачів', @@ -248,8 +248,8 @@ return [ // Webhooks 'webhooks' => 'Веб-хуки', - 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', + 'webhooks_index_desc' => 'Вебхуки – це спосіб надсилання даних на зовнішні URL-адреси, коли в системі відбуваються певні дії та події, що дозволяє інтегрувати події на основі зовнішніх платформ, таких як системи обміну повідомленнями чи сповіщення.', + 'webhooks_x_trigger_events' => ':count тригерна подія|:count тригерних подій', 'webhooks_create' => 'Створити новий Веб-хук', 'webhooks_none_created' => 'Немає створених Веб-хуків.', 'webhooks_edit' => 'Редагувати Веб-хук', diff --git a/lang/zh_CN/activities.php b/lang/zh_CN/activities.php index faad21918..d282fb3a1 100644 --- a/lang/zh_CN/activities.php +++ b/lang/zh_CN/activities.php @@ -68,9 +68,9 @@ return [ 'user_delete_notification' => '成功移除用户', // Roles - 'role_create_notification' => 'Role successfully created', - 'role_update_notification' => 'Role successfully updated', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create_notification' => '角色创建成功', + 'role_update_notification' => '角色更新成功', + 'role_delete_notification' => '角色删除成功', // Other 'commented_on' => '评论', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index dce9e7e14..6d6faea76 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新于 :timeLength', 'meta_updated_name' => '由 :user 更新于 :timeLength', 'meta_owned_name' => '拥有者 :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_page_count' => '被 :count 个页面引用|被 :count 个页面引用', 'entity_select' => '选择项目', 'entity_select_lack_permission' => '您没有选择此项目所需的权限', 'images' => '图片', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index 36dd21eb1..13036bd09 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -138,8 +138,8 @@ return [ 'roles' => '角色', 'role_user_roles' => '用户角色', 'roles_index_desc' => '角色用于对用户进行分组并为其成员提供系统权限。当一个用户是多个角色的成员时,授予的权限将叠加,用户将继承所有角色的能力。', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', - 'roles_x_permissions_provided' => ':count permission|:count permissions', + 'roles_x_users_assigned' => ':count 位用户已分配|:count 位用户已分配', + 'roles_x_permissions_provided' => ':count 个权限|:count 个权限', 'roles_assigned_users' => '已分配用户', 'roles_permissions_provided' => '已提供权限', 'role_create' => '创建角色', @@ -249,7 +249,7 @@ return [ // Webhooks 'webhooks' => 'Webhooks', 'webhooks_index_desc' => 'Webhook 是一种在系统内发生某些操作和事件时将数据发送到外部 URL 的方法,它允许与外部平台(例如消息传递或通知系统)进行基于事件的集成。', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', + 'webhooks_x_trigger_events' => ':count 个触发事件 |:count 个触发事件', 'webhooks_create' => '新建 Webhook', 'webhooks_none_created' => '尚未创建任何 Webhook。', 'webhooks_edit' => '编辑 Webhook', From 7b5111571c08b0c09eab4218249a49053b1bcec5 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Tue, 28 Feb 2023 01:01:25 +0000 Subject: [PATCH 20/27] Removed bookstack wording instances in color setting options --- lang/en/settings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/en/settings.php b/lang/en/settings.php index 76e689b6d..38d817915 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -50,8 +50,8 @@ return [ // Color settings 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the BookStack interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the primary color and default link color for BookStack. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the Bookstack interface.', + 'color_scheme_desc' => 'Set the colors to use in the application user interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', + 'ui_colors_desc' => 'Set the application primary color and default link color. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the application interface.', 'app_color' => 'Primary Color', 'link_color' => 'Default Link Color', 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', From c42956bcafc7c43275457887c119476af8f72b36 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Mon, 13 Mar 2023 13:18:33 +0000 Subject: [PATCH 21/27] Started build of content-permissions API endpoints --- app/Auth/Permissions/EntityPermission.php | 17 ++-- app/Entities/EntityProvider.php | 38 +++----- app/Entities/Tools/PermissionsUpdater.php | 74 +++++++++++++++- .../Api/ContentPermissionsController.php | 87 +++++++++++++++++++ routes/api.php | 4 + 5 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 app/Http/Controllers/Api/ContentPermissionsController.php diff --git a/app/Auth/Permissions/EntityPermission.php b/app/Auth/Permissions/EntityPermission.php index 32ebc440d..603cf61ad 100644 --- a/app/Auth/Permissions/EntityPermission.php +++ b/app/Auth/Permissions/EntityPermission.php @@ -5,7 +5,6 @@ namespace BookStack\Auth\Permissions; use BookStack\Auth\Role; use BookStack\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\MorphTo; /** * @property int $id @@ -23,14 +22,14 @@ class EntityPermission extends Model protected $fillable = ['role_id', 'view', 'create', 'update', 'delete']; public $timestamps = false; - - /** - * Get this restriction's attached entity. - */ - public function restrictable(): MorphTo - { - return $this->morphTo('restrictable'); - } + protected $hidden = ['entity_id', 'entity_type', 'id']; + protected $casts = [ + 'view' => 'boolean', + 'create' => 'boolean', + 'read' => 'boolean', + 'update' => 'boolean', + 'delete' => 'boolean', + ]; /** * Get the role assigned to this entity permission. diff --git a/app/Entities/EntityProvider.php b/app/Entities/EntityProvider.php index aaf392c7b..365daf7eb 100644 --- a/app/Entities/EntityProvider.php +++ b/app/Entities/EntityProvider.php @@ -18,30 +18,11 @@ use BookStack\Entities\Models\PageRevision; */ class EntityProvider { - /** - * @var Bookshelf - */ - public $bookshelf; - - /** - * @var Book - */ - public $book; - - /** - * @var Chapter - */ - public $chapter; - - /** - * @var Page - */ - public $page; - - /** - * @var PageRevision - */ - public $pageRevision; + public Bookshelf $bookshelf; + public Book $book; + public Chapter $chapter; + public Page $page; + public PageRevision $pageRevision; public function __construct() { @@ -69,13 +50,18 @@ class EntityProvider } /** - * Get an entity instance by it's basic name. + * Get an entity instance by its basic name. */ public function get(string $type): Entity { $type = strtolower($type); + $instance = $this->all()[$type] ?? null; - return $this->all()[$type]; + if (is_null($instance)) { + throw new \InvalidArgumentException("Provided type \"{$type}\" is not a valid entity type"); + } + + return $instance; } /** diff --git a/app/Entities/Tools/PermissionsUpdater.php b/app/Entities/Tools/PermissionsUpdater.php index eb4eb6b48..0d1d307af 100644 --- a/app/Entities/Tools/PermissionsUpdater.php +++ b/app/Entities/Tools/PermissionsUpdater.php @@ -4,20 +4,20 @@ namespace BookStack\Entities\Tools; use BookStack\Actions\ActivityType; use BookStack\Auth\Permissions\EntityPermission; +use BookStack\Auth\Role; use BookStack\Auth\User; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Entity; use BookStack\Facades\Activity; use Illuminate\Http\Request; -use Illuminate\Support\Collection; class PermissionsUpdater { /** * Update an entities permissions from a permission form submit request. */ - public function updateFromPermissionsForm(Entity $entity, Request $request) + public function updateFromPermissionsForm(Entity $entity, Request $request): void { $permissions = $request->get('permissions', null); $ownerId = $request->get('owned_by', null); @@ -39,12 +39,44 @@ class PermissionsUpdater Activity::add(ActivityType::PERMISSIONS_UPDATE, $entity); } + /** + * Update permissions from API request data. + */ + public function updateFromApiRequestData(Entity $entity, array $data): void + { + if (isset($data['override_role_permissions'])) { + $entity->permissions()->where('role_id', '!=', 0)->delete(); + $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['override_role_permissions'] ?? [], false); + $entity->permissions()->createMany($rolePermissionData); + } + + if (array_key_exists('override_fallback_permissions', $data)) { + $entity->permissions()->where('role_id', '=', 0)->delete(); + } + + if (isset($data['override_fallback_permissions'])) { + $data = $data['override_fallback_permissions']; + $data['role_id'] = 0; + $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions([$data], true); + $entity->permissions()->createMany($rolePermissionData); + } + + if (isset($data['owner_id'])) { + $this->updateOwnerFromId($entity, intval($data['owner_id'])); + } + + $entity->save(); + $entity->rebuildPermissions(); + + Activity::add(ActivityType::PERMISSIONS_UPDATE, $entity); + } + /** * Update the owner of the given entity. * Checks the user exists in the system first. * Does not save the model, just updates it. */ - protected function updateOwnerFromId(Entity $entity, int $newOwnerId) + protected function updateOwnerFromId(Entity $entity, int $newOwnerId): void { $newOwner = User::query()->find($newOwnerId); if (!is_null($newOwner)) { @@ -67,7 +99,41 @@ class PermissionsUpdater $formatted[] = $entityPermissionData; } - return $formatted; + return $this->filterEntityPermissionDataUponRole($formatted, true); + } + + protected function formatPermissionsFromApiRequestToEntityPermissions(array $permissions, bool $allowFallback): array + { + $formatted = []; + + foreach ($permissions as $requestPermissionData) { + $entityPermissionData = ['role_id' => $requestPermissionData['role_id']]; + foreach (EntityPermission::PERMISSIONS as $permission) { + $entityPermissionData[$permission] = boolval($requestPermissionData[$permission] ?? false); + } + $formatted[] = $entityPermissionData; + } + + return $this->filterEntityPermissionDataUponRole($formatted, $allowFallback); + } + + protected function filterEntityPermissionDataUponRole(array $entityPermissionData, bool $allowFallback): array + { + $roleIds = []; + foreach ($entityPermissionData as $permissionEntry) { + $roleIds[] = intval($permissionEntry['role_id']); + } + + $actualRoleIds = array_unique(array_values(array_filter($roleIds))); + $rolesById = Role::query()->whereIn('id', $actualRoleIds)->get('id')->keyBy('id'); + + return array_values(array_filter($entityPermissionData, function ($data) use ($rolesById, $allowFallback) { + if (intval($data['role_id']) === 0) { + return $allowFallback; + } + + return $rolesById->has($data['role_id']); + })); } /** diff --git a/app/Http/Controllers/Api/ContentPermissionsController.php b/app/Http/Controllers/Api/ContentPermissionsController.php new file mode 100644 index 000000000..16fb68a0f --- /dev/null +++ b/app/Http/Controllers/Api/ContentPermissionsController.php @@ -0,0 +1,87 @@ +<?php + +namespace BookStack\Http\Controllers\Api; + +use BookStack\Entities\EntityProvider; +use BookStack\Entities\Models\Entity; +use BookStack\Entities\Tools\PermissionsUpdater; +use Illuminate\Http\Request; + +class ContentPermissionsController extends ApiController +{ + public function __construct( + protected PermissionsUpdater $permissionsUpdater, + protected EntityProvider $entities + ) { + } + + protected $rules = [ + 'update' => [ + 'owner_id' => ['int'], + + 'override_role_permissions' => ['array'], + 'override_role_permissions.*.role_id' => ['required', 'int'], + 'override_role_permissions.*.view' => ['required', 'boolean'], + 'override_role_permissions.*.create' => ['required', 'boolean'], + 'override_role_permissions.*.update' => ['required', 'boolean'], + 'override_role_permissions.*.delete' => ['required', 'boolean'], + + 'override_fallback_permissions' => ['nullable'], + 'override_fallback_permissions.view' => ['required', 'boolean'], + 'override_fallback_permissions.create' => ['required', 'boolean'], + 'override_fallback_permissions.update' => ['required', 'boolean'], + 'override_fallback_permissions.delete' => ['required', 'boolean'], + ] + ]; + + /** + * Read the configured content-level permissions for the item of the given type and ID. + * 'contentType' should be one of: page, book, chapter, bookshelf. + * 'contentId' should be the relevant ID of that item type you'd like to handle permissions for. + */ + public function read(string $contentType, string $contentId) + { + $entity = $this->entities->get($contentType) + ->newQuery()->scopes(['visible'])->findOrFail($contentId); + + $this->checkOwnablePermission('restrictions-manage', $entity); + + return response()->json($this->formattedPermissionDataForEntity($entity)); + } + + /** + * Update the configured content-level permissions for the item of the given type and ID. + * 'contentType' should be one of: page, book, chapter, bookshelf. + * 'contentId' should be the relevant ID of that item type you'd like to handle permissions for. + */ + public function update(Request $request, string $contentType, string $contentId) + { + $entity = $this->entities->get($contentType) + ->newQuery()->scopes(['visible'])->findOrFail($contentId); + + $this->checkOwnablePermission('restrictions-manage', $entity); + + $data = $this->validate($request, $this->rules()['update']); + $this->permissionsUpdater->updateFromApiRequestData($entity, $data); + + return response()->json($this->formattedPermissionDataForEntity($entity)); + } + + protected function formattedPermissionDataForEntity(Entity $entity): array + { + $rolePermissions = $entity->permissions() + ->where('role_id', '!=', 0) + ->with(['role:id,display_name']) + ->get(); + + $fallback = $entity->permissions()->where('role_id', '=', 0)->first(); + $fallback?->makeHidden('role_id'); + + return [ + 'owner' => $entity->ownedBy()->first(), + 'override_role_permissions' => $rolePermissions, + 'override_fallback_permissions' => $fallback, + 'inheriting' => is_null($fallback), + ]; + } +} diff --git a/routes/api.php b/routes/api.php index d1b64d455..1b852fed7 100644 --- a/routes/api.php +++ b/routes/api.php @@ -13,6 +13,7 @@ use BookStack\Http\Controllers\Api\BookExportApiController; use BookStack\Http\Controllers\Api\BookshelfApiController; use BookStack\Http\Controllers\Api\ChapterApiController; use BookStack\Http\Controllers\Api\ChapterExportApiController; +use BookStack\Http\Controllers\Api\ContentPermissionsController; use BookStack\Http\Controllers\Api\PageApiController; use BookStack\Http\Controllers\Api\PageExportApiController; use BookStack\Http\Controllers\Api\RecycleBinApiController; @@ -85,3 +86,6 @@ Route::delete('roles/{id}', [RoleApiController::class, 'delete']); Route::get('recycle-bin', [RecycleBinApiController::class, 'list']); Route::put('recycle-bin/{deletionId}', [RecycleBinApiController::class, 'restore']); Route::delete('recycle-bin/{deletionId}', [RecycleBinApiController::class, 'destroy']); + +Route::get('content-permissions/{contentType}/{contentId}', [ContentPermissionsController::class, 'read']); +Route::put('content-permissions/{contentType}/{contentId}', [ContentPermissionsController::class, 'update']); From 0de75300591873ece8af60152fdb51172e41f3a5 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Mon, 13 Mar 2023 20:06:52 +0000 Subject: [PATCH 22/27] Tweaked content permission endpoints, covered with tests --- app/Entities/Tools/PermissionsUpdater.php | 10 +- .../Api/ContentPermissionsController.php | 43 ++- tests/Api/ContentPermissionsApiTest.php | 262 ++++++++++++++++++ 3 files changed, 295 insertions(+), 20 deletions(-) create mode 100644 tests/Api/ContentPermissionsApiTest.php diff --git a/app/Entities/Tools/PermissionsUpdater.php b/app/Entities/Tools/PermissionsUpdater.php index 0d1d307af..36ed7ccde 100644 --- a/app/Entities/Tools/PermissionsUpdater.php +++ b/app/Entities/Tools/PermissionsUpdater.php @@ -44,18 +44,18 @@ class PermissionsUpdater */ public function updateFromApiRequestData(Entity $entity, array $data): void { - if (isset($data['override_role_permissions'])) { + if (isset($data['role_permissions'])) { $entity->permissions()->where('role_id', '!=', 0)->delete(); - $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['override_role_permissions'] ?? [], false); + $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions($data['role_permissions'] ?? [], false); $entity->permissions()->createMany($rolePermissionData); } - if (array_key_exists('override_fallback_permissions', $data)) { + if (array_key_exists('fallback_permissions', $data)) { $entity->permissions()->where('role_id', '=', 0)->delete(); } - if (isset($data['override_fallback_permissions'])) { - $data = $data['override_fallback_permissions']; + if (isset($data['fallback_permissions']['inheriting']) && $data['fallback_permissions']['inheriting'] !== true) { + $data = $data['fallback_permissions']; $data['role_id'] = 0; $rolePermissionData = $this->formatPermissionsFromApiRequestToEntityPermissions([$data], true); $entity->permissions()->createMany($rolePermissionData); diff --git a/app/Http/Controllers/Api/ContentPermissionsController.php b/app/Http/Controllers/Api/ContentPermissionsController.php index 16fb68a0f..1db90f97a 100644 --- a/app/Http/Controllers/Api/ContentPermissionsController.php +++ b/app/Http/Controllers/Api/ContentPermissionsController.php @@ -19,18 +19,19 @@ class ContentPermissionsController extends ApiController 'update' => [ 'owner_id' => ['int'], - 'override_role_permissions' => ['array'], - 'override_role_permissions.*.role_id' => ['required', 'int'], - 'override_role_permissions.*.view' => ['required', 'boolean'], - 'override_role_permissions.*.create' => ['required', 'boolean'], - 'override_role_permissions.*.update' => ['required', 'boolean'], - 'override_role_permissions.*.delete' => ['required', 'boolean'], + 'role_permissions' => ['array'], + 'role_permissions.*.role_id' => ['required', 'int', 'exists:roles,id'], + 'role_permissions.*.view' => ['required', 'boolean'], + 'role_permissions.*.create' => ['required', 'boolean'], + 'role_permissions.*.update' => ['required', 'boolean'], + 'role_permissions.*.delete' => ['required', 'boolean'], - 'override_fallback_permissions' => ['nullable'], - 'override_fallback_permissions.view' => ['required', 'boolean'], - 'override_fallback_permissions.create' => ['required', 'boolean'], - 'override_fallback_permissions.update' => ['required', 'boolean'], - 'override_fallback_permissions.delete' => ['required', 'boolean'], + 'fallback_permissions' => ['nullable'], + 'fallback_permissions.inheriting' => ['required_with:fallback_permissions', 'boolean'], + 'fallback_permissions.view' => ['required_if:fallback_permissions.inheriting,false', 'boolean'], + 'fallback_permissions.create' => ['required_if:fallback_permissions.inheriting,false', 'boolean'], + 'fallback_permissions.update' => ['required_if:fallback_permissions.inheriting,false', 'boolean'], + 'fallback_permissions.delete' => ['required_if:fallback_permissions.inheriting,false', 'boolean'], ] ]; @@ -38,6 +39,9 @@ class ContentPermissionsController extends ApiController * Read the configured content-level permissions for the item of the given type and ID. * 'contentType' should be one of: page, book, chapter, bookshelf. * 'contentId' should be the relevant ID of that item type you'd like to handle permissions for. + * The permissions shown are those that override the default for just the specified item, they do not show the + * full evaluated permission for a role, nor do they reflect permissions inherited from other items in the hierarchy. + * Fallback permission values may be `null` when inheriting is active. */ public function read(string $contentType, string $contentId) { @@ -53,6 +57,10 @@ class ContentPermissionsController extends ApiController * Update the configured content-level permissions for the item of the given type and ID. * 'contentType' should be one of: page, book, chapter, bookshelf. * 'contentId' should be the relevant ID of that item type you'd like to handle permissions for. + * Providing an empty `role_permissions` array will remove any existing configured role permissions, + * so you may want to fetch existing permissions beforehand if just adding/removing a single item. + * You should completely omit the `owner_id`, `role_permissions` and/or the `fallback_permissions` properties + * if you don't wish to update details within those categories. */ public function update(Request $request, string $contentType, string $contentId) { @@ -75,13 +83,18 @@ class ContentPermissionsController extends ApiController ->get(); $fallback = $entity->permissions()->where('role_id', '=', 0)->first(); - $fallback?->makeHidden('role_id'); + $fallbackData = [ + 'inheriting' => is_null($fallback), + 'view' => $fallback->view ?? null, + 'create' => $fallback->create ?? null, + 'update' => $fallback->update ?? null, + 'delete' => $fallback->delete ?? null, + ]; return [ 'owner' => $entity->ownedBy()->first(), - 'override_role_permissions' => $rolePermissions, - 'override_fallback_permissions' => $fallback, - 'inheriting' => is_null($fallback), + 'role_permissions' => $rolePermissions, + 'fallback_permissions' => $fallbackData, ]; } } diff --git a/tests/Api/ContentPermissionsApiTest.php b/tests/Api/ContentPermissionsApiTest.php new file mode 100644 index 000000000..50b82e5c4 --- /dev/null +++ b/tests/Api/ContentPermissionsApiTest.php @@ -0,0 +1,262 @@ +<?php + +namespace Tests\Api; + +use Tests\TestCase; + +class ContentPermissionsApiTest extends TestCase +{ + use TestsApi; + + protected string $baseEndpoint = '/api/content-permissions'; + + public function test_user_roles_manage_permission_needed_for_all_endpoints() + { + $page = $this->entities->page(); + $endpointMap = [ + ['get', "/api/content-permissions/page/{$page->id}"], + ['put', "/api/content-permissions/page/{$page->id}"], + ]; + $editor = $this->users->editor(); + + $this->actingAs($editor, 'api'); + foreach ($endpointMap as [$method, $uri]) { + $resp = $this->json($method, $uri); + $resp->assertStatus(403); + $resp->assertJson($this->permissionErrorResponse()); + } + + $this->permissions->grantUserRolePermissions($editor, ['restrictions-manage-all']); + + foreach ($endpointMap as [$method, $uri]) { + $resp = $this->json($method, $uri); + $this->assertNotEquals(403, $resp->getStatusCode()); + } + } + + public function test_read_endpoint_shows_expected_detail() + { + $page = $this->entities->page(); + $owner = $this->users->newUser(); + $role = $this->users->createRole(); + $this->permissions->addEntityPermission($page, ['view', 'delete'], $role); + $this->permissions->changeEntityOwner($page, $owner); + $this->permissions->setFallbackPermissions($page, ['update', 'create']); + + $this->actingAsApiAdmin(); + $resp = $this->getJson($this->baseEndpoint . "/page/{$page->id}"); + + $resp->assertOk(); + $resp->assertExactJson([ + 'owner' => [ + 'id' => $owner->id, 'name' => $owner->name, 'slug' => $owner->slug, + ], + 'role_permissions' => [ + [ + 'role_id' => $role->id, + 'view' => true, + 'create' => false, + 'update' => false, + 'delete' => true, + 'role' => [ + 'id' => $role->id, + 'display_name' => $role->display_name, + ] + ] + ], + 'fallback_permissions' => [ + 'inheriting' => false, + 'view' => false, + 'create' => true, + 'update' => true, + 'delete' => false, + ], + ]); + } + + public function test_read_endpoint_shows_expected_detail_when_items_are_empty() + { + $page = $this->entities->page(); + $page->permissions()->delete(); + $page->owned_by = null; + $page->save(); + + $this->actingAsApiAdmin(); + $resp = $this->getJson($this->baseEndpoint . "/page/{$page->id}"); + + $resp->assertOk(); + $resp->assertExactJson([ + 'owner' => null, + 'role_permissions' => [], + 'fallback_permissions' => [ + 'inheriting' => true, + 'view' => null, + 'create' => null, + 'update' => null, + 'delete' => null, + ], + ]); + } + + public function test_update_endpoint_can_change_owner() + { + $page = $this->entities->page(); + $newOwner = $this->users->newUser(); + + $this->actingAsApiAdmin(); + $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [ + 'owner_id' => $newOwner->id, + ]); + + $resp->assertOk(); + $resp->assertExactJson([ + 'owner' => ['id' => $newOwner->id, 'name' => $newOwner->name, 'slug' => $newOwner->slug], + 'role_permissions' => [], + 'fallback_permissions' => [ + 'inheriting' => true, + 'view' => null, + 'create' => null, + 'update' => null, + 'delete' => null, + ], + ]); + } + + public function test_update_can_set_role_permissions() + { + $page = $this->entities->page(); + $page->owned_by = null; + $page->save(); + $newRoleA = $this->users->createRole(); + $newRoleB = $this->users->createRole(); + + $this->actingAsApiAdmin(); + $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [ + 'role_permissions' => [ + ['role_id' => $newRoleA->id, 'view' => true, 'create' => false, 'update' => false, 'delete' => false], + ['role_id' => $newRoleB->id, 'view' => true, 'create' => false, 'update' => true, 'delete' => true], + ], + ]); + + $resp->assertOk(); + $resp->assertExactJson([ + 'owner' => null, + 'role_permissions' => [ + [ + 'role_id' => $newRoleA->id, + 'view' => true, + 'create' => false, + 'update' => false, + 'delete' => false, + 'role' => [ + 'id' => $newRoleA->id, + 'display_name' => $newRoleA->display_name, + ] + ], + [ + 'role_id' => $newRoleB->id, + 'view' => true, + 'create' => false, + 'update' => true, + 'delete' => true, + 'role' => [ + 'id' => $newRoleB->id, + 'display_name' => $newRoleB->display_name, + ] + ] + ], + 'fallback_permissions' => [ + 'inheriting' => true, + 'view' => null, + 'create' => null, + 'update' => null, + 'delete' => null, + ], + ]); + } + + public function test_update_can_set_fallback_permissions() + { + $page = $this->entities->page(); + $page->owned_by = null; + $page->save(); + + $this->actingAsApiAdmin(); + $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [ + 'fallback_permissions' => [ + 'inheriting' => false, + 'view' => true, + 'create' => true, + 'update' => true, + 'delete' => false, + ], + ]); + + $resp->assertOk(); + $resp->assertExactJson([ + 'owner' => null, + 'role_permissions' => [], + 'fallback_permissions' => [ + 'inheriting' => false, + 'view' => true, + 'create' => true, + 'update' => true, + 'delete' => false, + ], + ]); + } + + public function test_update_can_clear_roles_permissions() + { + $page = $this->entities->page(); + $this->permissions->addEntityPermission($page, ['view'], $this->users->createRole()); + $page->owned_by = null; + $page->save(); + + $this->actingAsApiAdmin(); + $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [ + 'role_permissions' => [], + ]); + + $resp->assertOk(); + $resp->assertExactJson([ + 'owner' => null, + 'role_permissions' => [], + 'fallback_permissions' => [ + 'inheriting' => true, + 'view' => null, + 'create' => null, + 'update' => null, + 'delete' => null, + ], + ]); + } + + public function test_update_can_clear_fallback_permissions() + { + $page = $this->entities->page(); + $this->permissions->setFallbackPermissions($page, ['view', 'update']); + $page->owned_by = null; + $page->save(); + + $this->actingAsApiAdmin(); + $resp = $this->putJson($this->baseEndpoint . "/page/{$page->id}", [ + 'fallback_permissions' => [ + 'inheriting' => true, + ], + ]); + + $resp->assertOk(); + $resp->assertExactJson([ + 'owner' => null, + 'role_permissions' => [], + 'fallback_permissions' => [ + 'inheriting' => true, + 'view' => null, + 'create' => null, + 'update' => null, + 'delete' => null, + ], + ]); + } +} From 190392482992801e4bed18828ad328b07f7e572a Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Mon, 13 Mar 2023 20:41:32 +0000 Subject: [PATCH 23/27] Added content-perms API examples and docs tweaks --- .../Api/ContentPermissionsController.php | 4 +- .../requests/content-permissions-update.json | 26 +++++++++++++ .../responses/content-permissions-read.json | 38 +++++++++++++++++++ .../responses/content-permissions-update.json | 38 +++++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 dev/api/requests/content-permissions-update.json create mode 100644 dev/api/responses/content-permissions-read.json create mode 100644 dev/api/responses/content-permissions-update.json diff --git a/app/Http/Controllers/Api/ContentPermissionsController.php b/app/Http/Controllers/Api/ContentPermissionsController.php index 1db90f97a..ef17af8ad 100644 --- a/app/Http/Controllers/Api/ContentPermissionsController.php +++ b/app/Http/Controllers/Api/ContentPermissionsController.php @@ -54,13 +54,13 @@ class ContentPermissionsController extends ApiController } /** - * Update the configured content-level permissions for the item of the given type and ID. + * Update the configured content-level permission overrides for the item of the given type and ID. * 'contentType' should be one of: page, book, chapter, bookshelf. * 'contentId' should be the relevant ID of that item type you'd like to handle permissions for. * Providing an empty `role_permissions` array will remove any existing configured role permissions, * so you may want to fetch existing permissions beforehand if just adding/removing a single item. * You should completely omit the `owner_id`, `role_permissions` and/or the `fallback_permissions` properties - * if you don't wish to update details within those categories. + * from your request data if you don't wish to update details within those categories. */ public function update(Request $request, string $contentType, string $contentId) { diff --git a/dev/api/requests/content-permissions-update.json b/dev/api/requests/content-permissions-update.json new file mode 100644 index 000000000..124bb8bff --- /dev/null +++ b/dev/api/requests/content-permissions-update.json @@ -0,0 +1,26 @@ +{ + "owner_id": 1, + "role_permissions": [ + { + "role_id": 2, + "view": true, + "create": true, + "update": true, + "delete": false + }, + { + "role_id": 3, + "view": false, + "create": false, + "update": false, + "delete": false + } + ], + "fallback_permissions": { + "inheriting": false, + "view": true, + "create": true, + "update": false, + "delete": false + } +} \ No newline at end of file diff --git a/dev/api/responses/content-permissions-read.json b/dev/api/responses/content-permissions-read.json new file mode 100644 index 000000000..591fc5c86 --- /dev/null +++ b/dev/api/responses/content-permissions-read.json @@ -0,0 +1,38 @@ +{ + "owner": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "role_permissions": [ + { + "role_id": 2, + "view": true, + "create": false, + "update": true, + "delete": false, + "role": { + "id": 2, + "display_name": "Editor" + } + }, + { + "role_id": 10, + "view": true, + "create": true, + "update": false, + "delete": false, + "role": { + "id": 10, + "display_name": "Wizards of the west" + } + } + ], + "fallback_permissions": { + "inheriting": false, + "view": true, + "create": false, + "update": false, + "delete": false + } +} \ No newline at end of file diff --git a/dev/api/responses/content-permissions-update.json b/dev/api/responses/content-permissions-update.json new file mode 100644 index 000000000..67fa40bf0 --- /dev/null +++ b/dev/api/responses/content-permissions-update.json @@ -0,0 +1,38 @@ +{ + "owner": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "role_permissions": [ + { + "role_id": 2, + "view": true, + "create": true, + "update": true, + "delete": false, + "role": { + "id": 2, + "display_name": "Editor" + } + }, + { + "role_id": 3, + "view": false, + "create": false, + "update": false, + "delete": false, + "role": { + "id": 3, + "display_name": "Viewer" + } + } + ], + "fallback_permissions": { + "inheriting": false, + "view": true, + "create": true, + "update": false, + "delete": false + } +} \ No newline at end of file From 6357056d7b4e5e4a738bba2c416b86e54ab00533 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Mon, 13 Mar 2023 21:03:00 +0000 Subject: [PATCH 24/27] Updated php deps --- composer.json | 2 +- composer.lock | 247 ++++++++++++++++++++++++-------------------------- 2 files changed, 120 insertions(+), 129 deletions(-) diff --git a/composer.json b/composer.json index b1ac1789b..44143e042 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "nunomaduro/larastan": "^2.4", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.7", - "ssddanbrown/asserthtml": "^1.0" + "ssddanbrown/asserthtml": "^2.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 040e9ce04..bbdf4efa9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "64276cbeb1f79f4c94992cc739807d72", + "content-hash": "5a066407dfbd1809ffd39114a873333d", "packages": [ { "name": "aws/aws-crt-php", @@ -58,16 +58,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.260.3", + "version": "3.261.10", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "547b8047b2f9a551a7100b22e1abe1a3cc1b0ff0" + "reference": "4889eff2b3fe35e878fbcaf8374d73f043609170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/547b8047b2f9a551a7100b22e1abe1a3cc1b0ff0", - "reference": "547b8047b2f9a551a7100b22e1abe1a3cc1b0ff0", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4889eff2b3fe35e878fbcaf8374d73f043609170", + "reference": "4889eff2b3fe35e878fbcaf8374d73f043609170", "shasum": "" }, "require": { @@ -146,9 +146,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.260.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.261.10" }, - "time": "2023-02-24T19:25:34+00:00" + "time": "2023-03-13T18:19:14+00:00" }, { "name": "bacon/bacon-qr-code", @@ -417,21 +417,24 @@ }, { "name": "dasprid/enum", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2" + "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/5abf82f213618696dda8e3bf6f64dd042d8542b2", - "reference": "5abf82f213618696dda8e3bf6f64dd042d8542b2", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", "shasum": "" }, + "require": { + "php": ">=7.1 <9.0" + }, "require-dev": { "phpunit/phpunit": "^7 | ^8 | ^9", - "squizlabs/php_codesniffer": "^3.4" + "squizlabs/php_codesniffer": "*" }, "type": "library", "autoload": { @@ -458,9 +461,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.3" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.4" }, - "time": "2020-10-02T16:03:48+00:00" + "time": "2023-03-01T18:44:03+00:00" }, { "name": "dflydev/dot-access-data", @@ -632,16 +635,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.0", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "85b98cb23c8af471a67abfe14485da696bcabc2e" + "reference": "57815c7bbcda3cd18871d253c1dd8cbe56f8526e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/85b98cb23c8af471a67abfe14485da696bcabc2e", - "reference": "85b98cb23c8af471a67abfe14485da696bcabc2e", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/57815c7bbcda3cd18871d253c1dd8cbe56f8526e", + "reference": "57815c7bbcda3cd18871d253c1dd8cbe56f8526e", "shasum": "" }, "require": { @@ -657,11 +660,11 @@ "doctrine/coding-standard": "11.1.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.9.14", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "9.6.3", + "phpstan/phpstan": "1.10.3", + "phpstan/phpstan-strict-rules": "^1.5", + "phpunit/phpunit": "9.6.4", "psalm/plugin-phpunit": "0.18.4", - "squizlabs/php_codesniffer": "3.7.1", + "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", "vimeo/psalm": "4.30.0" @@ -724,7 +727,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.0" + "source": "https://github.com/doctrine/dbal/tree/3.6.1" }, "funding": [ { @@ -740,7 +743,7 @@ "type": "tidelift" } ], - "time": "2023-02-07T22:52:03+00:00" + "time": "2023-03-02T19:26:24+00:00" }, { "name": "doctrine/deprecations", @@ -1309,24 +1312,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9" + "phpoption/phpoption": "^1.9.1" }, "require-dev": { - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "autoload": { @@ -1355,7 +1358,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" }, "funding": [ { @@ -1367,7 +1370,7 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:56:11+00:00" + "time": "2023-02-25T20:23:15+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1583,16 +1586,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.3", + "version": "2.4.4", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "67c26b443f348a51926030c83481b85718457d3d" + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", - "reference": "67c26b443f348a51926030c83481b85718457d3d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", "shasum": "" }, "require": { @@ -1682,7 +1685,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.3" + "source": "https://github.com/guzzle/psr7/tree/2.4.4" }, "funding": [ { @@ -1698,7 +1701,7 @@ "type": "tidelift" } ], - "time": "2022-10-26T14:07:24+00:00" + "time": "2023-03-09T13:19:02+00:00" }, { "name": "guzzlehttp/uri-template", @@ -3462,16 +3465,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.3", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { @@ -3512,9 +3515,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" }, - "time": "2023-01-16T22:05:37+00:00" + "time": "2023-03-05T19:49:14+00:00" }, { "name": "nunomaduro/termwind", @@ -3867,24 +3870,24 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "extra": { @@ -3926,7 +3929,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" }, "funding": [ { @@ -3938,20 +3941,20 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:51:26+00:00" + "time": "2023-02-25T19:38:58+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.18", + "version": "3.0.19", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "f28693d38ba21bb0d9f0c411ee5dae2b178201da" + "reference": "cc181005cf548bfd8a4896383bb825d859259f95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/f28693d38ba21bb0d9f0c411ee5dae2b178201da", - "reference": "f28693d38ba21bb0d9f0c411ee5dae2b178201da", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cc181005cf548bfd8a4896383bb825d859259f95", + "reference": "cc181005cf548bfd8a4896383bb825d859259f95", "shasum": "" }, "require": { @@ -4032,7 +4035,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.18" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.19" }, "funding": [ { @@ -4048,7 +4051,7 @@ "type": "tidelift" } ], - "time": "2022-12-17T18:26:50+00:00" + "time": "2023-03-05T17:13:09+00:00" }, { "name": "pragmarx/google2fa", @@ -4104,33 +4107,27 @@ }, { "name": "predis/predis", - "version": "v2.1.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "c5b60884e89630f9518a7919f0566db438f0fc9a" + "reference": "a77a43913a74f9331f637bb12867eb8e274814e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/c5b60884e89630f9518a7919f0566db438f0fc9a", - "reference": "c5b60884e89630f9518a7919f0566db438f0fc9a", + "url": "https://api.github.com/repos/predis/predis/zipball/a77a43913a74f9331f637bb12867eb8e274814e5", + "reference": "a77a43913a74f9331f637bb12867eb8e274814e5", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8.0 || ~9.4.4" }, - "suggest": { - "ext-curl": "Allows access to Webdis when paired with phpiredis" - }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.0-dev" - } - }, "autoload": { "psr-4": { "Predis\\": "src/" @@ -4145,12 +4142,6 @@ "name": "Till Krüss", "homepage": "https://till.im", "role": "Maintainer" - }, - { - "name": "Daniele Alessandri", - "email": "suppakilla@gmail.com", - "homepage": "http://clorophilla.net", - "role": "Creator" } ], "description": "A flexible and feature-complete Redis client for PHP.", @@ -4162,7 +4153,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.1.1" + "source": "https://github.com/predis/predis/tree/v2.1.2" }, "funding": [ { @@ -4170,7 +4161,7 @@ "type": "github" } ], - "time": "2023-01-17T20:57:35+00:00" + "time": "2023-03-02T18:32:04+00:00" }, { "name": "psr/cache", @@ -8043,16 +8034,16 @@ }, { "name": "filp/whoops", - "version": "2.14.6", + "version": "2.15.1", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "f7948baaa0330277c729714910336383286305da" + "reference": "e864ac957acd66e1565f25efda61e37791a5db0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/f7948baaa0330277c729714910336383286305da", - "reference": "f7948baaa0330277c729714910336383286305da", + "url": "https://api.github.com/repos/filp/whoops/zipball/e864ac957acd66e1565f25efda61e37791a5db0b", + "reference": "e864ac957acd66e1565f25efda61e37791a5db0b", "shasum": "" }, "require": { @@ -8102,7 +8093,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.6" + "source": "https://github.com/filp/whoops/tree/2.15.1" }, "funding": [ { @@ -8110,7 +8101,7 @@ "type": "github" } ], - "time": "2022-11-02T16:23:29+00:00" + "time": "2023-03-06T18:09:13+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8305,16 +8296,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -8352,7 +8343,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -8360,7 +8351,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nunomaduro/collision", @@ -8452,16 +8443,16 @@ }, { "name": "nunomaduro/larastan", - "version": "2.4.1", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/larastan.git", - "reference": "238fdbfba3aae133cdec73e99826c9b0232141f7" + "reference": "072e2c9566ae000bf66c92384fc933b81885244b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/238fdbfba3aae133cdec73e99826c9b0232141f7", - "reference": "238fdbfba3aae133cdec73e99826c9b0232141f7", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/072e2c9566ae000bf66c92384fc933b81885244b", + "reference": "072e2c9566ae000bf66c92384fc933b81885244b", "shasum": "" }, "require": { @@ -8475,11 +8466,11 @@ "illuminate/support": "^9.47.0 || ^10.0.0", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.6.0", - "phpstan/phpstan": "^1.9.8" + "phpstan/phpstan": "~1.10.3" }, "require-dev": { "nikic/php-parser": "^4.15.2", - "orchestra/testbench": "^7.19.0|^8.0.0", + "orchestra/testbench": "^7.19.0 || ^8.0.0", "phpunit/phpunit": "^9.5.27" }, "suggest": { @@ -8524,7 +8515,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/larastan/issues", - "source": "https://github.com/nunomaduro/larastan/tree/2.4.1" + "source": "https://github.com/nunomaduro/larastan/tree/2.5.1" }, "funding": [ { @@ -8544,7 +8535,7 @@ "type": "patreon" } ], - "time": "2023-02-05T12:19:17+00:00" + "time": "2023-03-04T23:46:40+00:00" }, { "name": "phar-io/manifest", @@ -8746,16 +8737,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.3", + "version": "1.10.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64" + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64", - "reference": "5419375b5891add97dc74be71e6c1c34baaddf64", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", + "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a", "shasum": "" }, "require": { @@ -8785,7 +8776,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.10.3" + "source": "https://github.com/phpstan/phpstan/tree/1.10.6" }, "funding": [ { @@ -8801,20 +8792,20 @@ "type": "tidelift" } ], - "time": "2023-02-25T14:47:13+00:00" + "time": "2023-03-09T16:55:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.25", + "version": "9.2.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954" + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954", - "reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", "shasum": "" }, "require": { @@ -8836,8 +8827,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -8870,7 +8861,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" }, "funding": [ { @@ -8878,7 +8869,7 @@ "type": "github" } ], - "time": "2023-02-25T05:32:00+00:00" + "time": "2023-03-06T12:58:08+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9123,16 +9114,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.3", + "version": "9.6.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", + "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", "shasum": "" }, "require": { @@ -9165,8 +9156,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -9205,7 +9196,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" }, "funding": [ { @@ -9221,7 +9212,7 @@ "type": "tidelift" } ], - "time": "2023-02-04T13:37:15+00:00" + "time": "2023-03-09T06:34:10+00:00" }, { "name": "sebastian/cli-parser", @@ -10246,16 +10237,16 @@ }, { "name": "ssddanbrown/asserthtml", - "version": "v1.0.1", + "version": "v2.0.0", "source": { "type": "git", - "url": "https://github.com/ssddanbrown/htmlassert.git", - "reference": "f7d4352bb3d69347097b2841fd71934182821928" + "url": "https://github.com/ssddanbrown/asserthtml.git", + "reference": "6baf3ef2087f5928ae34f0d41db27aefcdf60414" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ssddanbrown/htmlassert/zipball/f7d4352bb3d69347097b2841fd71934182821928", - "reference": "f7d4352bb3d69347097b2841fd71934182821928", + "url": "https://api.github.com/repos/ssddanbrown/asserthtml/zipball/6baf3ef2087f5928ae34f0d41db27aefcdf60414", + "reference": "6baf3ef2087f5928ae34f0d41db27aefcdf60414", "shasum": "" }, "require": { @@ -10289,8 +10280,8 @@ "description": "HTML Content Assertions for PHPUnit", "homepage": "https://github.com/ssddanbrown/asserthtml", "support": { - "issues": "https://github.com/ssddanbrown/htmlassert/issues", - "source": "https://github.com/ssddanbrown/htmlassert/tree/v1.0.1" + "issues": "https://github.com/ssddanbrown/asserthtml/issues", + "source": "https://github.com/ssddanbrown/asserthtml/tree/v2.0.0" }, "funding": [ { @@ -10298,7 +10289,7 @@ "type": "github" } ], - "time": "2022-04-09T13:31:13+00:00" + "time": "2023-03-01T16:48:08+00:00" }, { "name": "symfony/dom-crawler", From d9eec6d82caf2c63c8535f6842612fc6939d5d0e Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Tue, 14 Mar 2023 12:19:19 +0000 Subject: [PATCH 25/27] Started Image API build --- ...php => ContentPermissionApiController.php} | 2 +- .../Api/ImageGalleryApiController.php | 127 ++++++++++++++++++ .../Controllers/Api/RoleApiController.php | 6 +- .../Images/GalleryImageController.php | 11 +- app/Uploads/Image.php | 11 ++ routes/api.php | 13 +- 6 files changed, 155 insertions(+), 15 deletions(-) rename app/Http/Controllers/Api/{ContentPermissionsController.php => ContentPermissionApiController.php} (98%) create mode 100644 app/Http/Controllers/Api/ImageGalleryApiController.php diff --git a/app/Http/Controllers/Api/ContentPermissionsController.php b/app/Http/Controllers/Api/ContentPermissionApiController.php similarity index 98% rename from app/Http/Controllers/Api/ContentPermissionsController.php rename to app/Http/Controllers/Api/ContentPermissionApiController.php index ef17af8ad..47a0d3782 100644 --- a/app/Http/Controllers/Api/ContentPermissionsController.php +++ b/app/Http/Controllers/Api/ContentPermissionApiController.php @@ -7,7 +7,7 @@ use BookStack\Entities\Models\Entity; use BookStack\Entities\Tools\PermissionsUpdater; use Illuminate\Http\Request; -class ContentPermissionsController extends ApiController +class ContentPermissionApiController extends ApiController { public function __construct( protected PermissionsUpdater $permissionsUpdater, diff --git a/app/Http/Controllers/Api/ImageGalleryApiController.php b/app/Http/Controllers/Api/ImageGalleryApiController.php new file mode 100644 index 000000000..85c0c3cef --- /dev/null +++ b/app/Http/Controllers/Api/ImageGalleryApiController.php @@ -0,0 +1,127 @@ +<?php + +namespace BookStack\Http\Controllers\Api; + +use BookStack\Uploads\Image; +use BookStack\Uploads\ImageRepo; +use Illuminate\Http\Request; + +class ImageGalleryApiController extends ApiController +{ + protected array $fieldsToExpose = [ + 'id', 'name', 'url', 'path', 'type', 'uploaded_to', 'created_by', 'updated_by', 'created_at', 'updated_at', + ]; + + public function __construct( + protected ImageRepo $imageRepo + ) { + } + + protected function rules(): array + { + return [ + 'create' => [ + 'type' => ['required', 'string', 'in:gallery,drawio'], + 'uploaded_to' => ['required', 'integer', 'exists:pages,id'], + 'image' => ['required', 'file', ...$this->getImageValidationRules()], + 'name' => ['string', 'max:180'], + ], + 'update' => [ + 'name' => ['string', 'max:180'], + ] + ]; + } + + /** + * Get a listing of gallery images and drawings in the system. + * Requires visibility of the content they're originally uploaded to. + */ + public function list() + { + $images = Image::query()->scopes(['visible']) + ->select($this->fieldsToExpose) + ->whereIn('type', ['gallery', 'drawio']); + + return $this->apiListingResponse($images, [ + ...$this->fieldsToExpose + ]); + } + + /** + * Create a new image in the system. + */ + public function create(Request $request) + { + $data = $this->validate($request, $this->rules()['create']); + + $image = $this->imageRepo->saveNew($data['image'], $data['type'], $data['uploaded_to']); + + return response()->json($this->formatForSingleResponse($image)); + } + + /** + * View the details of a single image. + */ + public function read(string $id) + { + $image = $this->imageRepo->getById($id); + $this->checkOwnablePermission('page-view', $image->getPage()); + + return response()->json($this->formatForSingleResponse($image)); + } + + /** + * Update an existing image in the system. + */ + public function update(Request $request, string $id) + { + $data = $this->validate($request, $this->rules()['update']); + $image = $this->imageRepo->getById($id); + $this->checkOwnablePermission('page-view', $image->getPage()); + $this->checkOwnablePermission('image-update', $image); + + $this->imageRepo->updateImageDetails($image, $data); + + return response()->json($this->formatForSingleResponse($image)); + } + + /** + * Delete an image from the system. + */ + public function delete(string $id) + { + $image = $this->imageRepo->getById($id); + $this->checkOwnablePermission('page-view', $image->getPage()); + $this->checkOwnablePermission('image-delete', $image); + $this->imageRepo->destroyImage($image); + + return response('', 204); + } + + /** + * Format the given image model for single-result display. + */ + protected function formatForSingleResponse(Image $image): array + { + $this->imageRepo->loadThumbs($image); + $data = $image->getAttributes(); + $data['created_by'] = $image->createdBy; + $data['updated_by'] = $image->updatedBy; + $data['content'] = []; + + $escapedUrl = htmlentities($image->url); + $escapedName = htmlentities($image->name); + if ($image->type === 'drawio') { + $data['content']['html'] = "<div drawio-diagram=\"{$image->id}\"><img src=\"{$escapedUrl}\"></div>"; + $data['content']['markdown'] = $data['content']['html']; + } else { + $escapedDisplayThumb = htmlentities($image->thumbs['display']); + $data['content']['html'] = "<a href=\"{$escapedUrl}\" target=\"_blank\"><img src=\"{$escapedDisplayThumb}\" alt=\"{$escapedName}\"></a>"; + $mdEscapedName = str_replace(']', '', str_replace('[', '', $image->name)); + $mdEscapedThumb = str_replace(']', '', str_replace('[', '', $image->thumbs['display'])); + $data['content']['markdown'] = ""; + } + + return $data; + } +} diff --git a/app/Http/Controllers/Api/RoleApiController.php b/app/Http/Controllers/Api/RoleApiController.php index 4f78455e0..6986c73f7 100644 --- a/app/Http/Controllers/Api/RoleApiController.php +++ b/app/Http/Controllers/Api/RoleApiController.php @@ -88,10 +88,10 @@ class RoleApiController extends ApiController */ public function read(string $id) { - $user = $this->permissionsRepo->getRoleById($id); - $this->singleFormatter($user); + $role = $this->permissionsRepo->getRoleById($id); + $this->singleFormatter($role); - return response()->json($user); + return response()->json($role); } /** diff --git a/app/Http/Controllers/Images/GalleryImageController.php b/app/Http/Controllers/Images/GalleryImageController.php index 5484411d3..3f2f56265 100644 --- a/app/Http/Controllers/Images/GalleryImageController.php +++ b/app/Http/Controllers/Images/GalleryImageController.php @@ -10,14 +10,9 @@ use Illuminate\Validation\ValidationException; class GalleryImageController extends Controller { - protected $imageRepo; - - /** - * GalleryImageController constructor. - */ - public function __construct(ImageRepo $imageRepo) - { - $this->imageRepo = $imageRepo; + public function __construct( + protected ImageRepo $imageRepo + ) { } /** diff --git a/app/Uploads/Image.php b/app/Uploads/Image.php index c21a3b03f..038e7c199 100644 --- a/app/Uploads/Image.php +++ b/app/Uploads/Image.php @@ -3,9 +3,11 @@ namespace BookStack\Uploads; use BookStack\Auth\Permissions\JointPermission; +use BookStack\Auth\Permissions\PermissionApplicator; use BookStack\Entities\Models\Page; use BookStack\Model; use BookStack\Traits\HasCreatorAndUpdater; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -33,6 +35,15 @@ class Image extends Model ->where('joint_permissions.entity_type', '=', 'page'); } + /** + * Scope the query to just the images visible to the user based upon the + * user visibility of the uploaded_to page. + */ + public function scopeVisible(Builder $query): Builder + { + return app()->make(PermissionApplicator::class)->restrictPageRelationQuery($query, 'images', 'uploaded_to'); + } + /** * Get a thumbnail for this image. * diff --git a/routes/api.php b/routes/api.php index 1b852fed7..c809cdb3a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -13,7 +13,8 @@ use BookStack\Http\Controllers\Api\BookExportApiController; use BookStack\Http\Controllers\Api\BookshelfApiController; use BookStack\Http\Controllers\Api\ChapterApiController; use BookStack\Http\Controllers\Api\ChapterExportApiController; -use BookStack\Http\Controllers\Api\ContentPermissionsController; +use BookStack\Http\Controllers\Api\ContentPermissionApiController; +use BookStack\Http\Controllers\Api\ImageGalleryApiController; use BookStack\Http\Controllers\Api\PageApiController; use BookStack\Http\Controllers\Api\PageExportApiController; use BookStack\Http\Controllers\Api\RecycleBinApiController; @@ -63,6 +64,12 @@ Route::get('pages/{id}/export/pdf', [PageExportApiController::class, 'exportPdf' Route::get('pages/{id}/export/plaintext', [PageExportApiController::class, 'exportPlainText']); Route::get('pages/{id}/export/markdown', [PageExportApiController::class, 'exportMarkdown']); +Route::get('image-gallery', [ImageGalleryApiController::class, 'list']); +Route::post('image-gallery', [ImageGalleryApiController::class, 'create']); +Route::get('image-gallery/{id}', [ImageGalleryApiController::class, 'read']); +Route::put('image-gallery/{id}', [ImageGalleryApiController::class, 'update']); +Route::delete('image-gallery/{id}', [ImageGalleryApiController::class, 'delete']); + Route::get('search', [SearchApiController::class, 'all']); Route::get('shelves', [BookshelfApiController::class, 'list']); @@ -87,5 +94,5 @@ Route::get('recycle-bin', [RecycleBinApiController::class, 'list']); Route::put('recycle-bin/{deletionId}', [RecycleBinApiController::class, 'restore']); Route::delete('recycle-bin/{deletionId}', [RecycleBinApiController::class, 'destroy']); -Route::get('content-permissions/{contentType}/{contentId}', [ContentPermissionsController::class, 'read']); -Route::put('content-permissions/{contentType}/{contentId}', [ContentPermissionsController::class, 'update']); +Route::get('content-permissions/{contentType}/{contentId}', [ContentPermissionApiController::class, 'read']); +Route::put('content-permissions/{contentType}/{contentId}', [ContentPermissionApiController::class, 'update']); From 3a808fd76859a90cda0d6a4085bed053d7cabde1 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Tue, 14 Mar 2023 19:29:08 +0000 Subject: [PATCH 26/27] Added phpunit tests to cover image API endpoints --- .../Api/ImageGalleryApiController.php | 13 +- app/Uploads/Image.php | 2 +- tests/Api/ImageGalleryApiTest.php | 347 ++++++++++++++++++ tests/Api/TestsApi.php | 21 +- 4 files changed, 375 insertions(+), 8 deletions(-) create mode 100644 tests/Api/ImageGalleryApiTest.php diff --git a/app/Http/Controllers/Api/ImageGalleryApiController.php b/app/Http/Controllers/Api/ImageGalleryApiController.php index 85c0c3cef..a9fb3b103 100644 --- a/app/Http/Controllers/Api/ImageGalleryApiController.php +++ b/app/Http/Controllers/Api/ImageGalleryApiController.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Controllers\Api; +use BookStack\Entities\Models\Page; use BookStack\Uploads\Image; use BookStack\Uploads\ImageRepo; use Illuminate\Http\Request; @@ -22,7 +23,7 @@ class ImageGalleryApiController extends ApiController return [ 'create' => [ 'type' => ['required', 'string', 'in:gallery,drawio'], - 'uploaded_to' => ['required', 'integer', 'exists:pages,id'], + 'uploaded_to' => ['required', 'integer'], 'image' => ['required', 'file', ...$this->getImageValidationRules()], 'name' => ['string', 'max:180'], ], @@ -52,10 +53,17 @@ class ImageGalleryApiController extends ApiController */ public function create(Request $request) { + $this->checkPermission('image-create-all'); $data = $this->validate($request, $this->rules()['create']); + Page::visible()->findOrFail($data['uploaded_to']); $image = $this->imageRepo->saveNew($data['image'], $data['type'], $data['uploaded_to']); + if (isset($data['name'])) { + $image->refresh(); + $image->update(['name' => $data['name']]); + } + return response()->json($this->formatForSingleResponse($image)); } @@ -64,8 +72,7 @@ class ImageGalleryApiController extends ApiController */ public function read(string $id) { - $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission('page-view', $image->getPage()); + $image = Image::query()->scopes(['visible'])->findOrFail($id); return response()->json($this->formatForSingleResponse($image)); } diff --git a/app/Uploads/Image.php b/app/Uploads/Image.php index 038e7c199..0ab0b612a 100644 --- a/app/Uploads/Image.php +++ b/app/Uploads/Image.php @@ -49,7 +49,7 @@ class Image extends Model * * @throws \Exception */ - public function getThumb(int $width, int $height, bool $keepRatio = false): string + public function getThumb(?int $width, ?int $height, bool $keepRatio = false): string { return app()->make(ImageService::class)->getThumbnail($this, $width, $height, $keepRatio); } diff --git a/tests/Api/ImageGalleryApiTest.php b/tests/Api/ImageGalleryApiTest.php new file mode 100644 index 000000000..17c90518c --- /dev/null +++ b/tests/Api/ImageGalleryApiTest.php @@ -0,0 +1,347 @@ +<?php + +namespace Tests\Api; + +use BookStack\Entities\Models\Page; +use BookStack\Uploads\Image; +use Tests\TestCase; + +class ImageGalleryApiTest extends TestCase +{ + use TestsApi; + + protected string $baseEndpoint = '/api/image-gallery'; + + public function test_index_endpoint_returns_expected_image_and_count() + { + $this->actingAsApiAdmin(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id'); + $resp->assertJson(['data' => [ + [ + 'id' => $image->id, + 'name' => $image->name, + 'url' => $image->url, + 'path' => $image->path, + 'type' => 'gallery', + 'uploaded_to' => $imagePage->id, + 'created_by' => $this->users->admin()->id, + 'updated_by' => $this->users->admin()->id, + ], + ]]); + + $resp->assertJson(['total' => Image::query()->count()]); + } + + public function test_index_endpoint_doesnt_show_images_for_those_uploaded_to_non_visible_pages() + { + $this->actingAsApiEditor(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $resp = $this->getJson($this->baseEndpoint . '?filter[id]=' . $image->id); + $resp->assertJsonCount(1, 'data'); + $resp->assertJson(['total' => 1]); + + $this->permissions->disableEntityInheritedPermissions($imagePage); + + $resp = $this->getJson($this->baseEndpoint . '?filter[id]=' . $image->id); + $resp->assertJsonCount(0, 'data'); + $resp->assertJson(['total' => 0]); + } + + public function test_index_endpoint_doesnt_show_other_image_types() + { + $this->actingAsApiEditor(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $typesByCountExpectation = [ + 'cover_book' => 0, + 'drawio' => 1, + 'gallery' => 1, + 'user' => 0, + 'system' => 0, + ]; + + foreach ($typesByCountExpectation as $type => $count) { + $image->type = $type; + $image->save(); + + $resp = $this->getJson($this->baseEndpoint . '?filter[id]=' . $image->id); + $resp->assertJsonCount($count, 'data'); + $resp->assertJson(['total' => $count]); + } + } + + public function test_create_endpoint() + { + $this->actingAsApiAdmin(); + + $imagePage = $this->entities->page(); + $resp = $this->call('POST', $this->baseEndpoint, [ + 'type' => 'gallery', + 'uploaded_to' => $imagePage->id, + 'name' => 'My awesome image!', + ], [], [ + 'image' => $this->files->uploadedImage('my-cool-image.png'), + ]); + + $resp->assertStatus(200); + + $image = Image::query()->where('uploaded_to', '=', $imagePage->id)->first(); + $expectedUser = [ + 'id' => $this->users->admin()->id, + 'name' => $this->users->admin()->name, + 'slug' => $this->users->admin()->slug, + ]; + $resp->assertJson([ + 'id' => $image->id, + 'name' => 'My awesome image!', + 'url' => $image->url, + 'path' => $image->path, + 'type' => 'gallery', + 'uploaded_to' => $imagePage->id, + 'created_by' => $expectedUser, + 'updated_by' => $expectedUser, + ]); + } + + public function test_create_endpoint_requires_image_create_permissions() + { + $user = $this->users->editor(); + $this->actingAsForApi($user); + $this->permissions->removeUserRolePermissions($user, ['image-create-all']); + + $makeRequest = function () { + return $this->call('POST', $this->baseEndpoint, []); + }; + + $resp = $makeRequest(); + $resp->assertStatus(403); + + $this->permissions->grantUserRolePermissions($user, ['image-create-all']); + + $resp = $makeRequest(); + $resp->assertStatus(422); + } + + public function test_create_fails_if_uploaded_to_not_visible_or_not_exists() + { + $this->actingAsApiEditor(); + + $makeRequest = function (int $uploadedTo) { + return $this->call('POST', $this->baseEndpoint, [ + 'type' => 'gallery', + 'uploaded_to' => $uploadedTo, + 'name' => 'My awesome image!', + ], [], [ + 'image' => $this->files->uploadedImage('my-cool-image.png'), + ]); + }; + + $page = $this->entities->page(); + $this->permissions->disableEntityInheritedPermissions($page); + $resp = $makeRequest($page->id); + $resp->assertStatus(404); + + $resp = $makeRequest(Page::query()->max('id') + 55); + $resp->assertStatus(404); + } + + public function test_create_has_restricted_types() + { + $this->actingAsApiEditor(); + + $typesByStatusExpectation = [ + 'cover_book' => 422, + 'drawio' => 200, + 'gallery' => 200, + 'user' => 422, + 'system' => 422, + ]; + + $makeRequest = function (string $type) { + return $this->call('POST', $this->baseEndpoint, [ + 'type' => $type, + 'uploaded_to' => $this->entities->page()->id, + 'name' => 'My awesome image!', + ], [], [ + 'image' => $this->files->uploadedImage('my-cool-image.png'), + ]); + }; + + foreach ($typesByStatusExpectation as $type => $status) { + $resp = $makeRequest($type); + $resp->assertStatus($status); + } + } + + public function test_create_will_use_file_name_if_no_name_provided_in_request() + { + $this->actingAsApiEditor(); + + $imagePage = $this->entities->page(); + $resp = $this->call('POST', $this->baseEndpoint, [ + 'type' => 'gallery', + 'uploaded_to' => $imagePage->id, + ], [], [ + 'image' => $this->files->uploadedImage('my-cool-image.png'), + ]); + $resp->assertStatus(200); + + $this->assertDatabaseHas('images', [ + 'type' => 'gallery', + 'uploaded_to' => $imagePage->id, + 'name' => 'my-cool-image.png', + ]); + } + + public function test_read_endpoint() + { + $this->actingAsApiAdmin(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $resp = $this->getJson($this->baseEndpoint . "/{$image->id}"); + $resp->assertStatus(200); + + $expectedUser = [ + 'id' => $this->users->admin()->id, + 'name' => $this->users->admin()->name, + 'slug' => $this->users->admin()->slug, + ]; + + $displayUrl = $image->getThumb(1680, null, true); + $resp->assertJson([ + 'id' => $image->id, + 'name' => $image->name, + 'url' => $image->url, + 'path' => $image->path, + 'type' => 'gallery', + 'uploaded_to' => $imagePage->id, + 'created_by' => $expectedUser, + 'updated_by' => $expectedUser, + 'content' => [ + 'html' => "<a href=\"{$image->url}\" target=\"_blank\"><img src=\"{$displayUrl}\" alt=\"{$image->name}\"></a>", + 'markdown' => "", + ], + ]); + $this->assertStringStartsWith('http://', $resp->json('thumbs.gallery')); + $this->assertStringStartsWith('http://', $resp->json('thumbs.display')); + } + + public function test_read_endpoint_provides_different_content_for_drawings() + { + $this->actingAsApiAdmin(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $image->type = 'drawio'; + $image->save(); + + $resp = $this->getJson($this->baseEndpoint . "/{$image->id}"); + $resp->assertStatus(200); + + $drawing = "<div drawio-diagram=\"{$image->id}\"><img src=\"{$image->url}\"></div>"; + $resp->assertJson([ + 'id' => $image->id, + 'content' => [ + 'html' => $drawing, + 'markdown' => $drawing, + ], + ]); + } + + public function test_read_endpoint_does_not_show_if_no_permissions_for_related_page() + { + $this->actingAsApiEditor(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $this->permissions->disableEntityInheritedPermissions($imagePage); + + $resp = $this->getJson($this->baseEndpoint . "/{$image->id}"); + $resp->assertStatus(404); + } + + public function test_update_endpoint() + { + $this->actingAsApiAdmin(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $resp = $this->putJson($this->baseEndpoint . "/{$image->id}", [ + 'name' => 'My updated image name!', + ]); + + $resp->assertStatus(200); + $resp->assertJson([ + 'id' => $image->id, + 'name' => 'My updated image name!', + ]); + $this->assertDatabaseHas('images', [ + 'id' => $image->id, + 'name' => 'My updated image name!', + ]); + } + + public function test_update_endpoint_requires_image_delete_permission() + { + $user = $this->users->editor(); + $this->actingAsForApi($user); + $imagePage = $this->entities->page(); + $this->permissions->removeUserRolePermissions($user, ['image-update-all', 'image-update-own']); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $resp = $this->putJson($this->baseEndpoint . "/{$image->id}", ['name' => 'My new name']); + $resp->assertStatus(403); + $resp->assertJson($this->permissionErrorResponse()); + + $this->permissions->grantUserRolePermissions($user, ['image-update-all']); + $resp = $this->putJson($this->baseEndpoint . "/{$image->id}", ['name' => 'My new name']); + $resp->assertStatus(200); + } + + public function test_delete_endpoint() + { + $this->actingAsApiAdmin(); + $imagePage = $this->entities->page(); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + $this->assertDatabaseHas('images', ['id' => $image->id]); + + $resp = $this->deleteJson($this->baseEndpoint . "/{$image->id}"); + + $resp->assertStatus(204); + $this->assertDatabaseMissing('images', ['id' => $image->id]); + } + + public function test_delete_endpoint_requires_image_delete_permission() + { + $user = $this->users->editor(); + $this->actingAsForApi($user); + $imagePage = $this->entities->page(); + $this->permissions->removeUserRolePermissions($user, ['image-delete-all', 'image-delete-own']); + $data = $this->files->uploadGalleryImageToPage($this, $imagePage); + $image = Image::findOrFail($data['response']->id); + + $resp = $this->deleteJson($this->baseEndpoint . "/{$image->id}"); + $resp->assertStatus(403); + $resp->assertJson($this->permissionErrorResponse()); + + $this->permissions->grantUserRolePermissions($user, ['image-delete-all']); + $resp = $this->deleteJson($this->baseEndpoint . "/{$image->id}"); + $resp->assertStatus(204); + } +} diff --git a/tests/Api/TestsApi.php b/tests/Api/TestsApi.php index 501f28754..c566fd8de 100644 --- a/tests/Api/TestsApi.php +++ b/tests/Api/TestsApi.php @@ -2,15 +2,28 @@ namespace Tests\Api; +use BookStack\Auth\User; + trait TestsApi { - protected $apiTokenId = 'apitoken'; - protected $apiTokenSecret = 'password'; + protected string $apiTokenId = 'apitoken'; + protected string $apiTokenSecret = 'password'; + + /** + * Set the given user as the current logged-in user via the API driver. + * This does not ensure API access. The user may still lack required role permissions. + */ + protected function actingAsForApi(User $user): static + { + parent::actingAs($user, 'api'); + + return $this; + } /** * Set the API editor role as the current user via the API driver. */ - protected function actingAsApiEditor() + protected function actingAsApiEditor(): static { $this->actingAs($this->users->editor(), 'api'); @@ -20,7 +33,7 @@ trait TestsApi /** * Set the API admin role as the current user via the API driver. */ - protected function actingAsApiAdmin() + protected function actingAsApiAdmin(): static { $this->actingAs($this->users->admin(), 'api'); From 402eb845abe3312f6e6fe7611acd41541d8be245 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> Date: Wed, 15 Mar 2023 11:37:03 +0000 Subject: [PATCH 27/27] Added examples, updated docs for image gallery api endpoints --- .../Api/ImageGalleryApiController.php | 18 ++++++-- dev/api/requests/image-gallery-update.json | 3 ++ dev/api/responses/image-gallery-create.json | 28 +++++++++++++ dev/api/responses/image-gallery-list.json | 41 +++++++++++++++++++ dev/api/responses/image-gallery-read.json | 28 +++++++++++++ dev/api/responses/image-gallery-update.json | 28 +++++++++++++ .../api-docs/parts/getting-started.blade.php | 4 +- 7 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 dev/api/requests/image-gallery-update.json create mode 100644 dev/api/responses/image-gallery-create.json create mode 100644 dev/api/responses/image-gallery-list.json create mode 100644 dev/api/responses/image-gallery-read.json create mode 100644 dev/api/responses/image-gallery-update.json diff --git a/app/Http/Controllers/Api/ImageGalleryApiController.php b/app/Http/Controllers/Api/ImageGalleryApiController.php index a9fb3b103..3dba3d464 100644 --- a/app/Http/Controllers/Api/ImageGalleryApiController.php +++ b/app/Http/Controllers/Api/ImageGalleryApiController.php @@ -34,8 +34,8 @@ class ImageGalleryApiController extends ApiController } /** - * Get a listing of gallery images and drawings in the system. - * Requires visibility of the content they're originally uploaded to. + * Get a listing of images in the system. Includes gallery (page content) images and drawings. + * Requires visibility of the page they're originally uploaded to. */ public function list() { @@ -50,6 +50,11 @@ class ImageGalleryApiController extends ApiController /** * Create a new image in the system. + * Since "image" is expected to be a file, this needs to be a 'multipart/form-data' type request. + * The provided "uploaded_to" should be an existing page ID in the system. + * If the "name" parameter is omitted, the filename of the provided image file will be used instead. + * The "type" parameter should be 'gallery' for page content images, and 'drawio' should only be used + * when the file is a PNG file with diagrams.net image data embedded within. */ public function create(Request $request) { @@ -69,6 +74,10 @@ class ImageGalleryApiController extends ApiController /** * View the details of a single image. + * The "thumbs" response property contains links to scaled variants that BookStack may use in its UI. + * The "content" response property provides HTML and Markdown content, in the format that BookStack + * would typically use by default to add the image in page content, as a convenience. + * Actual image file data is not provided but can be fetched via the "url" response property. */ public function read(string $id) { @@ -78,7 +87,8 @@ class ImageGalleryApiController extends ApiController } /** - * Update an existing image in the system. + * Update the details of an existing image in the system. + * Only allows updating of the image name at this time. */ public function update(Request $request, string $id) { @@ -94,6 +104,8 @@ class ImageGalleryApiController extends ApiController /** * Delete an image from the system. + * Will also delete thumbnails for the image. + * Does not check or handle image usage so this could leave pages with broken image references. */ public function delete(string $id) { diff --git a/dev/api/requests/image-gallery-update.json b/dev/api/requests/image-gallery-update.json new file mode 100644 index 000000000..e332e3a8f --- /dev/null +++ b/dev/api/requests/image-gallery-update.json @@ -0,0 +1,3 @@ +{ + "name": "My updated image name" +} \ No newline at end of file diff --git a/dev/api/responses/image-gallery-create.json b/dev/api/responses/image-gallery-create.json new file mode 100644 index 000000000..e27824491 --- /dev/null +++ b/dev/api/responses/image-gallery-create.json @@ -0,0 +1,28 @@ +{ + "name": "cute-cat-image.png", + "path": "\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png", + "url": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png", + "type": "gallery", + "uploaded_to": 1, + "created_by": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "updated_by": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "updated_at": "2023-03-15 08:17:37", + "created_at": "2023-03-15 08:17:37", + "id": 618, + "thumbs": { + "gallery": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/thumbs-150-150\/cute-cat-image.png", + "display": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/scaled-1680-\/cute-cat-image.png" + }, + "content": { + "html": "<a href=\"https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png\" target=\"_blank\"><img src=\"https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/scaled-1680-\/cute-cat-image.png\" alt=\"cute-cat-image.png\"><\/a>", + "markdown": "" + } +} \ No newline at end of file diff --git a/dev/api/responses/image-gallery-list.json b/dev/api/responses/image-gallery-list.json new file mode 100644 index 000000000..054d68a15 --- /dev/null +++ b/dev/api/responses/image-gallery-list.json @@ -0,0 +1,41 @@ +{ + "data": [ + { + "id": 1, + "name": "My cat scribbles", + "url": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-02\/scribbles.jpg", + "path": "\/uploads\/images\/gallery\/2023-02\/scribbles.jpg", + "type": "gallery", + "uploaded_to": 1, + "created_by": 1, + "updated_by": 1, + "created_at": "2023-02-12T16:34:57.000000Z", + "updated_at": "2023-02-12T16:34:57.000000Z" + }, + { + "id": 2, + "name": "Drawing-1.png", + "url": "https:\/\/bookstack.example.com\/uploads\/images\/drawio\/2023-02\/drawing-1.png", + "path": "\/uploads\/images\/drawio\/2023-02\/drawing-1.png", + "type": "drawio", + "uploaded_to": 2, + "created_by": 2, + "updated_by": 2, + "created_at": "2023-02-12T16:39:19.000000Z", + "updated_at": "2023-02-12T16:39:19.000000Z" + }, + { + "id": 8, + "name": "beans.jpg", + "url": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-02\/beans.jpg", + "path": "\/uploads\/images\/gallery\/2023-02\/beans.jpg", + "type": "gallery", + "uploaded_to": 6, + "created_by": 1, + "updated_by": 1, + "created_at": "2023-02-15T19:37:44.000000Z", + "updated_at": "2023-02-15T19:37:44.000000Z" + } + ], + "total": 3 +} \ No newline at end of file diff --git a/dev/api/responses/image-gallery-read.json b/dev/api/responses/image-gallery-read.json new file mode 100644 index 000000000..c6c468daa --- /dev/null +++ b/dev/api/responses/image-gallery-read.json @@ -0,0 +1,28 @@ +{ + "id": 618, + "name": "cute-cat-image.png", + "url": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png", + "created_at": "2023-03-15 08:17:37", + "updated_at": "2023-03-15 08:17:37", + "created_by": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "updated_by": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "path": "\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png", + "type": "gallery", + "uploaded_to": 1, + "thumbs": { + "gallery": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/thumbs-150-150\/cute-cat-image.png", + "display": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/scaled-1680-\/cute-cat-image.png" + }, + "content": { + "html": "<a href=\"https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png\" target=\"_blank\"><img src=\"https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/scaled-1680-\/cute-cat-image.png\" alt=\"cute-cat-image.png\"><\/a>", + "markdown": "" + } +} \ No newline at end of file diff --git a/dev/api/responses/image-gallery-update.json b/dev/api/responses/image-gallery-update.json new file mode 100644 index 000000000..6e6168a1b --- /dev/null +++ b/dev/api/responses/image-gallery-update.json @@ -0,0 +1,28 @@ +{ + "id": 618, + "name": "My updated image name", + "url": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png", + "created_at": "2023-03-15 08:17:37", + "updated_at": "2023-03-15 08:24:50", + "created_by": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "updated_by": { + "id": 1, + "name": "Admin", + "slug": "admin" + }, + "path": "\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png", + "type": "gallery", + "uploaded_to": 1, + "thumbs": { + "gallery": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/thumbs-150-150\/cute-cat-image.png", + "display": "https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/scaled-1680-\/cute-cat-image.png" + }, + "content": { + "html": "<a href=\"https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/cute-cat-image.png\" target=\"_blank\"><img src=\"https:\/\/bookstack.example.com\/uploads\/images\/gallery\/2023-03\/scaled-1680-\/cute-cat-image.png\" alt=\"My updated image name\"><\/a>", + "markdown": "" + } +} \ No newline at end of file diff --git a/resources/views/api-docs/parts/getting-started.blade.php b/resources/views/api-docs/parts/getting-started.blade.php index 7358b5cd7..75b71c6be 100644 --- a/resources/views/api-docs/parts/getting-started.blade.php +++ b/resources/views/api-docs/parts/getting-started.blade.php @@ -14,11 +14,11 @@ HTTP POST calls upon events occurring in BookStack. </li> <li> - <a href="https://github.com/BookStackApp/BookStack/blob/master/dev/docs/visual-theme-system.md" target="_blank" rel="noopener noreferrer">Visual Theme System</a> - + <a href="https://github.com/BookStackApp/BookStack/blob/development/dev/docs/visual-theme-system.md" target="_blank" rel="noopener noreferrer">Visual Theme System</a> - Methods to override views, translations and icons within BookStack. </li> <li> - <a href="https://github.com/BookStackApp/BookStack/blob/master/dev/docs/logical-theme-system.md" target="_blank" rel="noopener noreferrer">Logical Theme System</a> - + <a href="https://github.com/BookStackApp/BookStack/blob/development/dev/docs/logical-theme-system.md" target="_blank" rel="noopener noreferrer">Logical Theme System</a> - Methods to extend back-end functionality within BookStack. </li> </ul>