mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-07 19:06:05 +03:00
Compare commits
447 Commits
further_th
...
v25.12
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c3a4c7e85 | ||
|
|
114fa802c0 | ||
|
|
8fcd3b24b3 | ||
|
|
ce703403c2 | ||
|
|
16110273ff | ||
|
|
93bcbd168e | ||
|
|
46001d61d0 | ||
|
|
8dd238ceae | ||
|
|
bb7fd59de9 | ||
|
|
ad8fc95521 | ||
|
|
cca066a258 | ||
|
|
bbda5fd468 | ||
|
|
8429cc93eb | ||
|
|
fef61f054a | ||
|
|
8082c95ec3 | ||
|
|
fcabf478de | ||
|
|
8de2c28497 | ||
|
|
0838d5ea16 | ||
|
|
449ac40114 | ||
|
|
3131050acd | ||
|
|
c0d2874892 | ||
|
|
5940a91809 | ||
|
|
9a4651badb | ||
|
|
92d15d9cf2 | ||
|
|
b06147fef7 | ||
|
|
841350a937 | ||
|
|
12183bac07 | ||
|
|
e65b4b63a2 | ||
|
|
7cac3f4780 | ||
|
|
92cd11d105 | ||
|
|
13115ace84 | ||
|
|
73f9834e6f | ||
|
|
3afe855156 | ||
|
|
bfde896f0b | ||
|
|
1cdc0a7a3d | ||
|
|
d19b86640b | ||
|
|
2936ba609b | ||
|
|
573a2dd22a | ||
|
|
b55cc803d3 | ||
|
|
304ade418e | ||
|
|
997931c42f | ||
|
|
268e353431 | ||
|
|
b491b5fbca | ||
|
|
387c786768 | ||
|
|
2641586a6f | ||
|
|
6d2cd20e80 | ||
|
|
b0c574356a | ||
|
|
07e45a20e5 | ||
|
|
14056c69e6 | ||
|
|
fb9c840c46 | ||
|
|
5fba4a5399 | ||
|
|
c0b377050e | ||
|
|
f3efb6441d | ||
|
|
0cf313a21e | ||
|
|
26aadffb20 | ||
|
|
a5f48e3202 | ||
|
|
b0dda6e6a7 | ||
|
|
d4025d95e7 | ||
|
|
d6021f4d22 | ||
|
|
b9a3290731 | ||
|
|
48f235ea5a | ||
|
|
047771b9f4 | ||
|
|
b5375114d3 | ||
|
|
fc13e56cea | ||
|
|
77fc37ac25 | ||
|
|
3424351e84 | ||
|
|
606f9d92d0 | ||
|
|
a5e25abb9c | ||
|
|
b310e87e4c | ||
|
|
425baf9d6e | ||
|
|
825c369ad9 | ||
|
|
10bab70438 | ||
|
|
350e0b281b | ||
|
|
08805ea3c8 | ||
|
|
9441e32c69 | ||
|
|
530fc37067 | ||
|
|
369e499dce | ||
|
|
655815de6d | ||
|
|
457adc1fee | ||
|
|
e86a90967e | ||
|
|
5d08f7cf14 | ||
|
|
8744eb2d62 | ||
|
|
d8383cfa80 | ||
|
|
4626278447 | ||
|
|
c61af9c22b | ||
|
|
72521d0906 | ||
|
|
7e44b195c5 | ||
|
|
5b45eac5e1 | ||
|
|
c1d30341e7 | ||
|
|
80d2b4913b | ||
|
|
3f473528b1 | ||
|
|
d0dcd4f61b | ||
|
|
bde66a1396 | ||
|
|
4de5a2d9bf | ||
|
|
27bf4299cf | ||
|
|
164f01bb25 | ||
|
|
f563a005f5 | ||
|
|
a14d8e30cc | ||
|
|
a9194ffb63 | ||
|
|
2f9c1b7127 | ||
|
|
bbea76668b | ||
|
|
becc630acf | ||
|
|
4ac8ecad6b | ||
|
|
903e88c700 | ||
|
|
ed96aa820e | ||
|
|
63ec079b7b | ||
|
|
d485fcb3db | ||
|
|
0f895668a4 | ||
|
|
6c577ac3bf | ||
|
|
31cc2423d2 | ||
|
|
c9ed32e518 | ||
|
|
6b4c3a0969 | ||
|
|
2dad92d1bd | ||
|
|
c1fb7ab7dc | ||
|
|
98315f3899 | ||
|
|
8c82aaabd6 | ||
|
|
ce9b536b78 | ||
|
|
d9c50e5bc1 | ||
|
|
bf075f7dd8 | ||
|
|
a4fd673285 | ||
|
|
e794c977bc | ||
|
|
0b088ef1d3 | ||
|
|
bf6a6af683 | ||
|
|
914790fd99 | ||
|
|
edb0c6a9e8 | ||
|
|
84049de696 | ||
|
|
da0531e63b | ||
|
|
421dc75f4e | ||
|
|
8ae91df038 | ||
|
|
64b41dd626 | ||
|
|
ebd6e4d3a2 | ||
|
|
80374aea5c | ||
|
|
2ac9efae7d | ||
|
|
a11d565ba4 | ||
|
|
1fdf854ea7 | ||
|
|
e9c9792cb9 | ||
|
|
5ae524c25a | ||
|
|
0d7287fc8b | ||
|
|
e77c96f6b7 | ||
|
|
9b8a10dd3a | ||
|
|
49200ca5ce | ||
|
|
34aa4dbf10 | ||
|
|
5ee79d16c9 | ||
|
|
a1ea4006e0 | ||
|
|
9078188939 | ||
|
|
ed0aad1a7a | ||
|
|
5c59cfb020 | ||
|
|
3ca15ad68a | ||
|
|
60014989f5 | ||
|
|
57b10f195e | ||
|
|
b1e95eb39f | ||
|
|
b3da77b8f9 | ||
|
|
1a345b74bb | ||
|
|
8ffc3a4abf | ||
|
|
7233c1c7b2 | ||
|
|
1309a01131 | ||
|
|
0333185b6d | ||
|
|
83f89f64e8 | ||
|
|
11a1a6fb16 | ||
|
|
882c609296 | ||
|
|
176a0dcd59 | ||
|
|
94b0f70bfa | ||
|
|
08b2a77d41 | ||
|
|
3e8e9a23cf | ||
|
|
58b83b64c8 | ||
|
|
dfe4cde6ee | ||
|
|
d11144d9e2 | ||
|
|
f96b0ea5f3 | ||
|
|
815f8d79ed | ||
|
|
b62dab32e0 | ||
|
|
262f863981 | ||
|
|
a4c94390a1 | ||
|
|
53f3cca85d | ||
|
|
ed08bbcecc | ||
|
|
de97ebf9b7 | ||
|
|
f492a660a8 | ||
|
|
09436836a5 | ||
|
|
bb455d7788 | ||
|
|
009212ab80 | ||
|
|
ba9cb591c8 | ||
|
|
d00ac2f34e | ||
|
|
bd4dc6d463 | ||
|
|
d91180a909 | ||
|
|
bc2913a5cb | ||
|
|
4802394562 | ||
|
|
1755556468 | ||
|
|
01cdbdb7ae | ||
|
|
fc8bbf3eab | ||
|
|
3cdab19319 | ||
|
|
5661d20e87 | ||
|
|
91f80123e8 | ||
|
|
7a0636d0f8 | ||
|
|
0fe5bdfbac | ||
|
|
f88687e977 | ||
|
|
68d437d05b | ||
|
|
1e56aaea04 | ||
|
|
dab170a6fe | ||
|
|
a8de717d9b | ||
|
|
78fe95b6fc | ||
|
|
e0c24e41aa | ||
|
|
fa8553839b | ||
|
|
b8fcefc794 | ||
|
|
88bcb68fcb | ||
|
|
7c000553ae | ||
|
|
391fa35c80 | ||
|
|
c6773a8c9f | ||
|
|
9b226e7d39 | ||
|
|
9865446267 | ||
|
|
926abbe776 | ||
|
|
4fabef3a57 | ||
|
|
5ef4cd80c3 | ||
|
|
e01f23583f | ||
|
|
7792cb3915 | ||
|
|
be26253a18 | ||
|
|
1bdd1f8189 | ||
|
|
fa62c79b17 | ||
|
|
d7d8fa1e5b | ||
|
|
18562f1e10 | ||
|
|
86090a694f | ||
|
|
1ee8287c73 | ||
|
|
8eb98cd591 | ||
|
|
0f9ba21b05 | ||
|
|
834f8e7046 | ||
|
|
32e3399334 | ||
|
|
2d8698a218 | ||
|
|
454fb883a2 | ||
|
|
6f4a6ab8ea | ||
|
|
9c4b6f36f1 | ||
|
|
78886b1e67 | ||
|
|
d9debaf032 | ||
|
|
d4360d6347 | ||
|
|
175b1785c0 | ||
|
|
c8740c0171 | ||
|
|
91ee895a74 | ||
|
|
a045e46571 | ||
|
|
44eaa65c3b | ||
|
|
0a22af7b14 | ||
|
|
b54702ab08 | ||
|
|
c4fdcfc5d1 | ||
|
|
cb8117e8df | ||
|
|
5a218d5056 | ||
|
|
8dbc5cf9c6 | ||
|
|
71e81615a3 | ||
|
|
611d37da04 | ||
|
|
0e799a3857 | ||
|
|
b91d6e2bfa | ||
|
|
ea16ad7e94 | ||
|
|
ba6eb54552 | ||
|
|
f705e7683b | ||
|
|
dc996adb20 | ||
|
|
a64c638ccc | ||
|
|
359c067279 | ||
|
|
66a746e297 | ||
|
|
a4d43ee24b | ||
|
|
f7793a70a9 | ||
|
|
ceba3d31fb | ||
|
|
eecc08edde | ||
|
|
eb19aadc75 | ||
|
|
06c81e69b9 | ||
|
|
3dc3d4a639 | ||
|
|
94c59c1e3d | ||
|
|
4d2205853a | ||
|
|
751772b87a | ||
|
|
76e30869e1 | ||
|
|
3edc9fe9eb | ||
|
|
616c62703e | ||
|
|
ecd56917e7 | ||
|
|
e22c9cae91 | ||
|
|
29ddb6e1b9 | ||
|
|
2ff90e2ff0 | ||
|
|
04ecc128a2 | ||
|
|
87d1d3423b | ||
|
|
4818192a2a | ||
|
|
965dd97f54 | ||
|
|
195b74926c | ||
|
|
2120db12b2 | ||
|
|
ed563fef28 | ||
|
|
0d31a8e3f1 | ||
|
|
b8354b974b | ||
|
|
034c1e289d | ||
|
|
f31605a3de | ||
|
|
e7cc75c74d | ||
|
|
4b79d5e4e8 | ||
|
|
34854915b3 | ||
|
|
af6f34b529 | ||
|
|
fb82a2b896 | ||
|
|
5b464938b6 | ||
|
|
81f954890d | ||
|
|
0e2bbcec62 | ||
|
|
fdd339f525 | ||
|
|
8cf7d6a83d | ||
|
|
58a5008718 | ||
|
|
c44a8df55d | ||
|
|
ff1494c519 | ||
|
|
b8ce8fd852 | ||
|
|
75e7454a5f | ||
|
|
2558ea8931 | ||
|
|
ac0f47a4b2 | ||
|
|
4f16129869 | ||
|
|
64a8037fdd | ||
|
|
7502ba1bc8 | ||
|
|
33a04697ef | ||
|
|
b70a5c0cdb | ||
|
|
9443ae9f40 | ||
|
|
220c2a4102 | ||
|
|
e9914eb301 | ||
|
|
934512d09c | ||
|
|
9102c90986 | ||
|
|
c3e74219c4 | ||
|
|
13c9d7bc2d | ||
|
|
119b539586 | ||
|
|
29a5c180f0 | ||
|
|
7906602291 | ||
|
|
6dafe773ff | ||
|
|
25bc28a1be | ||
|
|
4c561c7fa0 | ||
|
|
95b3e78573 | ||
|
|
63a345bc93 | ||
|
|
e093a172cb | ||
|
|
4b01f8934b | ||
|
|
bc116b45b5 | ||
|
|
a059960b9e | ||
|
|
7770966fed | ||
|
|
d7adcf6c69 | ||
|
|
04a364dcc3 | ||
|
|
db83ac7eaa | ||
|
|
3ca9dddf61 | ||
|
|
bf74f53ca7 | ||
|
|
9d67efb4a4 | ||
|
|
3a39b9f440 | ||
|
|
27f7aab375 | ||
|
|
337da0c467 | ||
|
|
f56b3560c4 | ||
|
|
02dfe11ce6 | ||
|
|
83d06beb70 | ||
|
|
a8cfc059c8 | ||
|
|
1614b2bab0 | ||
|
|
4bdec0d214 | ||
|
|
6a7d7e7c2b | ||
|
|
30d4674657 | ||
|
|
9f961f95f8 | ||
|
|
bab99a26ec | ||
|
|
9a7fecd269 | ||
|
|
a8dc0d449b | ||
|
|
a0381f76bf | ||
|
|
6102f66daa | ||
|
|
c6134d162d | ||
|
|
2046f9b9de | ||
|
|
ac3ba594a4 | ||
|
|
22df25a480 | ||
|
|
8b30c7f02e | ||
|
|
757cdddc7c | ||
|
|
df95e99680 | ||
|
|
5a6d544db7 | ||
|
|
16117d329c | ||
|
|
e90da18ada | ||
|
|
a08d80e1cc | ||
|
|
6258175922 | ||
|
|
15736777a0 | ||
|
|
75915e8a94 | ||
|
|
9bde0ae4ea | ||
|
|
0c802d1f86 | ||
|
|
b7a96c6466 | ||
|
|
4b645a82c7 | ||
|
|
d599b77b6f | ||
|
|
26e93dc8c1 | ||
|
|
a4c9a8491b | ||
|
|
70ee636d87 | ||
|
|
b35f6dbb03 | ||
|
|
67d9e24d8f | ||
|
|
3903fda6ca | ||
|
|
441e46ebaa | ||
|
|
1f4260f359 | ||
|
|
dc0bf8ad4e | ||
|
|
102e326e6a | ||
|
|
2b25bf6f3b | ||
|
|
f93280696d | ||
|
|
1787391b07 | ||
|
|
a74a8ee483 | ||
|
|
7fa5405cb7 | ||
|
|
6725ddcc41 | ||
|
|
bce941db3f | ||
|
|
6d926048ec | ||
|
|
5335c973b4 | ||
|
|
15c3e5c96e | ||
|
|
a5d5904969 | ||
|
|
598758b991 | ||
|
|
9926e23bc8 | ||
|
|
5d3264bc63 | ||
|
|
d71f819f95 | ||
|
|
ee13509760 | ||
|
|
82d7bb1f32 | ||
|
|
cdfda508d8 | ||
|
|
da941e584f | ||
|
|
65874d7b96 | ||
|
|
ac9b8f405c | ||
|
|
8d1419a12e | ||
|
|
04f7a7d301 | ||
|
|
c10d2a1493 | ||
|
|
97bbf79ffd | ||
|
|
f7b01ae53d | ||
|
|
d704e1dbba | ||
|
|
ef2ff5e093 | ||
|
|
7caed3b0db | ||
|
|
45641d0754 | ||
|
|
4b1d08ba99 | ||
|
|
160fa99ba4 | ||
|
|
d2a5ab49ed | ||
|
|
c6404d8917 | ||
|
|
7113807f12 | ||
|
|
be711215e8 | ||
|
|
7e3b404240 | ||
|
|
e86901ca20 | ||
|
|
bdfa61c8b2 | ||
|
|
2cc36787f5 | ||
|
|
448ac61b48 | ||
|
|
753f6394f7 | ||
|
|
b1faf65934 | ||
|
|
09f478bd74 | ||
|
|
a0497feddd | ||
|
|
789693bde9 | ||
|
|
1fe933e4ea | ||
|
|
724b4b5a70 | ||
|
|
1778a56146 | ||
|
|
744865fcb2 | ||
|
|
7f8c8b448d | ||
|
|
a67c53826d | ||
|
|
14b131e850 | ||
|
|
9b55a52b85 | ||
|
|
db1d10e80f | ||
|
|
1be576966f | ||
|
|
b97e792c5f | ||
|
|
8dec674cc3 | ||
|
|
f784c03746 | ||
|
|
148e172fe8 | ||
|
|
56ae86646f | ||
|
|
1d2b6fdfa2 | ||
|
|
4fc75beed4 | ||
|
|
3b3bc0c4bf | ||
|
|
910faab88e | ||
|
|
f184d763ad | ||
|
|
a91d42634d | ||
|
|
f517ef3616 | ||
|
|
e99507ddcf | ||
|
|
d2cacf1945 | ||
|
|
448ac1405b | ||
|
|
6ad21ce885 |
9
.github/translators.txt
vendored
9
.github/translators.txt
vendored
@@ -519,12 +519,3 @@ Tahsin Ahmed (tahsinahmed2012) :: Bengali
|
||||
bojan_che :: Serbian (Cyrillic)
|
||||
setiawan setiawan (culture.setiawan) :: Indonesian
|
||||
Donald Mac Kenzie (kiuman) :: Norwegian Bokmal
|
||||
Gabriel Silver (GabrielBSilver) :: Hebrew
|
||||
Tomas Darius Davainis (Tomasdd) :: Lithuanian
|
||||
CriedHero :: Chinese Simplified
|
||||
Henrik (henrik2105) :: Norwegian Bokmal
|
||||
FoW (fofwisdom) :: Korean
|
||||
serinf-lauza :: French
|
||||
Diyan Nikolaev (nikolaev.diyan) :: Bulgarian
|
||||
Shadluk Avan (quldosh) :: Uzbek
|
||||
Marci (MartonPoto) :: Hungarian
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -8,10 +8,10 @@ Homestead.yaml
|
||||
.idea
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/public/dist
|
||||
/public/dist/*.map
|
||||
/public/plugins
|
||||
/public/css
|
||||
/public/js
|
||||
/public/css/*.map
|
||||
/public/js/*.map
|
||||
/public/bower
|
||||
/public/build/
|
||||
/public/favicon.ico
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2026, Dan Brown and the BookStack project contributors.
|
||||
Copyright (c) 2015-2025, Dan Brown and the BookStack project contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace BookStack\App\Providers;
|
||||
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Theming\ThemeService;
|
||||
use BookStack\Theming\ThemeViews;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ThemeServiceProvider extends ServiceProvider
|
||||
@@ -26,23 +24,7 @@ class ThemeServiceProvider extends ServiceProvider
|
||||
{
|
||||
// Boot up the theme system
|
||||
$themeService = $this->app->make(ThemeService::class);
|
||||
$viewFactory = $this->app->make('view');
|
||||
if (!$themeService->getTheme()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$themeService->loadModules();
|
||||
$themeService->readThemeActions();
|
||||
$themeService->dispatch(ThemeEvents::APP_BOOT, $this->app);
|
||||
|
||||
$themeViews = new ThemeViews();
|
||||
$themeService->dispatch(ThemeEvents::THEME_REGISTER_VIEWS, $themeViews);
|
||||
$themeViews->registerViewPathsForTheme($viewFactory->getFinder(), $themeService->getModules());
|
||||
if ($themeViews->hasRegisteredViews()) {
|
||||
$viewFactory->share('__themeViews', $themeViews);
|
||||
Blade::directive('include', function ($expression) {
|
||||
return "<?php echo \$__themeViews->handleViewInclude({$expression}, array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1])); ?>";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ function setting(?string $key = null, mixed $default = null): mixed
|
||||
|
||||
/**
|
||||
* Get a path to a theme resource.
|
||||
* Returns null if a theme is not configured, and therefore a full path is not available for use.
|
||||
* Returns null if a theme is not configured and
|
||||
* therefore a full path is not available for use.
|
||||
*/
|
||||
function theme_path(string $path = ''): ?string
|
||||
{
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
* Do not edit this file unless you're happy to maintain any changes yourself.
|
||||
*/
|
||||
|
||||
// Join up possible view locations
|
||||
$viewPaths = [realpath(base_path('resources/views'))];
|
||||
if ($theme = env('APP_THEME', false)) {
|
||||
array_unshift($viewPaths, base_path('themes/' . $theme));
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
// App theme
|
||||
@@ -20,7 +26,7 @@ return [
|
||||
// Most templating systems load templates from disk. Here you may specify
|
||||
// an array of paths that should be checked for your views. Of course
|
||||
// the usual Laravel view path has already been registered for you.
|
||||
'paths' => [realpath(base_path('resources/views'))],
|
||||
'paths' => $viewPaths,
|
||||
|
||||
// Compiled View Path
|
||||
// This option determines where all the compiled Blade templates will be
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Http\HttpRequestService;
|
||||
use BookStack\Theming\ThemeModule;
|
||||
use BookStack\Theming\ThemeModuleException;
|
||||
use BookStack\Theming\ThemeModuleManager;
|
||||
use BookStack\Theming\ThemeModuleZip;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class InstallModuleCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:install-module
|
||||
{location : The URL or path of the module file}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Install a module to the currently configured theme';
|
||||
|
||||
protected array $cleanupActions = [];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$location = $this->argument('location');
|
||||
|
||||
// Get the ZIP file containing the module files
|
||||
$zipPath = $this->getPathToZip($location);
|
||||
if (!$zipPath) {
|
||||
$this->cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Validate module zip file (metadata, size, etc...) and get module instance
|
||||
$zip = new ThemeModuleZip($zipPath);
|
||||
$themeModule = $this->validateAndGetModuleInfoFromZip($zip);
|
||||
if (!$themeModule) {
|
||||
$this->cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the theme folder in use, attempting to create one if no active theme in use
|
||||
$themeFolder = $this->getThemeFolder();
|
||||
if (!$themeFolder) {
|
||||
$this->cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the modules folder of the theme, attempting to create it if not existing,
|
||||
// and create a new module manager instance.
|
||||
$moduleFolder = $this->getModuleFolder($themeFolder);
|
||||
if (!$moduleFolder) {
|
||||
$this->cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
$manager = new ThemeModuleManager($moduleFolder);
|
||||
|
||||
// Handle existing modules with the same name
|
||||
$exitingModulesWithName = $manager->getByName($themeModule->name);
|
||||
$shouldContinue = $this->handleExistingModulesWithSameName($exitingModulesWithName, $manager);
|
||||
if (!$shouldContinue) {
|
||||
$this->cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Extract module ZIP into the theme modules folder
|
||||
try {
|
||||
$newModule = $manager->addFromZip($themeModule->name, $zip);
|
||||
} catch (ThemeModuleException $exception) {
|
||||
$this->error("ERROR: Failed to install module with error: {$exception->getMessage()}");
|
||||
$this->cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->info("Module \"{$newModule->name}\" ({$newModule->getVersion()}) successfully installed!");
|
||||
$this->info("Install location: {$moduleFolder}/{$newModule->folderName}");
|
||||
$this->cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ThemeModule[] $existingModules
|
||||
*/
|
||||
protected function handleExistingModulesWithSameName(array $existingModules, ThemeModuleManager $manager): bool
|
||||
{
|
||||
if (count($existingModules) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->warn("The following modules already exist with the same name:");
|
||||
foreach ($existingModules as $folder => $module) {
|
||||
$this->line("{$module->name} ({$folder}:{$module->getVersion()}) - {$module->description}");
|
||||
}
|
||||
$this->line('');
|
||||
|
||||
$choices = ['Cancel module install', 'Add alongside existing module'];
|
||||
if (count($existingModules) === 1) {
|
||||
$choices[] = 'Replace existing module';
|
||||
}
|
||||
$choice = $this->choice("What would you like to do?", $choices, 0, null, false);
|
||||
if ($choice === 'Cancel module install') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($choice === 'Replace existing module') {
|
||||
$existingModuleFolder = array_key_first($existingModules);
|
||||
$this->info("Replacing existing module in {$existingModuleFolder} folder");
|
||||
$manager->deleteModuleFolder($existingModuleFolder);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getModuleFolder(string $themeFolder): string|null
|
||||
{
|
||||
$path = $themeFolder . DIRECTORY_SEPARATOR . 'modules';
|
||||
|
||||
if (file_exists($path) && !is_dir($path)) {
|
||||
$this->error("ERROR: Cannot create a modules folder, file already exists at {$path}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!file_exists($path)) {
|
||||
$created = mkdir($path, 0755, true);
|
||||
if (!$created) {
|
||||
$this->error("ERROR: Failed to create a modules folder at {$path}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
protected function getThemeFolder(): string|null
|
||||
{
|
||||
$path = theme_path('');
|
||||
if (!$path || !is_dir($path)) {
|
||||
$shouldCreate = $this->confirm('No active theme folder found, would you like to create one?');
|
||||
if (!$shouldCreate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$folder = 'custom';
|
||||
while (file_exists(base_path("themes" . DIRECTORY_SEPARATOR . $folder))) {
|
||||
$folder = 'custom-' . Str::random(4);
|
||||
}
|
||||
|
||||
$path = base_path("themes/{$folder}");
|
||||
$created = mkdir($path, 0755, true);
|
||||
if (!$created) {
|
||||
$this->error('Failed to create a theme folder to use. This may be a permissions issue. Try manually configuring an active theme');
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->info("Created theme folder at {$path}");
|
||||
$this->warn("You will need to set APP_THEME={$folder} in your BookStack env configuration to enable this theme!");
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
protected function validateAndGetModuleInfoFromZip(ThemeModuleZip $zip): ThemeModule|null
|
||||
{
|
||||
if (!$zip->exists()) {
|
||||
$this->error("ERROR: Cannot open ZIP file at {$zip->getPath()}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($zip->getContentsSize() > (50 * 1024 * 1024)) {
|
||||
$this->error("ERROR: Module ZIP file contents are too large. Maximum size is 50MB");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$themeModule = $zip->getModuleInstance();
|
||||
} catch (ThemeModuleException $exception) {
|
||||
$this->error("ERROR: Failed to read module metadata with error: {$exception->getMessage()}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $themeModule;
|
||||
}
|
||||
|
||||
protected function downloadModuleFile(string $location): string|null
|
||||
{
|
||||
$httpRequests = app()->make(HttpRequestService::class);
|
||||
$client = $httpRequests->buildClient(30, ['stream' => true]);
|
||||
$originalUrl = parse_url($location);
|
||||
$currentLocation = $location;
|
||||
$maxRedirects = 3;
|
||||
$redirectCount = 0;
|
||||
|
||||
// Follow redirects up to 3 times for the same hostname
|
||||
do {
|
||||
$resp = $client->sendRequest(new Request('GET', $currentLocation));
|
||||
$statusCode = $resp->getStatusCode();
|
||||
|
||||
if ($statusCode >= 300 && $statusCode < 400 && $redirectCount < $maxRedirects) {
|
||||
$redirectLocation = $resp->getHeaderLine('Location');
|
||||
if ($redirectLocation) {
|
||||
$redirectUrl = parse_url($redirectLocation);
|
||||
if (
|
||||
($originalUrl['host'] ?? '') === ($redirectUrl['host'] ?? '')
|
||||
&& ($originalUrl['scheme'] ?? '') === ($redirectUrl['scheme'] ?? '')
|
||||
&& ($originalUrl['port'] ?? '') === ($redirectUrl['port'] ?? '')
|
||||
) {
|
||||
$currentLocation = $redirectLocation;
|
||||
$redirectCount++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
if ($resp->getStatusCode() >= 300) {
|
||||
$this->error("ERROR: Failed to download module from {$location}");
|
||||
$this->error("Download failed with status code {$resp->getStatusCode()}");
|
||||
return null;
|
||||
}
|
||||
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'bookstack_module_');
|
||||
$fileHandle = fopen($tempFile, 'w');
|
||||
$respBody = $resp->getBody();
|
||||
$size = 0;
|
||||
$maxSize = 50 * 1024 * 1024;
|
||||
|
||||
while (!$respBody->eof()) {
|
||||
fwrite($fileHandle, $respBody->read(1024));
|
||||
$size += 1024;
|
||||
if ($size > $maxSize) {
|
||||
fclose($fileHandle);
|
||||
unlink($tempFile);
|
||||
$this->error("ERROR: Module ZIP file is too large. Maximum size is 50MB");
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
fclose($fileHandle);
|
||||
|
||||
$this->cleanupActions[] = function () use ($tempFile) {
|
||||
unlink($tempFile);
|
||||
};
|
||||
|
||||
return $tempFile;
|
||||
}
|
||||
|
||||
protected function getPathToZip(string $location): string|null
|
||||
{
|
||||
$lowerLocation = strtolower($location);
|
||||
$isRemote = str_starts_with($lowerLocation, 'http://') || str_starts_with($lowerLocation, 'https://');
|
||||
|
||||
if ($isRemote) {
|
||||
// Warning about fetching from source
|
||||
$host = parse_url($location, PHP_URL_HOST);
|
||||
$this->warn("This will download a module from {$host}. Modules can contain code which would have the ability to do anything on the BookStack host server.\nYou should only install modules from trusted sources.");
|
||||
$trustHost = $this->confirm('Are you sure you trust this source?');
|
||||
if (!$trustHost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the connection is http. If so, warn the user.
|
||||
if (str_starts_with($lowerLocation, 'http://')) {
|
||||
$this->warn("You are downloading a module from an insecure HTTP source.\nWe recommend only using HTTPS sources to avoid various security risks.");
|
||||
if (!$this->confirm('Are you sure you want to continue without HTTPS?')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Download ZIP and get its location
|
||||
return $this->downloadModuleFile($location);
|
||||
}
|
||||
|
||||
// Validate file and get full location
|
||||
$zipPath = realpath($location);
|
||||
if (!$zipPath || !is_file($zipPath)) {
|
||||
$this->error("ERROR: Module file not found at {$location}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return $zipPath;
|
||||
}
|
||||
|
||||
protected function cleanup(): void
|
||||
{
|
||||
foreach ($this->cleanupActions as $action) {
|
||||
$action();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,16 +58,6 @@ class ZipExportReader
|
||||
{
|
||||
$this->open();
|
||||
|
||||
$info = $this->zip->statName('data.json');
|
||||
if ($info === false) {
|
||||
throw new ZipExportException(trans('errors.import_zip_cant_decode_data'));
|
||||
}
|
||||
|
||||
$maxSize = max(intval(config()->get('app.upload_limit')), 1) * 1000000;
|
||||
if ($info['size'] > $maxSize) {
|
||||
throw new ZipExportException(trans('errors.import_zip_data_too_large'));
|
||||
}
|
||||
|
||||
// Validate json data exists, including metadata
|
||||
$jsonData = $this->zip->getFromName('data.json') ?: '';
|
||||
$importData = json_decode($jsonData, true);
|
||||
@@ -83,17 +73,6 @@ class ZipExportReader
|
||||
return $this->zip->statName("files/{$fileName}") !== false;
|
||||
}
|
||||
|
||||
public function fileWithinSizeLimit(string $fileName): bool
|
||||
{
|
||||
$fileInfo = $this->zip->statName("files/{$fileName}");
|
||||
if ($fileInfo === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$maxSize = max(intval(config()->get('app.upload_limit')), 1) * 1000000;
|
||||
return $fileInfo['size'] <= $maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|resource
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@ class ZipFileReferenceRule implements ValidationRule
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -22,13 +23,6 @@ class ZipFileReferenceRule implements ValidationRule
|
||||
$fail('validation.zip_file')->translate();
|
||||
}
|
||||
|
||||
if (!$this->context->zipReader->fileWithinSizeLimit($value)) {
|
||||
$fail('validation.zip_file_size')->translate([
|
||||
'attribute' => $value,
|
||||
'size' => config('app.upload_limit'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($this->acceptedMimes)) {
|
||||
$fileMime = $this->context->zipReader->sniffFileMime($value);
|
||||
if (!in_array($fileMime, $this->acceptedMimes)) {
|
||||
|
||||
@@ -265,12 +265,6 @@ class ZipImportRunner
|
||||
|
||||
protected function zipFileToUploadedFile(string $fileName, ZipExportReader $reader): UploadedFile
|
||||
{
|
||||
if (!$reader->fileWithinSizeLimit($fileName)) {
|
||||
throw new ZipImportException([
|
||||
"File $fileName exceeds app upload limit."
|
||||
]);
|
||||
}
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'bszipextract');
|
||||
$fileStream = $reader->streamFile($fileName);
|
||||
$tempStream = fopen($tempPath, 'wb');
|
||||
|
||||
@@ -78,9 +78,8 @@ class SearchController extends Controller
|
||||
|
||||
// Search for entities otherwise show most popular
|
||||
if ($searchTerm !== false) {
|
||||
$options = SearchOptions::fromString($searchTerm);
|
||||
$options->setFilter('type', implode('|', $entityTypes));
|
||||
$entities = $this->searchRunner->searchEntities($options, 'all', 1, 20)['results'];
|
||||
$searchTerm .= ' {type:' . implode('|', $entityTypes) . '}';
|
||||
$entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results'];
|
||||
} else {
|
||||
$entities = $queryPopular->run(20, 0, $entityTypes);
|
||||
}
|
||||
|
||||
@@ -82,12 +82,4 @@ class SearchOptionSet
|
||||
$values = array_values(array_filter($this->options, fn (SearchOption $option) => !$option->negated));
|
||||
return new self($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self<T>
|
||||
*/
|
||||
public function limit(int $limit): self
|
||||
{
|
||||
return new self(array_slice(array_values($this->options), 0, $limit));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class SearchOptions
|
||||
{
|
||||
$instance = new self();
|
||||
$instance->addOptionsFromString($search);
|
||||
$instance->limitOptions();
|
||||
return $instance;
|
||||
}
|
||||
|
||||
@@ -88,8 +87,6 @@ class SearchOptions
|
||||
$instance->filters = $instance->filters->merge($extras->filters);
|
||||
}
|
||||
|
||||
$instance->limitOptions();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
@@ -150,25 +147,6 @@ class SearchOptions
|
||||
$this->filters = $this->filters->merge(new SearchOptionSet($terms['filters']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the amount of search options to reasonable levels.
|
||||
* Provides higher limits to logged-in users since that signals a slightly
|
||||
* higher level of trust.
|
||||
*/
|
||||
protected function limitOptions(): void
|
||||
{
|
||||
$userLoggedIn = !user()->isGuest();
|
||||
$searchLimit = $userLoggedIn ? 10 : 5;
|
||||
$exactLimit = $userLoggedIn ? 4 : 2;
|
||||
$tagLimit = $userLoggedIn ? 8 : 4;
|
||||
$filterLimit = $userLoggedIn ? 10 : 5;
|
||||
|
||||
$this->searches = $this->searches->limit($searchLimit);
|
||||
$this->exacts = $this->exacts->limit($exactLimit);
|
||||
$this->tags = $this->tags->limit($tagLimit);
|
||||
$this->filters = $this->filters->limit($filterLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode backslash escaping within the input string.
|
||||
*/
|
||||
|
||||
@@ -5,22 +5,21 @@ namespace BookStack\Theming;
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Util\FilePathNormalizer;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class ThemeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Serve a public file from the configured theme.
|
||||
*/
|
||||
public function publicFile(string $theme, string $path): StreamedResponse
|
||||
public function publicFile(string $theme, string $path)
|
||||
{
|
||||
$cleanPath = FilePathNormalizer::normalize($path);
|
||||
if ($theme !== Theme::getTheme() || !$cleanPath) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$filePath = Theme::findFirstFile("public/{$cleanPath}");
|
||||
if (!$filePath) {
|
||||
$filePath = theme_path("public/{$cleanPath}");
|
||||
if (!file_exists($filePath)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,16 +134,6 @@ class ThemeEvents
|
||||
*/
|
||||
const ROUTES_REGISTER_WEB_AUTH = 'routes_register_web_auth';
|
||||
|
||||
|
||||
/**
|
||||
* Theme register views event.
|
||||
* Called by the theme system when a theme is active, so that custom view templates can be registered
|
||||
* to be rendered in addition to existing app views.
|
||||
*
|
||||
* @param \BookStack\Theming\ThemeViews $themeViews
|
||||
*/
|
||||
const THEME_REGISTER_VIEWS = 'theme_register_views';
|
||||
|
||||
/**
|
||||
* Web before middleware action.
|
||||
* Runs before the request is handled but after all other middleware apart from those
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Theming;
|
||||
|
||||
readonly class ThemeModule
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $description,
|
||||
public string $version,
|
||||
public string $folderName,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ThemeModule instance from JSON data.
|
||||
*
|
||||
* @throws ThemeModuleException
|
||||
*/
|
||||
public static function fromJson(array $data, string $folderName): self
|
||||
{
|
||||
if (empty($data['name']) || !is_string($data['name'])) {
|
||||
throw new ThemeModuleException("Module in folder \"{$folderName}\" is missing a valid 'name' property");
|
||||
}
|
||||
|
||||
if (!isset($data['description']) || !is_string($data['description'])) {
|
||||
throw new ThemeModuleException("Module in folder \"{$folderName}\" is missing a valid 'description' property");
|
||||
}
|
||||
|
||||
if (!isset($data['version']) || !is_string($data['version'])) {
|
||||
throw new ThemeModuleException("Module in folder \"{$folderName}\" is missing a valid 'version' property");
|
||||
}
|
||||
|
||||
if (!preg_match('/^v?\d+\.\d+\.\d+(-.*)?$/', $data['version'])) {
|
||||
throw new ThemeModuleException("Module in folder \"{$folderName}\" has an invalid 'version' format. Expected semantic version format like '1.0.0' or 'v1.0.0'");
|
||||
}
|
||||
|
||||
return new self(
|
||||
name: $data['name'],
|
||||
description: $data['description'],
|
||||
version: $data['version'],
|
||||
folderName: $folderName,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a path for a file within this module.
|
||||
*/
|
||||
public function path($path = ''): string
|
||||
{
|
||||
$component = trim($path, '/');
|
||||
return theme_path("modules/{$this->folderName}/{$component}");
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return str_starts_with($this->version, 'v') ? $this->version : 'v' . $this->version;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Theming;
|
||||
|
||||
class ThemeModuleException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Theming;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ThemeModuleManager
|
||||
{
|
||||
/** @var array<string, ThemeModule>|null */
|
||||
protected array|null $loadedModules = null;
|
||||
|
||||
public function __construct(
|
||||
protected string $modulesFolderPath
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, ThemeModule>
|
||||
*/
|
||||
public function getByName(string $name): array
|
||||
{
|
||||
return array_filter($this->load(), fn(ThemeModule $module) => $module->name === $name);
|
||||
}
|
||||
|
||||
public function deleteModuleFolder(string $moduleFolderName): void
|
||||
{
|
||||
$modules = $this->load();
|
||||
$module = $modules[$moduleFolderName] ?? null;
|
||||
if (!$module) {
|
||||
return;
|
||||
}
|
||||
|
||||
$moduleFolderPath = $module->path('');
|
||||
if (!file_exists($moduleFolderPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deleteDirectoryRecursively($moduleFolderPath);
|
||||
unset($this->loadedModules[$moduleFolderName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ThemeModuleException
|
||||
*/
|
||||
public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule
|
||||
{
|
||||
$baseFolderName = Str::limit(Str::slug($name), 20);
|
||||
$folderName = $baseFolderName;
|
||||
while (!$baseFolderName || file_exists($this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName)) {
|
||||
$folderName = ($baseFolderName ?: 'mod') . '-' . Str::random(4);
|
||||
}
|
||||
|
||||
$folderPath = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName;
|
||||
$zip->extractTo($folderPath);
|
||||
|
||||
$module = $this->loadFromFolder($folderName);
|
||||
if (!$module) {
|
||||
throw new ThemeModuleException("Failed to load module from zip file after extraction");
|
||||
}
|
||||
|
||||
return $module;
|
||||
}
|
||||
|
||||
protected function deleteDirectoryRecursively(string $path): void
|
||||
{
|
||||
$items = array_diff(scandir($path), ['.', '..']);
|
||||
foreach ($items as $item) {
|
||||
$itemPath = $path . DIRECTORY_SEPARATOR . $item;
|
||||
if (is_dir($itemPath)) {
|
||||
$this->deleteDirectoryRecursively($itemPath);
|
||||
} else {
|
||||
$deleted = unlink($itemPath);
|
||||
if (!$deleted) {
|
||||
throw new ThemeModuleException("Failed to delete file at \"{$itemPath}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
rmdir($path);
|
||||
}
|
||||
|
||||
public function load(): array
|
||||
{
|
||||
if ($this->loadedModules !== null) {
|
||||
return $this->loadedModules;
|
||||
}
|
||||
|
||||
if (!is_dir($this->modulesFolderPath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$subFolders = array_filter(scandir($this->modulesFolderPath), function ($item) {
|
||||
return $item !== '.' && $item !== '..' && is_dir($this->modulesFolderPath . DIRECTORY_SEPARATOR . $item);
|
||||
});
|
||||
|
||||
$modules = [];
|
||||
|
||||
foreach ($subFolders as $folderName) {
|
||||
$module = $this->loadFromFolder($folderName);
|
||||
if ($module) {
|
||||
$modules[$folderName] = $module;
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadedModules = $modules;
|
||||
|
||||
return $modules;
|
||||
}
|
||||
|
||||
protected function loadFromFolder(string $folderName): ThemeModule|null
|
||||
{
|
||||
$moduleJsonFile = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName . DIRECTORY_SEPARATOR . 'bookstack-module.json';
|
||||
if (!file_exists($moduleJsonFile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$jsonContent = file_get_contents($moduleJsonFile);
|
||||
$jsonData = json_decode($jsonContent, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new ThemeModuleException("Invalid JSON in module file at \"{$moduleJsonFile}\": " . json_last_error_msg());
|
||||
}
|
||||
|
||||
$module = ThemeModule::fromJson($jsonData, $folderName);
|
||||
} catch (ThemeModuleException $exception) {
|
||||
throw $exception;
|
||||
} catch (\Exception $exception) {
|
||||
throw new ThemeModuleException("Failed loading module from \"{$moduleJsonFile}\" with error: {$exception->getMessage()}");
|
||||
}
|
||||
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Theming;
|
||||
|
||||
use ZipArchive;
|
||||
|
||||
readonly class ThemeModuleZip
|
||||
{
|
||||
public function __construct(
|
||||
protected string $path
|
||||
) {
|
||||
}
|
||||
|
||||
public function extractTo(string $destinationPath): void
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($this->path);
|
||||
$zip->extractTo($destinationPath);
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the module's JSON metadata to read it into a ThemeModule instance.
|
||||
* @throws ThemeModuleException
|
||||
*/
|
||||
public function getModuleInstance(): ThemeModule
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$open = $zip->open($this->path);
|
||||
if ($open !== true) {
|
||||
throw new ThemeModuleException("Unable to open zip file at {$this->path}");
|
||||
}
|
||||
|
||||
$moduleJsonText = $zip->getFromName('bookstack-module.json');
|
||||
$zip->close();
|
||||
|
||||
if ($moduleJsonText === false) {
|
||||
throw new ThemeModuleException("bookstack-module.json not found within module ZIP at {$this->path}");
|
||||
}
|
||||
|
||||
$moduleJson = json_decode($moduleJsonText, true);
|
||||
if ($moduleJson === null) {
|
||||
throw new ThemeModuleException("Could not read JSON from bookstack-module.json within module ZIP at {$this->path}");
|
||||
}
|
||||
|
||||
return ThemeModule::fromJson($moduleJson, '_temp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the zip file.
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the zip file exists and that it appears to be a valid zip file.
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
if (!file_exists($this->path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$zip = new ZipArchive();
|
||||
$open = $zip->open($this->path, ZipArchive::RDONLY);
|
||||
if ($open === true) {
|
||||
$zip->close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total size of the zip file contents when uncompressed.
|
||||
*/
|
||||
public function getContentsSize(): int
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
|
||||
if ($zip->open($this->path) !== true) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$totalSize = 0;
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$stat = $zip->statIndex($i);
|
||||
if ($stat !== false) {
|
||||
$totalSize += $stat['size'];
|
||||
}
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
return $totalSize;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use BookStack\Access\SocialDriverManager;
|
||||
use BookStack\Exceptions\ThemeException;
|
||||
use Illuminate\Console\Application;
|
||||
use Illuminate\Console\Application as Artisan;
|
||||
use Illuminate\View\FileViewFinder;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
class ThemeService
|
||||
@@ -16,11 +15,6 @@ class ThemeService
|
||||
*/
|
||||
protected array $listeners = [];
|
||||
|
||||
/**
|
||||
* @var array<string, ThemeModule>
|
||||
*/
|
||||
protected array $modules = [];
|
||||
|
||||
/**
|
||||
* Get the currently configured theme.
|
||||
* Returns an empty string if not configured.
|
||||
@@ -82,71 +76,20 @@ class ThemeService
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any actions from the 'functions.php' file of the active theme or its modules.
|
||||
* Read any actions from the set theme path if the 'functions.php' file exists.
|
||||
*/
|
||||
public function readThemeActions(): void
|
||||
{
|
||||
$moduleFunctionFiles = array_map(function (ThemeModule $module): string {
|
||||
return $module->path('functions.php');
|
||||
}, $this->modules);
|
||||
$allFunctionFiles = array_merge(array_values($moduleFunctionFiles), [theme_path('functions.php')]);
|
||||
$filteredFunctionFiles = array_filter($allFunctionFiles, function (string $file): bool {
|
||||
return $file && file_exists($file);
|
||||
});
|
||||
|
||||
foreach ($filteredFunctionFiles as $functionFile) {
|
||||
$themeActionsFile = theme_path('functions.php');
|
||||
if ($themeActionsFile && file_exists($themeActionsFile)) {
|
||||
try {
|
||||
require $functionFile;
|
||||
require $themeActionsFile;
|
||||
} catch (\Error $exception) {
|
||||
throw new ThemeException("Failed loading theme functions file at \"{$functionFile}\" with error: {$exception->getMessage()}");
|
||||
throw new ThemeException("Failed loading theme functions file at \"{$themeActionsFile}\" with error: {$exception->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the modules folder and load in any valid theme modules.
|
||||
* @throws ThemeModuleException
|
||||
*/
|
||||
public function loadModules(): void
|
||||
{
|
||||
$modulesFolder = theme_path('modules');
|
||||
if (!$modulesFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->modules = (new ThemeModuleManager($modulesFolder))->load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all loaded theme modules.
|
||||
* @return array<string, ThemeModule>
|
||||
*/
|
||||
public function getModules(): array
|
||||
{
|
||||
return $this->modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a specific file within the theme or its modules.
|
||||
* Returns the first file found or null if not found.
|
||||
*/
|
||||
public function findFirstFile(string $path): ?string
|
||||
{
|
||||
$themePath = theme_path($path);
|
||||
if (file_exists($themePath)) {
|
||||
return $themePath;
|
||||
}
|
||||
|
||||
foreach ($this->modules as $module) {
|
||||
$customizedFile = $module->path($path);
|
||||
if (file_exists($customizedFile)) {
|
||||
return $customizedFile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SocialDriverManager::addSocialDriver
|
||||
*/
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Theming;
|
||||
|
||||
use BookStack\Exceptions\ThemeException;
|
||||
use Illuminate\View\FileViewFinder;
|
||||
|
||||
class ThemeViews
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
protected array $beforeViews = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
protected array $afterViews = [];
|
||||
|
||||
/**
|
||||
* Register any extra paths for where we may expect views to be located
|
||||
* with the provided FileViewFinder, to make custom views available for use.
|
||||
* @param ThemeModule[] $modules
|
||||
*/
|
||||
public function registerViewPathsForTheme(FileViewFinder $finder, array $modules): void
|
||||
{
|
||||
foreach ($modules as $module) {
|
||||
$moduleViewsPath = $module->path('views');
|
||||
if (file_exists($moduleViewsPath) && is_dir($moduleViewsPath)) {
|
||||
$finder->prependLocation($moduleViewsPath);
|
||||
}
|
||||
}
|
||||
|
||||
$finder->prependLocation(theme_path());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the response for a blade template view include.
|
||||
*/
|
||||
public function handleViewInclude(string $viewPath, array $data = []): string
|
||||
{
|
||||
if (!$this->hasRegisteredViews()) {
|
||||
return view()->make($viewPath, $data)->render();
|
||||
}
|
||||
|
||||
$viewsContent = [
|
||||
...$this->renderViewSets($this->beforeViews[$viewPath] ?? [], $data),
|
||||
view()->make($viewPath, $data)->render(),
|
||||
...$this->renderViewSets($this->afterViews[$viewPath] ?? [], $data),
|
||||
];
|
||||
|
||||
return implode("\n", $viewsContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom view to be rendered before the given target view is included in the template system.
|
||||
*/
|
||||
public function renderBefore(string $targetView, string $localView, int $priority = 50): void
|
||||
{
|
||||
$this->registerAdjacentView($this->beforeViews, $targetView, $localView, $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom view to be rendered after the given target view is included in the template system.
|
||||
*/
|
||||
public function renderAfter(string $targetView, string $localView, int $priority = 50): void
|
||||
{
|
||||
$this->registerAdjacentView($this->afterViews, $targetView, $localView, $priority);
|
||||
}
|
||||
|
||||
public function hasRegisteredViews(): bool
|
||||
{
|
||||
return !empty($this->beforeViews) && !empty($this->afterViews);
|
||||
}
|
||||
|
||||
protected function registerAdjacentView(array &$location, string $targetView, string $localView, int $priority = 50): void
|
||||
{
|
||||
$viewPath = theme_path($localView . '.blade.php');
|
||||
if (!file_exists($viewPath)) {
|
||||
throw new ThemeException("Expected registered view file at \"{$viewPath}\" does not exist");
|
||||
}
|
||||
|
||||
if (!isset($location[$targetView])) {
|
||||
$location[$targetView] = [];
|
||||
}
|
||||
$location[$targetView][$viewPath] = $priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, int> $viewSet
|
||||
* @return string[]
|
||||
*/
|
||||
protected function renderViewSets(array $viewSet, array $data): array
|
||||
{
|
||||
$paths = array_keys($viewSet);
|
||||
usort($paths, function (string $a, string $b) use ($viewSet) {
|
||||
return $viewSet[$a] <=> $viewSet[$b];
|
||||
});
|
||||
|
||||
return array_map(function (string $viewPath) use ($data) {
|
||||
return view()->file($viewPath, $data)->render();
|
||||
}, $paths);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace BookStack\Translation;
|
||||
|
||||
use BookStack\Facades\Theme;
|
||||
use Illuminate\Translation\FileLoader as BaseLoader;
|
||||
|
||||
class FileLoader extends BaseLoader
|
||||
@@ -13,6 +12,11 @@ class FileLoader extends BaseLoader
|
||||
* Extends Laravel's translation FileLoader to look in multiple directories
|
||||
* so that we can load in translation overrides from the theme file if wanted.
|
||||
*
|
||||
* Note: As of using Laravel 10, this may now be redundant since Laravel's
|
||||
* file loader supports multiple paths. This needs further testing though
|
||||
* to confirm if Laravel works how we expect, since we specifically need
|
||||
* the theme folder to be able to partially override core lang files.
|
||||
*
|
||||
* @param string $locale
|
||||
* @param string $group
|
||||
* @param string|null $namespace
|
||||
@@ -28,18 +32,9 @@ class FileLoader extends BaseLoader
|
||||
if (is_null($namespace) || $namespace === '*') {
|
||||
$themePath = theme_path('lang');
|
||||
$themeTranslations = $themePath ? $this->loadPaths([$themePath], $locale, $group) : [];
|
||||
|
||||
$modules = Theme::getModules();
|
||||
$moduleTranslations = [];
|
||||
foreach ($modules as $module) {
|
||||
$modulePath = $module->path('lang');
|
||||
if (file_exists($modulePath)) {
|
||||
$moduleTranslations = array_merge($moduleTranslations, $this->loadPaths([$modulePath], $locale, $group));
|
||||
}
|
||||
}
|
||||
|
||||
$originalTranslations = $this->loadPaths($this->paths, $locale, $group);
|
||||
return array_merge($originalTranslations, $moduleTranslations, $themeTranslations);
|
||||
|
||||
return array_merge($originalTranslations, $themeTranslations);
|
||||
}
|
||||
|
||||
return $this->loadNamespaced($locale, $group, $namespace);
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace BookStack\Util;
|
||||
|
||||
use BookStack\Facades\Theme;
|
||||
|
||||
class SvgIcon
|
||||
{
|
||||
public function __construct(
|
||||
@@ -25,9 +23,12 @@ class SvgIcon
|
||||
$attrString .= $attrName . '="' . $attr . '" ';
|
||||
}
|
||||
|
||||
$defaultIconPath = resource_path('icons/' . $this->name . '.svg');
|
||||
$iconPath = Theme::findFirstFile("icons/{$this->name}.svg") ?? $defaultIconPath;
|
||||
if (!file_exists($iconPath)) {
|
||||
$iconPath = resource_path('icons/' . $this->name . '.svg');
|
||||
$themeIconPath = theme_path('icons/' . $this->name . '.svg');
|
||||
|
||||
if ($themeIconPath && file_exists($themeIconPath)) {
|
||||
$iconPath = $themeIconPath;
|
||||
} elseif (!file_exists($iconPath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
537
composer.lock
generated
537
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
project_id: "377219"
|
||||
project_identifier: bookstack
|
||||
base_path: .
|
||||
preserve_hierarchy: false
|
||||
|
||||
@@ -1 +1 @@
|
||||
22e02ee72d21ff719c1073abbec8302f8e2096ba6d072e133051064ed24b45b1
|
||||
1b42490f844df68cfd0194d16ca4e40dc82bd50256236d5dee516820dd8271ac
|
||||
@@ -14,9 +14,6 @@ RUN apt-get update && \
|
||||
wait-for-it && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Mark /app as safe for Git >= 2.35.2
|
||||
RUN git config --system --add safe.directory /app
|
||||
|
||||
# Install PHP extensions
|
||||
RUN docker-php-ext-configure ldap --with-libdir="lib/$(gcc -dumpmachine)" && \
|
||||
docker-php-ext-configure gd --with-freetype --with-jpeg && \
|
||||
|
||||
@@ -99,41 +99,6 @@ Theme::listen(ThemeEvents::APP_BOOT, function($app) {
|
||||
});
|
||||
```
|
||||
|
||||
## Custom View Registration Example
|
||||
|
||||
Using the logical theme system, you can register custom views to be rendered before/after other existing views, providing a flexible way to add content without needing to override and/or replicate existing content. This is done by listening to the `THEME_REGISTER_VIEWS`.
|
||||
|
||||
**Note:** You don't need to use this to override existing views, or register whole new main views to use, since that's done automatically based on their existence. This is just for advanced capabilities like inserting before/after existing views.
|
||||
|
||||
This event provides a `ThemeViews` instance which has the following methods made available:
|
||||
|
||||
- `renderBefore(string $targetView, string $localView, int $priority)`
|
||||
- `renderAfter(string $targetView, string $localView, int $priority)`
|
||||
|
||||
The target view is the name of that which we want to insert our custom view relative to.
|
||||
The local view is the name of the view we want to add and render.
|
||||
The priority provides a suggestion to the ordering of view display, with lower numbers being shown first. This defaults to 50 if not provided.
|
||||
|
||||
Here's an example of this in use:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use BookStack\Facades\Theme;
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Theming\ThemeViews;
|
||||
|
||||
Theme::listen(ThemeEvents::THEME_REGISTER_VIEWS, function (ThemeViews $themeViews) {
|
||||
$themeViews->renderBefore('layouts.parts.header', 'welcome-banner', 4);
|
||||
$themeViews->renderAfter('layouts.parts.header', 'information-alert');
|
||||
$themeViews->renderAfter('layouts.parts.header', 'additions.password-notice', 20);
|
||||
});
|
||||
```
|
||||
|
||||
In this example, we're inserting custom views before and after the main header bar.
|
||||
BookStack will look for a `welcome-banner.blade.php` file within our theme folder (or a theme module view folder) to render before the header. It'll look for the `information-alert.blade.php` and `additions/password-notice.blade.php` views to render afterwards.
|
||||
The password notice will be shown above the information alert view, since it has a specified priority of 20, whereas the information alert view would default to a priority of 50.
|
||||
|
||||
## Custom Command Registration Example
|
||||
|
||||
The logical theme system supports adding custom [artisan commands](https://laravel.com/docs/8.x/artisan) to BookStack.
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# Theme System Modules
|
||||
|
||||
A theme system module is a collection of customizations using the [visual](visual-theme-system.md) and [logical](logical-theme-system.md) theme systems, provided along with some metadata, that can be installed alongside other modules within a theme. They can effectively be thought of as "plugins" or "extensions" that can be applied in addition to any customizations in the active theme.
|
||||
|
||||
### Module Location
|
||||
|
||||
Modules are contained within a folder themselves, which should be located inside a `modules` folder within a [BookStack theme folder](visual-theme-system.md#getting-started).
|
||||
As an example, starting from the `themes/` top-level folder of a BookStack instance:
|
||||
|
||||
```txt
|
||||
themes
|
||||
└── my-theme
|
||||
└── modules
|
||||
├── module-a
|
||||
│ └── bookstack-module.json
|
||||
└── module-b
|
||||
└── bookstack-module.json
|
||||
```
|
||||
|
||||
### Module Format
|
||||
|
||||
A module exists as a folder in the location [as detailed above](#module-location).
|
||||
The content within the module folder should then follow this format:
|
||||
|
||||
- `bookstack-module.json` - REQUIRED - A JSON file containing [the metadata](#module-json-metadata) for the module.
|
||||
- `functions.php` - OPTIONAL - A PHP file containing code for the [logical theme system](logical-theme-system.md).
|
||||
- `icons/` - OPTIONAL - A folder containing any icons to use as per [the visual theme system](visual-theme-system.md#customizing-icons).
|
||||
- `lang/` - OPTIONAL - A folder containing any language files to use as per [the visual theme system](visual-theme-system.md#customizing-text-content).
|
||||
- `public/` - OPTIONAL - A folder containing any files to expose into public web-space as per [the visual theme system](visual-theme-system.md#publicly-accessible-files).
|
||||
- `views/` - OPTIONAL - A folder containing any view additions or overrides as per [the visual theme system](visual-theme-system.md#customizing-view-files).
|
||||
|
||||
You can create additional directories/files for your own needs within the module, but ideally name them something unique to prevent conflicts with the above structure.
|
||||
|
||||
### Module JSON Metadata
|
||||
|
||||
Modules are required to have a `bookstack-module.json` file in the top level directory of the module.
|
||||
This must be a JSON file with the following properties:
|
||||
|
||||
- `name` - string - An (ideally unique) name for the module.
|
||||
- `description` - string - A short description of the module.
|
||||
- `version` - string - A string version number generally following [semantic versioning](https://semver.org/).
|
||||
- Examples: `v0.4.0`, `4.3.12`, `v0.1.0-beta4`.
|
||||
|
||||
### Customization Order/Precedence
|
||||
|
||||
It's possible that multiple modules may override/customize the same content.
|
||||
Right now, there's no assurance in regard to the order in which modules may be loaded.
|
||||
Generally they will be used/searched in order of their module folder name, but this is not assured and should not be relied upon.
|
||||
|
||||
It's also possible that modules customize the same content as the configured theme.
|
||||
In this scenario, the theme takes precedence. Modules are designed to be more portable and instance abstract, whereas the theme folder would typically be specific to the instance.
|
||||
This allows the theme to be used to customize or override module content for the BookStack instance, without altering the module code itself.
|
||||
|
||||
### Module Best Practices
|
||||
|
||||
Here are some general best practices when it comes to creating modules:
|
||||
|
||||
- Use a unique name and clear description so the user can understand the purpose of the module.
|
||||
- Increment the metadata version on change, keeping to [semver](https://semver.org/) to indicate compatibility of new versions.
|
||||
- Where possible, prefer to [insert views before/after](logical-theme-system.md#custom-view-registration-example) instead of overriding existing views, to reduce likelihood of conflicts or update troubles.
|
||||
|
||||
### Distribution Format
|
||||
|
||||
Modules are expected to be distributed as a compressed ZIP file, where the ZIP contents follow that of a module folder.
|
||||
BookStack provides a `php artisan bookstack:install-module` command which allows modules to be installed from these ZIP files, either from a local path or from a web URL.
|
||||
Currently, there's a hardcoded total filesize limit of 50MB for module contents installed via this method.
|
||||
|
||||
There is not yet any direct update mechanism for modules, although this is something we may introduce in the future.
|
||||
@@ -4,7 +4,7 @@ BookStack allows visual customization via the theme system which enables you to
|
||||
|
||||
This is part of the theme system alongside the [logical theme system](./logical-theme-system.md).
|
||||
|
||||
**Note:** This theme system itself is maintained and supported, but usages of this system, including the files you are able to override, are not considered stable and may change upon any update. You should test any customizations made after updates.
|
||||
**Note:** This theme system itself is maintained and supported but usages of this system, including the files you are able to override, are not considered stable and may change upon any update. You should test any customizations made after updates.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -18,9 +18,6 @@ You'll need to tell BookStack to use your theme via the `APP_THEME` option in yo
|
||||
Content placed in your `themes/<theme_name>/` folder will override the original view files found in the `resources/views` folder. These files are typically [Laravel Blade](https://laravel.com/docs/10.x/blade) files.
|
||||
As an example, I could override the `resources/views/books/parts/list-item.blade.php` file with my own template at the path `themes/<theme_name>/books/parts/list-item.blade.php`.
|
||||
|
||||
In addition to overriding original views, this could be used to add new views for use via the [logical theme system](logical-theme-system.md).
|
||||
By using the `THEME_REGISTER_VIEWS` logical event, you can also register your views to be rendered before/after existing views. An example of this can be found in our [logical theme guidance](logical-theme-system.md#custom-view-registration-example).
|
||||
|
||||
## Customizing Icons
|
||||
|
||||
SVG files placed in a `themes/<theme_name>/icons` folder will override any icons of the same name within `resources/icons`. You'd typically want to follow the format convention of the existing icons, where no XML deceleration is included and no width & height attributes are set, to ensure optimal compatibility.
|
||||
@@ -53,7 +50,7 @@ configured application theme.
|
||||
|
||||
There are some considerations to these publicly served files:
|
||||
|
||||
- Only a predetermined range of "web safe" content-types are currently served.
|
||||
- Only a predetermined range "web safe" content-types are currently served.
|
||||
- This limits running into potential insecure scenarios in serving problematic file types.
|
||||
- A static 1-day cache time it set on files served from this folder.
|
||||
- You can use alternative cache-breaking techniques (change of query string) upon changes if needed.
|
||||
|
||||
@@ -760,13 +760,6 @@ Copyright: Copyright (c) 2014-present Fabien Potencier
|
||||
Source: https://github.com/symfony/var-dumper.git
|
||||
Link: https://symfony.com
|
||||
-----------
|
||||
thecodingmachine/safe
|
||||
License: MIT
|
||||
License File: vendor/thecodingmachine/safe/LICENSE
|
||||
Copyright: Copyright (c) 2018 TheCodingMachine
|
||||
Source: https://github.com/thecodingmachine/safe.git
|
||||
Link: https://github.com/thecodingmachine/safe.git
|
||||
-----------
|
||||
tijsverkoyen/css-to-inline-styles
|
||||
License: BSD-3-Clause
|
||||
License File: vendor/tijsverkoyen/css-to-inline-styles/LICENSE.md
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'لم أتمكن من قراءة المِلَفّ المضغوط -ZIP-.',
|
||||
'import_zip_cant_decode_data' => 'لم نتمكن من العثور على محتوى المِلَفّ المضغوط data.json وفك تشفيره.',
|
||||
'import_zip_no_data' => 'لا تتضمن بيانات المِلَفّ المضغوط أي محتوى متوقع للكتاب أو الفصل أو الصفحة.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'فشل التحقق من صحة استيراد المِلَفّ المضغوط بسبب الأخطاء التالية:',
|
||||
'import_zip_failed_notification' => 'فشل استيراد المِلَفّ المضغوط.',
|
||||
'import_perms_books' => 'أنت تفتقر إلى الصلاحيات المطلوبة لإنشاء الكتب.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'تعذر تحميل الملف. قد لا يقبل الخادم ملفات بهذا الحجم.',
|
||||
|
||||
'zip_file' => ':attribute بحاجة إلى الرجوع إلى مِلَفّ داخل المِلَفّ المضغوط.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => ':attribute بحاجة إلى الإشارة إلى مِلَفّ من نوع :validTypes، وجدت :foundType.',
|
||||
'zip_model_expected' => 'عنصر البيانات المتوقع ولكن ":type" تم العثور عليه.',
|
||||
'zip_unique' => 'يجب أن يكون :attribute فريداً لنوع الكائن داخل المِلَفّ المضغوط.',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
return [
|
||||
|
||||
'failed' => 'Въведените данни не съвпадат с информацията в системата.',
|
||||
'failed' => 'Въведените удостоверителни данни не съвпадат с нашите записи.',
|
||||
'throttle' => 'Твърде много опити за влизане. Опитайте пак след :seconds секунди.',
|
||||
|
||||
// Login & Register
|
||||
@@ -65,7 +65,7 @@ return [
|
||||
'email_confirm_thanks_desc' => 'Почакайте малко, обработвайки потвърждението ви. Ако не сте пренасочени след 3 секунди, то натиснете долу връзката "Продължаване", за да продължите.',
|
||||
|
||||
'email_not_confirmed' => 'Имейл адресът не е потвърден',
|
||||
'email_not_confirmed_text' => 'Вашият имейл адрес все още не е потвърден.',
|
||||
'email_not_confirmed_text' => 'Вашият емейл адрес все още не е потвърден.',
|
||||
'email_not_confirmed_click_link' => 'Моля да последвате линка, който ви беше изпратен непосредствено след регистрацията.',
|
||||
'email_not_confirmed_resend' => 'Ако не откривате писмото, може да го изпратите отново като попълните формуляра по-долу.',
|
||||
'email_not_confirmed_resend_button' => 'Изпрати отново емейла за потвърждение',
|
||||
@@ -91,7 +91,7 @@ return [
|
||||
'mfa_option_totp_title' => 'Мобилно приложение',
|
||||
'mfa_option_totp_desc' => 'За да използваш многофакторно удостоверяване, ще ти трябва мобилно приложение, което поддържа временни еднократни пароли (TOTP), като например Google Authenticator, Authy или Microsoft Authenticator.',
|
||||
'mfa_option_backup_codes_title' => 'Резервни кодове',
|
||||
'mfa_option_backup_codes_desc' => 'Генерира набор от еднократни резервни кодове, които ще въвеждате при влизане, за да потвърдите самоличността си. Уверете се, че ги съхранявате на безопасно и сигурно място.',
|
||||
'mfa_option_backup_codes_desc' => 'Generates a set of one-time-use backup codes which you\'ll enter on login to verify your identity. Make sure to store these in a safe & secure place.',
|
||||
'mfa_gen_confirm_and_enable' => 'Потвърди и включи',
|
||||
'mfa_gen_backup_codes_title' => 'Настройка на резервни кодове',
|
||||
'mfa_gen_backup_codes_desc' => 'Запази този лист с кодове на сигурно място. Когато достъпваш системата, ще можеш да използваш един от тези кодове като вторичен механизъм за удостоверяване.',
|
||||
|
||||
@@ -6,7 +6,7 @@ return [
|
||||
|
||||
// Buttons
|
||||
'cancel' => 'Отказ',
|
||||
'close' => 'Затвори',
|
||||
'close' => 'Close',
|
||||
'confirm' => 'Потвърждаване',
|
||||
'back' => 'Назад',
|
||||
'save' => 'Запис',
|
||||
@@ -20,7 +20,7 @@ return [
|
||||
'description' => 'Описание',
|
||||
'role' => 'Роля',
|
||||
'cover_image' => 'Образ на корицата',
|
||||
'cover_image_description' => 'Изображението трябва да е около 440x250 px. Тъй като ще се мащабира и изрязва автоматично спрямо нуждите на интерфейса, крайните размери при показване може да се различават.',
|
||||
'cover_image_description' => 'This image should be approximately 440x250px although it will be flexibly scaled & cropped to fit the user interface in different scenarios as required, so actual dimensions for display will differ.',
|
||||
|
||||
// Actions
|
||||
'actions' => 'Действия',
|
||||
@@ -30,8 +30,8 @@ return [
|
||||
'create' => 'Създаване',
|
||||
'update' => 'Обновяване',
|
||||
'edit' => 'Редактиране',
|
||||
'archive' => 'Архивирай',
|
||||
'unarchive' => 'Разархивирай',
|
||||
'archive' => 'Archive',
|
||||
'unarchive' => 'Un-Archive',
|
||||
'sort' => 'Сортиране',
|
||||
'move' => 'Преместване',
|
||||
'copy' => 'Копиране',
|
||||
@@ -44,7 +44,7 @@ return [
|
||||
'remove' => 'Премахване',
|
||||
'add' => 'Добавяне',
|
||||
'configure' => 'Конфигуриране',
|
||||
'manage' => 'Управлявай',
|
||||
'manage' => 'Manage',
|
||||
'fullscreen' => 'Цял екран',
|
||||
'favourite' => 'Любимо',
|
||||
'unfavourite' => 'Не е любимо',
|
||||
@@ -54,7 +54,7 @@ return [
|
||||
'filter_clear' => 'Изчистване на филтрите',
|
||||
'download' => 'Изтегляне',
|
||||
'open_in_tab' => 'Отваряне в раздел',
|
||||
'open' => 'Отвори',
|
||||
'open' => 'Open',
|
||||
|
||||
// Sort Options
|
||||
'sort_options' => 'Опции за сортиране',
|
||||
@@ -111,5 +111,5 @@ return [
|
||||
'terms_of_service' => 'Условия на услугата',
|
||||
|
||||
// OpenSearch
|
||||
'opensearch_description' => 'Търси :appName',
|
||||
'opensearch_description' => 'Search :appName',
|
||||
];
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
'cancel' => 'Отказ',
|
||||
'save' => 'Запис',
|
||||
'close' => 'Затваряне',
|
||||
'apply' => 'Приложи',
|
||||
'apply' => 'Apply',
|
||||
'undo' => 'Отмяна',
|
||||
'redo' => 'Повтаряне',
|
||||
'left' => 'Вляво',
|
||||
|
||||
@@ -10,7 +10,7 @@ return [
|
||||
|
||||
// Auth
|
||||
'error_user_exists_different_creds' => 'Потребител с емайл :email вече съществува но с други данни.',
|
||||
'auth_pre_register_theme_prevention' => 'Потребителски профил не може да бъде създаден с посочената информация',
|
||||
'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details',
|
||||
'email_already_confirmed' => 'Емейлът вече беше потвърден. Моля опитрайте да влезете.',
|
||||
'email_confirmation_invalid' => 'Този код за достъп не е валиден или вече е бил използван, Моля опитай да се регистрираш отново.',
|
||||
'email_confirmation_expired' => 'Кодът за потвърждение изтече, нов емейл за потвърждение беше изпратен.',
|
||||
@@ -37,7 +37,7 @@ return [
|
||||
'social_driver_not_found' => 'Кодът за връзка със социалната мрежа не съществува',
|
||||
'social_driver_not_configured' => 'Социалните настройки на твоя :socialAccount не са конфигурирани правилно.',
|
||||
'invite_token_expired' => 'Твоята покана е изтекла. Вместо това може да пробваш да възстановиш паролата на профила си.',
|
||||
'login_user_not_found' => 'Потребител за това действие не може да бъде намерено.',
|
||||
'login_user_not_found' => 'A user for this action could not be found.',
|
||||
|
||||
// System
|
||||
'path_not_writable' => 'Не може да се качи файл в :filePath. Увери се на сървъра, че в пътя може да се записва.',
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Файлът не можа да бъде качен. Сървърът може да не приема файлове с такъв размер.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Fajl nije učitan. Server ne prihvata fajlove ove veličine.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'No es pot llegir el fitxer ZIP.',
|
||||
'import_zip_cant_decode_data' => 'No s\'ha pogut trobar i descodificar el fitxer data.json en el fitxer ZIP.',
|
||||
'import_zip_no_data' => 'Les dades del fitxer ZIP no contenen cap llibre, capítol o contingut de pàgina.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Error en validar la importació del ZIP amb els errors:',
|
||||
'import_zip_failed_notification' => 'Error en importar l\'arxiu ZIP.',
|
||||
'import_perms_books' => 'Li falten els permisos necessaris per crear llibres.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'No s’ha pogut pujar el fitxer. És possible que el servidor no admeti fitxers d’aquesta mida.',
|
||||
|
||||
'zip_file' => 'El :attribute necessita fer referència a un arxiu dins del ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'El :attribute necessita fer referència a un arxiu de tipus :validTyes, trobat :foundType.',
|
||||
'zip_model_expected' => 'S\'esperava un objecte de dades, però s\'ha trobat ":type".',
|
||||
'zip_unique' => 'El :attribute ha de ser únic pel tipus d\'objecte dins del ZIP.',
|
||||
|
||||
@@ -63,10 +63,10 @@ return [
|
||||
'import_delete_desc' => 'Potvrzením odstraníte nahraný ZIP soubor. Tento krok nelze vrátit zpět.',
|
||||
'import_errors' => 'Chyby importu',
|
||||
'import_errors_desc' => 'Při pokusu o import došlo k následujícím chybám:',
|
||||
'breadcrumb_siblings_for_page' => 'Přejít na jinou stránku',
|
||||
'breadcrumb_siblings_for_chapter' => 'Přejít na jinou kapitolu',
|
||||
'breadcrumb_siblings_for_book' => 'Přejít na jinou knihu',
|
||||
'breadcrumb_siblings_for_bookshelf' => 'Přejít na jinou polici',
|
||||
'breadcrumb_siblings_for_page' => 'Navigate siblings for page',
|
||||
'breadcrumb_siblings_for_chapter' => 'Navigate siblings for chapter',
|
||||
'breadcrumb_siblings_for_book' => 'Navigate siblings for book',
|
||||
'breadcrumb_siblings_for_bookshelf' => 'Navigate siblings for shelf',
|
||||
|
||||
// Permissions and restrictions
|
||||
'permissions' => 'Oprávnění',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Nelze načíst ZIP soubor.',
|
||||
'import_zip_cant_decode_data' => 'Nelze najít a dekódovat data.json v archivu ZIP.',
|
||||
'import_zip_no_data' => 'ZIP archiv neobsahuje knihy, kapitoly nebo stránky.',
|
||||
'import_zip_data_too_large' => 'Obsah souboru data.json v archivu ZIP překračuje maximální povolenou velikost.',
|
||||
'import_validation_failed' => 'Importování ZIP selhalo s chybami:',
|
||||
'import_zip_failed_notification' => 'Nepodařilo se naimportovat ZIP soubor.',
|
||||
'import_perms_books' => 'Chybí vám požadovaná oprávnění k vytvoření knih.',
|
||||
|
||||
@@ -11,8 +11,8 @@ return [
|
||||
'updated_page_subject' => 'Aktualizovaná stránka: :pageName',
|
||||
'updated_page_intro' => 'V :appName byla aktualizována stránka:',
|
||||
'updated_page_debounce' => 'Po nějakou dobu neobdržíte další oznámení o aktualizaci této stránky stejným editorem, aby se omezil počet stejných zpráv.',
|
||||
'comment_mention_subject' => 'Byli jste zmíněni v komentáři na stránce: :pageName',
|
||||
'comment_mention_intro' => 'Byli jste zmíněni v komentáři na webu :appName:',
|
||||
'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
|
||||
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
|
||||
|
||||
'detail_page_name' => 'Název stránky:',
|
||||
'detail_page_path' => 'Umístění:',
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'notifications_desc' => 'Nastavte si e-mailová oznámení, která dostanete při provedení určitých akcí v systému.',
|
||||
'notifications_opt_own_page_changes' => 'Upozornit na změny stránek u kterých jsem vlastníkem',
|
||||
'notifications_opt_own_page_comments' => 'Upozornit na komentáře na stránkách, které vlastním',
|
||||
'notifications_opt_comment_mentions' => 'Upozornit, když mě někdo zmíní v komentáři',
|
||||
'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
|
||||
'notifications_opt_comment_replies' => 'Upozornit na odpovědi na mé komentáře',
|
||||
'notifications_save' => 'Uložit nastavení',
|
||||
'notifications_update_success' => 'Nastavení oznámení byla aktualizována!',
|
||||
|
||||
@@ -75,8 +75,8 @@ return [
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Žádná omezení nebyla nastavena',
|
||||
|
||||
// Sorting Settings
|
||||
'sorting' => 'Seznamy a řazení',
|
||||
'sorting_book_default' => 'Výchozí řazení knih',
|
||||
'sorting' => 'Lists & Sorting',
|
||||
'sorting_book_default' => 'Default Book Sort Rule',
|
||||
'sorting_book_default_desc' => 'Vybere výchozí pravidlo řazení pro nové knihy. Řazení neovlivní existující knihy a může být upraveno u konkrétní knihy.',
|
||||
'sorting_rules' => 'Pravidla řazení',
|
||||
'sorting_rules_desc' => 'Toto jsou předem definovaná pravidla řazení, která mohou být použita na webu.',
|
||||
@@ -103,8 +103,8 @@ return [
|
||||
'sort_rule_op_updated_date' => 'Datum aktualizace',
|
||||
'sort_rule_op_chapters_first' => 'Kapitoly jako první',
|
||||
'sort_rule_op_chapters_last' => 'Kapitoly jako poslední',
|
||||
'sorting_page_limits' => 'Počet zobrazených položek na stránce',
|
||||
'sorting_page_limits_desc' => 'Nastavte, kolik položek se má zobrazit na stránce v různých seznamech na webu. Obvykle bude nižší počet výkonnější, zatímco vyšší počet eliminuje nutnost proklikávat se několika stránkami. Doporučuje se použít sudý násobek čísla 3 (18, 24, 30 atd.).',
|
||||
'sorting_page_limits' => 'Per-Page Display Limits',
|
||||
'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'Údržba',
|
||||
@@ -197,13 +197,13 @@ return [
|
||||
'role_import_content' => 'Importovat obsah',
|
||||
'role_editor_change' => 'Změnit editor stránek',
|
||||
'role_notifications' => 'Přijímat a spravovat oznámení',
|
||||
'role_permission_note_users_and_roles' => 'Tato oprávnění zároveň umožní zobrazit a vyhledat uživatele a role na webu.',
|
||||
'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.',
|
||||
'role_asset' => 'Obsahová oprávnění',
|
||||
'roles_system_warning' => 'Berte na vědomí, že přístup k některému ze tří výše uvedených oprávnění může uživateli umožnit změnit svá vlastní oprávnění nebo oprávnění ostatních uživatelů v systému. Přiřazujte role s těmito oprávněními pouze důvěryhodným uživatelům.',
|
||||
'role_asset_desc' => 'Tato oprávnění řídí přístup k obsahu napříč systémem. Specifická oprávnění na knihách, kapitolách a stránkách převáží tato nastavení.',
|
||||
'role_asset_admins' => 'Administrátoři automaticky dostávají přístup k veškerému obsahu, ale tyto volby mohou ukázat nebo skrýt volby v uživatelském rozhraní.',
|
||||
'role_asset_image_view_note' => 'To se týká viditelnosti ve správci obrázků. Skutečný přístup k nahraným souborům obrázků bude záviset na možnosti uložení systémových obrázků.',
|
||||
'role_asset_users_note' => 'Tato oprávnění zároveň umožní zobrazit a vyhledat uživatele v systému.',
|
||||
'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.',
|
||||
'role_all' => 'Vše',
|
||||
'role_own' => 'Vlastní',
|
||||
'role_controlled_by_asset' => 'Řídí se obsahem, do kterého jsou nahrávány',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Nahrávání :attribute se nezdařilo.',
|
||||
|
||||
'zip_file' => ':attribute musí odkazovat na soubor v archivu ZIP.',
|
||||
'zip_file_size' => 'Soubor :attribute nesmí překročit :size MB.',
|
||||
'zip_file_mime' => ':attribute musí odkazovat na soubor typu :validTypes, nalezen :foundType.',
|
||||
'zip_model_expected' => 'Očekáván datový objekt, ale nalezen „:type“.',
|
||||
'zip_unique' => ':attribute musí být jedinečný pro typ objektu v archivu ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Wedi methu darllen ffeil ZIP.',
|
||||
'import_zip_cant_decode_data' => 'Wedi methu ffeindio a dadgodio cynnwys ZIP data.json.',
|
||||
'import_zip_no_data' => 'Nid oes cynnwys llyfr, pennod neu dudalen disgwyliedig yn nata ffeil ZIP.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'ZIP mewnforyn wedi\'i methu dilysu gyda gwallau:',
|
||||
'import_zip_failed_notification' => 'Wedi methu mewnforio ffeil ZIP.',
|
||||
'import_perms_books' => 'Dych chi\'n methu\'r caniatâd gofynnol i greu llyfrau.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Nid oedd modd uwchlwytho’r ffeil. Efallai na fydd y gweinydd yn derbyn ffeiliau o\'r maint hwn.',
|
||||
|
||||
'zip_file' => 'Mae\'r :attribute angen cyfeirio at ffeil yn y ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'Mae\'r :attribute angen cyfeirio at ffeil o fath :valid Types, sydd wedi\'i ffeindio :foundType.',
|
||||
'zip_model_expected' => 'Dyswgyl am wrthrych data ond wedi ffeindio ":type".',
|
||||
'zip_unique' => 'Mae rhaid y :attribute fod yn unigol i\'r fath o wrthrych yn y ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Kunne ikke læse ZIP-filen.',
|
||||
'import_zip_cant_decode_data' => 'Kunne ikke finde og afkode ZIP data.json-indhold.',
|
||||
'import_zip_no_data' => 'ZIP-filens data har ikke noget forventet bog-, kapitel- eller sideindhold.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP kunne ikke valideres med fejl:',
|
||||
'import_zip_failed_notification' => 'Kunne ikke importere ZIP-fil.',
|
||||
'import_perms_books' => 'Du mangler de nødvendige tilladelser til at oprette bøger.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Filen kunne ikke oploades. Serveren accepterer muligvis ikke filer af denne størrelse.',
|
||||
|
||||
'zip_file' => 'Attributten skal henvise til en fil i ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'Attributten skal henvise til en fil af typen: validTypes, fundet:foundType.',
|
||||
'zip_model_expected' => 'Data objekt forventet men ":type" fundet.',
|
||||
'zip_unique' => 'Attributten skal være unik for objekttypen i ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.',
|
||||
'import_zip_cant_decode_data' => 'ZIP data.json konnte nicht gefunden und dekodiert werden.',
|
||||
'import_zip_no_data' => 'ZIP-Datei Daten haben kein erwartetes Buch, Kapitel oder Seiteninhalt.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'ZIP Import konnte mit Fehlern nicht validiert werden:',
|
||||
'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.',
|
||||
'import_perms_books' => 'Ihnen fehlt die erforderliche Berechtigung, um Bücher zu erstellen.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.',
|
||||
|
||||
'zip_file' => ':attribute muss eine Datei innerhalb des ZIP referenzieren.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => ':attribute muss eine Datei des Typs :validType referenzieren, gefunden :foundType.',
|
||||
'zip_model_expected' => 'Datenobjekt erwartet, aber ":type" gefunden.',
|
||||
'zip_unique' => ':attribute muss für den Objekttyp innerhalb des ZIP eindeutig sein.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'ZIP-Datei konnte nicht gelesen werden.',
|
||||
'import_zip_cant_decode_data' => 'Konnte Inhalt der data.json im ZIP nicht finden und dekodieren.',
|
||||
'import_zip_no_data' => 'ZIP-Datei hat kein erwartetes Buch, Kapitel oder Seiteninhalt.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'ZIP Import konnte aufgrund folgender Fehler nicht validiert werden:',
|
||||
'import_zip_failed_notification' => 'Importieren der ZIP-Datei fehlgeschlagen.',
|
||||
'import_perms_books' => 'Dir fehlt die erforderliche Berechtigung, um Bücher zu erstellen.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Die Datei konnte nicht hochgeladen werden. Der Server akzeptiert möglicherweise keine Dateien dieser Größe.',
|
||||
|
||||
'zip_file' => ':attribute muss auf eine Datei innerhalb des ZIP verweisen.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => ':attribute muss eine Datei des Typs :validType referenzieren, gefunden :foundType.',
|
||||
'zip_model_expected' => 'Datenobjekt erwartet, aber ":type" gefunden.',
|
||||
'zip_unique' => ':attribute muss für den Objekttyp innerhalb des ZIP eindeutig sein.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Δεν ήταν δυνατή η αποστολή του αρχείου. Ο διακομιστής ενδέχεται να μην δέχεται αρχεία αυτού του μεγέθους.',
|
||||
|
||||
'zip_file' => 'Το :attribute πρέπει να παραπέμπει σε ένα αρχείο εντός του ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'Το :attribute πρέπει να αναφέρεται σε αρχείο τύπου :validTypes, βρέθηκε :foundType.',
|
||||
'zip_model_expected' => 'Αναμενόταν αντικείμενο δεδομένων, αλλά ":type" βρέθηκε.',
|
||||
'zip_unique' => 'Το :attribute πρέπει να είναι μοναδικό για τον τύπο αντικειμένου εντός του ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'No se pudo leer el archivo ZIP.',
|
||||
'import_zip_cant_decode_data' => 'No se pudo encontrar y decodificar el archivo data.json. en el archivo ZIP.',
|
||||
'import_zip_no_data' => 'Los datos del archivo ZIP no contienen ningún libro, capítulo o contenido de página.',
|
||||
'import_zip_data_too_large' => 'El contenido del ZIP data.json excede el tamaño máximo de carga configurado.',
|
||||
'import_validation_failed' => 'Error al validar la importación del ZIP con errores:',
|
||||
'import_zip_failed_notification' => 'Error al importar archivo ZIP.',
|
||||
'import_perms_books' => 'Le faltan los permisos necesarios para crear libros.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'El archivo no ha podido subirse. Es posible que el servidor no acepte archivos de este tamaño.',
|
||||
|
||||
'zip_file' => 'El :attribute necesita hacer referencia a un archivo dentro del ZIP.',
|
||||
'zip_file_size' => 'El archivo :attribute no debe exceder :size MB.',
|
||||
'zip_file_mime' => 'El :attribute necesita hacer referencia a un archivo de tipo :validTypes, encontrado :foundType.',
|
||||
'zip_model_expected' => 'Se esperaba un objeto de datos, pero se encontró ":type".',
|
||||
'zip_unique' => 'El :attribute debe ser único para el tipo de objeto dentro del ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'No se pudo leer el archivo ZIP.',
|
||||
'import_zip_cant_decode_data' => 'No se pudo encontrar ni decodificar el contenido del archivo ZIP data.json.',
|
||||
'import_zip_no_data' => 'Los datos del archivo ZIP no tienen un libro, un capítulo o contenido de página en su contenido.',
|
||||
'import_zip_data_too_large' => 'El contenido del ZIP data.json excede el tamaño máximo de carga configurado.',
|
||||
'import_validation_failed' => 'Error al validar la importación del ZIP con los errores:',
|
||||
'import_zip_failed_notification' => 'Error al importar archivo ZIP.',
|
||||
'import_perms_books' => 'Le faltan los permisos necesarios para crear libros.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'El archivo no se pudo subir. Puede ser que el servidor no acepte archivos de este tamaño.',
|
||||
|
||||
'zip_file' => 'El :attribute necesita hacer referencia a un archivo dentro del ZIP.',
|
||||
'zip_file_size' => 'El archivo :attribute no debe exceder :size MB.',
|
||||
'zip_file_mime' => 'El :attribute necesita hacer referencia a un archivo de tipo :validTypes, encontrado :foundType.',
|
||||
'zip_model_expected' => 'Se esperaba un objeto de datos, pero se encontró ":type".',
|
||||
'zip_unique' => 'El :attribute debe ser único para el tipo de objeto dentro del ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'ZIP-faili lugemine ebaõnnestus.',
|
||||
'import_zip_cant_decode_data' => 'ZIP-failist ei leitud data.json sisu.',
|
||||
'import_zip_no_data' => 'ZIP-failist ei leitud raamatute, peatükkide või lehtede sisu.',
|
||||
'import_zip_data_too_large' => 'ZIP-faili data.json sisu ületab rakenduses seadistatud maksimaalse failisuuruse.',
|
||||
'import_validation_failed' => 'Imporditud ZIP-faili valideerimine ebaõnnestus vigadega:',
|
||||
'import_zip_failed_notification' => 'ZIP-faili importimine ebaõnnestus.',
|
||||
'import_perms_books' => 'Sul puuduvad õigused raamatute lisamiseks.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Faili üleslaadimine ebaõnnestus. Server ei pruugi sellise suurusega faile vastu võtta.',
|
||||
|
||||
'zip_file' => ':attribute peab viitama failile ZIP-arhiivi sees.',
|
||||
'zip_file_size' => 'Fail :attribute ei tohi olla suurem kui :size MB.',
|
||||
'zip_file_mime' => ':attribute peab viitama :validTypes tüüpi failile, leiti :foundType.',
|
||||
'zip_model_expected' => 'Oodatud andmete asemel leiti ":type".',
|
||||
'zip_unique' => ':attribute peab olema ZIP-arhiivi piires objekti tüübile unikaalne.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'امکان ایجاد کاربر وجود ندارد؛ زیرا ارسال ایمیل دعوت با خطا مواجه شد.',
|
||||
'import_zip_cant_decode_data' => 'محتوای data.json در فایل ZIP پیدا یا رمزگشایی نشد.',
|
||||
'import_zip_no_data' => 'دادههای فایل ZIP فاقد محتوای کتاب، فصل یا صفحه مورد انتظار است.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'اعتبارسنجی فایل ZIP واردشده با خطا مواجه شد:',
|
||||
'import_zip_failed_notification' => ' فایل ZIP وارد نشد.',
|
||||
'import_perms_books' => 'شما مجوز لازم برای ایجاد کتاب را ندارید.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'بارگذاری فایل :attribute موفقیت آمیز نبود.',
|
||||
|
||||
'zip_file' => 'ویژگی :attribute باید به یک فایل درون پرونده فشرده شده اشاره کند.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'ویژگی :attribute باید به فایلی با نوع :validTypes اشاره کند، اما نوع یافتشده :foundType است.',
|
||||
'zip_model_expected' => 'سیستم در این بخش انتظار دریافت یک شیء دادهای را داشت، اما «:type» دریافت گردید',
|
||||
'zip_unique' => 'برای هر نوع شیء در فایل ZIP، مقدار ویژگی :attribute باید یکتا و بدون تکرار باشد.',
|
||||
|
||||
@@ -110,7 +110,6 @@ Sovellus ei tunnista ulkoisen todennuspalvelun pyyntöä. Ongelman voi aiheuttaa
|
||||
'import_zip_cant_read' => 'ZIP-tiedostoa ei voitu lukea.',
|
||||
'import_zip_cant_decode_data' => 'ZIP-tiedoston data.json sisältöä ei löydy eikä sitä voitu purkaa.',
|
||||
'import_zip_no_data' => 'ZIP-tiedostoilla ei ole odotettua kirjaa, lukua tai sivun sisältöä.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Tuonti ZIP epäonnistui virheiden kanssa:',
|
||||
'import_zip_failed_notification' => 'ZIP-tiedoston tuominen epäonnistui.',
|
||||
'import_perms_books' => 'Sinulla ei ole tarvittavia oikeuksia luoda kirjoja.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Tiedostoa ei voitu ladata. Palvelin ei ehkä hyväksy tämän kokoisia tiedostoja.',
|
||||
|
||||
'zip_file' => 'Attribuutin :attribute on viitattava tiedostoon ZIP-tiedoston sisällä.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -54,7 +54,7 @@ return [
|
||||
'import_continue_desc' => 'Examinez le contenu à importer à partir du fichier ZIP téléchargé. Lorsque vous êtes prêt, lancez l\'importation pour ajouter son contenu à ce système. Le fichier d\'importation ZIP téléchargé sera automatiquement supprimé si l\'importation est réussie.',
|
||||
'import_details' => 'Détails de l\'importation',
|
||||
'import_run' => 'Exécuter Importation',
|
||||
'import_size' => ':size Taille du fichier ZIP à importer',
|
||||
'import_size' => ':size taille du ZIP d\'import',
|
||||
'import_uploaded_at' => ':relativeTime téléchargé',
|
||||
'import_uploaded_by' => 'Téléchargé par',
|
||||
'import_location' => 'Emplacement de l\'importation',
|
||||
@@ -330,13 +330,13 @@ return [
|
||||
|
||||
// Editor Sidebar
|
||||
'toggle_sidebar' => 'Afficher/masquer la barre latérale',
|
||||
'page_tags' => 'Étiquettes de la page',
|
||||
'chapter_tags' => 'Étiquettes du chapitre',
|
||||
'book_tags' => 'Étiquettes du livre',
|
||||
'shelf_tags' => 'Étiquettes de l\'étagère',
|
||||
'tag' => 'Étiquette',
|
||||
'tags' => 'Étiquettes',
|
||||
'tags_index_desc' => 'Les étiquettes peuvent être mises sur le contenu pour appliquer une forme flexible de catégorisation. Les étiquettes peuvent avoir à la fois une clé et une valeur, la valeur étant facultative. Une fois appliqué, le contenu peut ensuite être interrogé à l’aide du nom et de la valeur de l’étiquette.',
|
||||
'page_tags' => 'Mots-clés de la page',
|
||||
'chapter_tags' => 'Mots-clés du chapitre',
|
||||
'book_tags' => 'Mots-clés du livre',
|
||||
'shelf_tags' => 'Mots-clés de l\'étagère',
|
||||
'tag' => 'Mot-clé',
|
||||
'tags' => 'Mots-clés',
|
||||
'tags_index_desc' => 'Les tags peuvent être appliqués au contenu du système pour appliquer une forme flexible de catégorisation. Les tags peuvent avoir à la fois une clé et une valeur, la valeur étant facultative. Une fois appliqué, le contenu peut ensuite être interrogé à l’aide du nom et de la valeur du tag.',
|
||||
'tag_name' => 'Nom de l’étiquette',
|
||||
'tag_value' => 'Valeur du mot-clé (optionnel)',
|
||||
'tags_explain' => "Ajouter des mots-clés pour catégoriser votre contenu.",
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Impossible de lire le fichier ZIP.',
|
||||
'import_zip_cant_decode_data' => 'Impossible de trouver et de décoder le contenu ZIP data.json.',
|
||||
'import_zip_no_data' => 'Les données du fichier ZIP n\'ont pas de livre, de chapitre ou de page attendus.',
|
||||
'import_zip_data_too_large' => 'Le contenu du fichier ZIP pour data.json dépasse la taille maximale de téléversement autorisée.',
|
||||
'import_validation_failed' => 'L\'importation du ZIP n\'a pas été validée avec les erreurs :',
|
||||
'import_zip_failed_notification' => 'Impossible d\'importer le fichier ZIP.',
|
||||
'import_perms_books' => 'Vous n\'avez pas les permissions requises pour créer des livres.',
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
*/
|
||||
return [
|
||||
|
||||
'new_comment_subject' => 'Nouveau commentaire sur la page : :pageName',
|
||||
'new_comment_subject' => 'Nouveau commentaire sur la page: :pageName',
|
||||
'new_comment_intro' => 'Un utilisateur a commenté une page dans :appName:',
|
||||
'new_page_subject' => 'Nouvelle page: :pageName',
|
||||
'new_page_intro' => 'Une nouvelle page a été créée dans :appName:',
|
||||
'updated_page_subject' => 'Page mise à jour: :pageName',
|
||||
'updated_page_intro' => 'Une page a été mise à jour dans :appName:',
|
||||
'updated_page_debounce' => 'Pour éviter de nombreuses notifications, pendant un certain temps, vous ne recevrez pas de notifications pour d\'autres modifications de cette page par le même éditeur.',
|
||||
'comment_mention_subject' => 'Vous avez été mentionné dans un commentaire sur la page : :pageName',
|
||||
'comment_mention_intro' => 'Vous avez été mentionné dans un commentaire sur :appName:',
|
||||
'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
|
||||
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
|
||||
|
||||
'detail_page_name' => 'Nom de la page :',
|
||||
'detail_page_path' => 'Chemin de la page :',
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'notifications_desc' => 'Contrôlez les notifications par e-mail que vous recevez lorsque certaines activités sont effectuées dans le système.',
|
||||
'notifications_opt_own_page_changes' => 'Notifier lors des modifications des pages que je possède',
|
||||
'notifications_opt_own_page_comments' => 'Notifier lorsque les pages que je possède sont commentées',
|
||||
'notifications_opt_comment_mentions' => 'Notifier lorsque je suis mentionné dans un commentaire',
|
||||
'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
|
||||
'notifications_opt_comment_replies' => 'Notifier les réponses à mes commentaires',
|
||||
'notifications_save' => 'Enregistrer les préférences',
|
||||
'notifications_update_success' => 'Les préférences de notification ont été mises à jour !',
|
||||
|
||||
@@ -17,14 +17,14 @@ return [
|
||||
'app_features_security' => 'Fonctionnalités et sécurité',
|
||||
'app_name' => 'Nom de l\'application',
|
||||
'app_name_desc' => 'Ce nom est affiché dans l\'en-tête et les e-mails.',
|
||||
'app_name_header' => 'Afficher le nom dans l\'en-tête',
|
||||
'app_name_header' => 'Afficher le nom dans l\'en-tête ?',
|
||||
'app_public_access' => 'Accès public',
|
||||
'app_public_access_desc' => 'L\'activation de cette option permettra aux visiteurs, qui ne sont pas connectés, d\'accéder au contenu de votre instance BookStack.',
|
||||
'app_public_access_desc_guest' => 'L\'accès pour les visiteurs publics peut être contrôlé par l\'utilisateur "Guest".',
|
||||
'app_public_access_toggle' => 'Autoriser l\'accès public',
|
||||
'app_public_viewing' => 'Accepter l\'affichage public des pages ?',
|
||||
'app_secure_images' => 'Ajout d\'image sécurisé',
|
||||
'app_secure_images_toggle' => 'Activer l\'ajout d\'image sécurisée',
|
||||
'app_secure_images_toggle' => 'Activer l\'ajout d\'image sécurisé',
|
||||
'app_secure_images_desc' => 'Pour des questions de performances, toutes les images sont publiques. Cette option ajoute une chaîne aléatoire difficile à deviner dans les URLs des images.',
|
||||
'app_default_editor' => 'Éditeur de page par défaut',
|
||||
'app_default_editor_desc' => 'Sélectionnez l\'éditeur qui sera utilisé par défaut lors de l\'édition de nouvelles pages. Cela peut être remplacé au niveau de la page où les permissions sont autorisées.',
|
||||
@@ -75,8 +75,8 @@ return [
|
||||
'reg_confirm_restrict_domain_placeholder' => 'Aucune restriction en place',
|
||||
|
||||
// Sorting Settings
|
||||
'sorting' => 'Listes et tri',
|
||||
'sorting_book_default' => 'Tri des livres par défaut',
|
||||
'sorting' => 'Lists & Sorting',
|
||||
'sorting_book_default' => 'Default Book Sort Rule',
|
||||
'sorting_book_default_desc' => 'Sélectionnez le tri par défaut à mettre en place sur les nouveaux livres. Cela n’affectera pas les livres existants, et peut être redéfini dans les livres.',
|
||||
'sorting_rules' => 'Règles de tri',
|
||||
'sorting_rules_desc' => 'Ce sont les opérations de tri qui peuvent être appliquées au contenu du système.',
|
||||
@@ -103,8 +103,8 @@ return [
|
||||
'sort_rule_op_updated_date' => 'Date de mise à jour',
|
||||
'sort_rule_op_chapters_first' => 'Chapitres en premier',
|
||||
'sort_rule_op_chapters_last' => 'Chapitres en dernier',
|
||||
'sorting_page_limits' => 'Limite d\'affichage par page',
|
||||
'sorting_page_limits_desc' => 'Définissez le nombre d’éléments à afficher par page dans les différentes listes du système. En général, un nombre plus faible offre de meilleures performances, tandis qu’un nombre plus élevé réduit le besoin de naviguer entre plusieurs pages. Il est recommandé d’utiliser un multiple pair de 3 (18, 24, 30, etc.).',
|
||||
'sorting_page_limits' => 'Per-Page Display Limits',
|
||||
'sorting_page_limits_desc' => 'Set how many items to show per-page in various lists within the system. Typically a lower amount will be more performant, while a higher amount avoids the need to click through multiple pages. Using an even multiple of 3 (18, 24, 30, etc...) is recommended.',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'Maintenance',
|
||||
@@ -197,13 +197,13 @@ return [
|
||||
'role_import_content' => 'Importer le contenu',
|
||||
'role_editor_change' => 'Changer l\'éditeur de page',
|
||||
'role_notifications' => 'Recevoir et gérer les notifications',
|
||||
'role_permission_note_users_and_roles' => 'Ces autorisations permettront également l\'accès à la consultation et la recherche des utilisateurs et des rôles dans le système.',
|
||||
'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.',
|
||||
'role_asset' => 'Permissions des ressources',
|
||||
'roles_system_warning' => 'Sachez que l\'accès à l\'une des trois permissions ci-dessus peut permettre à un utilisateur de modifier ses propres privilèges ou les privilèges des autres utilisateurs du système. N\'attribuez uniquement des rôles avec ces permissions qu\'à des utilisateurs de confiance.',
|
||||
'role_asset_desc' => 'Ces permissions contrôlent l\'accès par défaut des ressources dans le système. Les permissions dans les livres, les chapitres et les pages ignoreront ces permissions',
|
||||
'role_asset_admins' => 'Les administrateurs ont automatiquement accès à tous les contenus mais les options suivantes peuvent afficher ou masquer certaines options de l\'interface.',
|
||||
'role_asset_image_view_note' => 'Cela concerne la visibilité dans le gestionnaire d\'images. L\'accès réel des fichiers d\'image téléchargés dépendra de l\'option de stockage d\'images du système.',
|
||||
'role_asset_users_note' => 'Ces autorisations permettront également l\'accès à la consultation et la recherche des utilisateurs dans le système.',
|
||||
'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.',
|
||||
'role_all' => 'Tous',
|
||||
'role_own' => 'Propres',
|
||||
'role_controlled_by_asset' => 'Contrôlé par les ressources les ayant envoyés',
|
||||
@@ -270,7 +270,7 @@ return [
|
||||
'user_api_token_name_desc' => 'Donnez à votre jeton un nom lisible pour l\'identifier plus tard.',
|
||||
'user_api_token_expiry' => 'Date d\'expiration',
|
||||
'user_api_token_expiry_desc' => 'Définissez une date à laquelle ce jeton expire. Après cette date, les demandes effectuées à l\'aide de ce jeton ne fonctionneront plus. Le fait de laisser ce champ vide entraînera une expiration dans 100 ans.',
|
||||
'user_api_token_create_secret_message' => 'Immédiatement après la création de ce jeton, un "ID de jeton" et "Secret de jeton" sera généré et affiché. Le secret ne sera affiché qu\'une seule fois, alors assurez-vous de copier la valeur dans un endroit sûr et sécurisé avant de continuer.',
|
||||
'user_api_token_create_secret_message' => 'Immédiatement après la création de ce jeton, un "ID de jeton" "et" Secret de jeton "sera généré et affiché. Le secret ne sera affiché qu\'une seule fois, alors assurez-vous de copier la valeur dans un endroit sûr et sécurisé avant de continuer.',
|
||||
'user_api_token' => 'Jeton API',
|
||||
'user_api_token_id' => 'Token ID',
|
||||
'user_api_token_id_desc' => 'Il s\'agit d\'un identifiant généré par le système non modifiable pour ce jeton qui devra être fourni dans les demandes d\'API.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Le fichier n\'a pas pu être envoyé. Le serveur peut ne pas accepter des fichiers de cette taille.',
|
||||
|
||||
'zip_file' => 'L\'attribut :attribute doit référencer un fichier dans le ZIP.',
|
||||
'zip_file_size' => 'Le fichier :attribute ne doit pas dépasser :size Mo.',
|
||||
'zip_file_mime' => ':attribute doit référencer un fichier de type :validTypes, trouvé :foundType.',
|
||||
'zip_model_expected' => 'Objet de données attendu, mais ":type" trouvé.',
|
||||
'zip_unique' => 'L\'attribut :attribute doit être unique pour le type d\'objet dans le ZIP.',
|
||||
|
||||
@@ -128,12 +128,12 @@ return [
|
||||
'comment_delete' => 'תגובה נמחקה',
|
||||
|
||||
// Sort Rules
|
||||
'sort_rule_create' => 'נוצר חוק מיון',
|
||||
'sort_rule_create_notification' => 'חוק מיון נוצר בהצלחה',
|
||||
'sort_rule_update' => 'חוק מיון עודכן',
|
||||
'sort_rule_update_notification' => 'חוק מיון עודכן בהצלחה',
|
||||
'sort_rule_delete' => 'חוק מיון נמחק',
|
||||
'sort_rule_delete_notification' => 'חוק מיון נמחק בהצלחה',
|
||||
'sort_rule_create' => 'created sort rule',
|
||||
'sort_rule_create_notification' => 'Sort rule successfully created',
|
||||
'sort_rule_update' => 'updated sort rule',
|
||||
'sort_rule_update_notification' => 'Sort rule successfully updated',
|
||||
'sort_rule_delete' => 'deleted sort rule',
|
||||
'sort_rule_delete_notification' => 'Sort rule successfully deleted',
|
||||
|
||||
// Other
|
||||
'permissions_update' => 'הרשאות עודכנו',
|
||||
|
||||
@@ -106,13 +106,12 @@ return [
|
||||
'mfa_verify_access' => 'אשר גישה',
|
||||
'mfa_verify_access_desc' => 'חשבון המשתמש שלך דורש ממך לאת את הזהות שלך בשכבת הגנה נוספת על מנת לאפשר לך גישה. יש לאשר גישה דרך אחד האמצעים הקיימים על מנת להמשיך.',
|
||||
'mfa_verify_no_methods' => 'אין אפשרויות אימות דו-שלבי מוגדרות',
|
||||
'mfa_verify_no_methods_desc' => 'לא נמצאו אפשרויות ווידוא זהות עבור המשתמש שלך.
|
||||
נדרש לקנפג לפחות אחד על מנת לקבל גישה.',
|
||||
'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.',
|
||||
'mfa_verify_use_totp' => 'אמת באמצעות אפליקציה',
|
||||
'mfa_verify_use_backup_codes' => 'אמת באמצעות קוד גיבוי',
|
||||
'mfa_verify_backup_code' => 'קוד גיבוי',
|
||||
'mfa_verify_backup_code_desc' => 'הזן מטה אחד מקודי הגיבוי הנותרים לך:',
|
||||
'mfa_verify_backup_code_enter_here' => 'הזן קוד גיבוי כאן',
|
||||
'mfa_verify_totp_desc' => 'הזן את הקוד, שהונפק דרך האפליקציה שלך, מטה:',
|
||||
'mfa_setup_login_notification' => 'אמצעי זיהוי זהות הוגדרו, אנא התחבר מחדש.',
|
||||
'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.',
|
||||
];
|
||||
|
||||
@@ -30,8 +30,8 @@ return [
|
||||
'create' => 'צור',
|
||||
'update' => 'עדכן',
|
||||
'edit' => 'ערוך',
|
||||
'archive' => 'הכנס לארכיון',
|
||||
'unarchive' => 'הוצא מארכיון',
|
||||
'archive' => 'Archive',
|
||||
'unarchive' => 'Un-Archive',
|
||||
'sort' => 'מיין',
|
||||
'move' => 'הזז',
|
||||
'copy' => 'העתק',
|
||||
|
||||
@@ -22,8 +22,8 @@ return [
|
||||
'meta_created_name' => 'נוצר :timeLength על ידי :user',
|
||||
'meta_updated' => 'עודכן :timeLength',
|
||||
'meta_updated_name' => 'עודכן :timeLength על ידי :user',
|
||||
'meta_owned_name' => 'בבעלות של :user',
|
||||
'meta_reference_count' => '',
|
||||
'meta_owned_name' => 'Owned by :user',
|
||||
'meta_reference_count' => 'Referenced by :count item|Referenced by :count items',
|
||||
'entity_select' => 'בחר יישות',
|
||||
'entity_select_lack_permission' => 'אין לך אישורים דרושים לבחירת פריט זה',
|
||||
'images' => 'תמונות',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'שדה :attribute ארעה שגיאה בעת ההעלאה.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Datoteka se ne može prenijeti. Server možda ne prihvaća datoteke te veličine.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -85,12 +85,12 @@ return [
|
||||
'webhook_delete_notification' => 'Webhook sikeresen törölve',
|
||||
|
||||
// Imports
|
||||
'import_create' => 'import elkészült',
|
||||
'import_create_notification' => 'Az import sikeresen feltöltötve',
|
||||
'import_run' => 'import frissítve',
|
||||
'import_run_notification' => 'A tartalmat sikeresen importáltam.',
|
||||
'import_delete' => 'import törölve',
|
||||
'import_delete_notification' => 'Az import sikeresen törölve',
|
||||
'import_create' => 'created import',
|
||||
'import_create_notification' => 'Import successfully uploaded',
|
||||
'import_run' => 'updated import',
|
||||
'import_run_notification' => 'Content successfully imported',
|
||||
'import_delete' => 'deleted import',
|
||||
'import_delete_notification' => 'Import successfully deleted',
|
||||
|
||||
// Users
|
||||
'user_create' => 'létrehozta a felhasználót',
|
||||
|
||||
@@ -30,8 +30,8 @@ return [
|
||||
'create' => 'Létrehozás',
|
||||
'update' => 'Frissítés',
|
||||
'edit' => 'Szerkesztés',
|
||||
'archive' => 'Archiválás',
|
||||
'unarchive' => 'Archiválás visszavonása',
|
||||
'archive' => 'Archive',
|
||||
'unarchive' => 'Un-Archive',
|
||||
'sort' => 'Rendezés',
|
||||
'move' => 'Áthelyezés',
|
||||
'copy' => 'Másolás',
|
||||
@@ -111,5 +111,5 @@ return [
|
||||
'terms_of_service' => 'Felhasználási feltételek',
|
||||
|
||||
// OpenSearch
|
||||
'opensearch_description' => 'Keresés :appName',
|
||||
'opensearch_description' => 'Search :appName',
|
||||
];
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Could not read ZIP file.',
|
||||
'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.',
|
||||
'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Import ZIP failed to validate with errors:',
|
||||
'import_zip_failed_notification' => 'Failed to import ZIP file.',
|
||||
'import_perms_books' => 'You are lacking the required permissions to create books.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'A fájlt nem lehet feltölteni. A kiszolgáló nem fogad el ilyen méretű fájlokat.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -13,7 +13,7 @@ return [
|
||||
'image_intro_upload' => 'Unggah gambar baru dengan menyeret berkas gambar ke jendela ini, atau dengan menggunakan tombol "Unggah Gambar" di atas.',
|
||||
'image_all' => 'Semua',
|
||||
'image_all_title' => 'Lihat semua gambar',
|
||||
'image_book_title' => 'Lihat gambar untuk diunggah ke buku ini',
|
||||
'image_book_title' => 'Lihat gambar yang diunggah ke buku ini',
|
||||
'image_page_title' => 'Lihat gambar yang diunggah ke halaman ini',
|
||||
'image_search_hint' => 'Cari berdasarkan nama gambar',
|
||||
'image_uploaded' => 'Diunggah :uploadedDate',
|
||||
@@ -33,7 +33,7 @@ return [
|
||||
'image_update_success' => 'Detail gambar berhasil diperbarui',
|
||||
'image_delete_success' => 'Gambar berhasil dihapus',
|
||||
'image_replace' => 'Ganti Gambar',
|
||||
'image_replace_success' => 'Detail gambar berhasil diperbarui',
|
||||
'image_replace_success' => 'Berkas gambar berhasil diperbarui',
|
||||
'image_rebuild_thumbs' => 'Buat Ulang Variasi Ukuran',
|
||||
'image_rebuild_thumbs_success' => 'Variasi ukuran gambar berhasil dibuat ulang!',
|
||||
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Tidak dapat membaca berkas ZIP.',
|
||||
'import_zip_cant_decode_data' => 'Tidak dapat menemukan dan mendekode konten ZIP data.json.',
|
||||
'import_zip_no_data' => 'Data berkas ZIP tidak berisi konten buku, bab, atau halaman yang diharapkan.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'Impor ZIP gagal divalidasi dengan kesalahan:',
|
||||
'import_zip_failed_notification' => 'Gagal mengimpor berkas ZIP.',
|
||||
'import_perms_books' => 'Anda tidak memiliki izin yang diperlukan untuk membuat buku.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'Berkas tidak dapat diunggah. Server mungkin tidak menerima berkas dengan ukuran ini.',
|
||||
|
||||
'zip_file' => ':attribute perlu merujuk ke sebuah file yang terdapat di dalam arsip ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => ':attribute seharusnya berupa file dengan tipe :validTypes, tapi yang Anda unggah bertipe :foundType.',
|
||||
'zip_model_expected' => 'Diharapkan sebuah objek data, namun yang ditemukan adalah \':type\'.',
|
||||
'zip_unique' => ':attribute harus bersifat unik untuk setiap jenis objek dalam file ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Gat ekki lesið ZIP skrá.',
|
||||
'import_zip_cant_decode_data' => 'Fann ekki ZIP data.json innihald.',
|
||||
'import_zip_no_data' => 'ZIP skráin inniheldur ekkert efni.',
|
||||
'import_zip_data_too_large' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'import_validation_failed' => 'ZIP skráin stóðst ekki staðfestingu og skilaði villu:',
|
||||
'import_zip_failed_notification' => 'Gat ekki lesið inn ZIP skrá.',
|
||||
'import_perms_books' => 'Þú hefur ekki heimild til að búa til bækur.',
|
||||
|
||||
@@ -106,7 +106,6 @@ return [
|
||||
'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.',
|
||||
|
||||
'zip_file' => 'The :attribute needs to reference a file within the ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'The :attribute needs to reference a file of type :validTypes, found :foundType.',
|
||||
'zip_model_expected' => 'Data object expected but ":type" found.',
|
||||
'zip_unique' => 'The :attribute must be unique for the object type within the ZIP.',
|
||||
|
||||
@@ -109,7 +109,6 @@ return [
|
||||
'import_zip_cant_read' => 'Impossibile leggere il file ZIP.',
|
||||
'import_zip_cant_decode_data' => 'Impossibile trovare e decodificare il contenuto ZIP data.json.',
|
||||
'import_zip_no_data' => 'I dati del file ZIP non hanno il contenuto previsto di libri, capitoli o pagine.',
|
||||
'import_zip_data_too_large' => 'Il contenuto ZIP data.json supera la dimensione massima di upload configurata nell\'applicazione.',
|
||||
'import_validation_failed' => 'L\'importazione ZIP non è stata convalidata con errori:',
|
||||
'import_zip_failed_notification' => 'Impossibile importare il file ZIP.',
|
||||
'import_perms_books' => 'Non hai i permessi necessari per creare libri.',
|
||||
|
||||
@@ -11,8 +11,8 @@ return [
|
||||
'updated_page_subject' => 'Pagina aggiornata: :pageName',
|
||||
'updated_page_intro' => 'Una pagina è stata aggiornata in :appName:',
|
||||
'updated_page_debounce' => 'Per evitare una massa di notifiche, per un po\' non ti verranno inviate notifiche per ulteriori modifiche a questa pagina dallo stesso editor.',
|
||||
'comment_mention_subject' => 'Sei stato menzionato in un commento nella pagina: :pageName',
|
||||
'comment_mention_intro' => 'Sei stato menzionato in un commento su :appName:',
|
||||
'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
|
||||
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
|
||||
|
||||
'detail_page_name' => 'Nome della pagina:',
|
||||
'detail_page_path' => 'Percorso della pagina:',
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'notifications_desc' => 'Controlla le notifiche email che ricevi quando viene eseguita una determinata attività all\'interno del sistema.',
|
||||
'notifications_opt_own_page_changes' => 'Notifica in caso di modifiche alle pagine che possiedo',
|
||||
'notifications_opt_own_page_comments' => 'Notifica i commenti sulle pagine che possiedo',
|
||||
'notifications_opt_comment_mentions' => 'Avvisami quando vengo menzionato in un commento',
|
||||
'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
|
||||
'notifications_opt_comment_replies' => 'Notificare le risposte ai miei commenti',
|
||||
'notifications_save' => 'Salva preferenze',
|
||||
'notifications_update_success' => 'Le preferenze di notifica sono state aggiornate!',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user