mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-06 00:59:39 +03:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e0edb63c7 | ||
|
|
20db372596 | ||
|
|
43eed1660c | ||
|
|
e6b754fad0 | ||
|
|
018de5def3 | ||
|
|
5c4fc3dc2c | ||
|
|
07ec880e33 | ||
|
|
ab436ed5c3 | ||
|
|
082befb2fc | ||
|
|
b0a8cb0c5d | ||
|
|
b08d1b36de | ||
|
|
88d86df66f | ||
|
|
bb08f62327 | ||
|
|
8eef5a1ee7 | ||
|
|
88ccd9e5b9 | ||
|
|
2c3100e401 | ||
|
|
54f883e815 | ||
|
|
e611b3239e | ||
|
|
b9ecf55e1f | ||
|
|
2d5548240a |
2
.github/translators.txt
vendored
2
.github/translators.txt
vendored
@@ -519,3 +519,5 @@ 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
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2025, Dan Brown and the BookStack project contributors.
|
||||
Copyright (c) 2015-2026, 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
|
||||
|
||||
@@ -22,6 +22,18 @@ return [
|
||||
// Callback URL for social authentication methods
|
||||
'callback_url' => env('APP_URL', false),
|
||||
|
||||
// LLM Service
|
||||
// Options: openai
|
||||
'llm' => env('LLM_SERVICE', ''),
|
||||
|
||||
// OpenAI API-compatible service details
|
||||
'openai' => [
|
||||
'endpoint' => env('OPENAI_ENDPOINT', 'https://api.openai.com'),
|
||||
'key' => env('OPENAI_KEY', ''),
|
||||
'embedding_model' => env('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
|
||||
'query_model' => env('OPENAI_QUERY_MODEL', 'gpt-4o'),
|
||||
],
|
||||
|
||||
'github' => [
|
||||
'client_id' => env('GITHUB_APP_ID', false),
|
||||
'client_secret' => env('GITHUB_APP_SECRET', false),
|
||||
|
||||
46
app/Console/Commands/RegenerateVectorsCommand.php
Normal file
46
app/Console/Commands/RegenerateVectorsCommand.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Search\Queries\SearchVector;
|
||||
use BookStack\Search\Queries\StoreEntityVectorsJob;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RegenerateVectorsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:regenerate-vectors';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Re-index vectors for all content in the system';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(EntityProvider $entityProvider)
|
||||
{
|
||||
// TODO - Add confirmation before run regarding deletion/time/effort/api-cost etc...
|
||||
SearchVector::query()->delete();
|
||||
|
||||
$types = $entityProvider->all();
|
||||
foreach ($types as $type => $typeInstance) {
|
||||
$this->info("Creating jobs to store vectors for {$type} data...");
|
||||
/** @var Entity[] $entities */
|
||||
$typeInstance->newQuery()->chunkById(100, function ($entities) {
|
||||
foreach ($entities as $entity) {
|
||||
dispatch(new StoreEntityVectorsJob($entity));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,16 @@ 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);
|
||||
@@ -73,6 +83,17 @@ 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,7 +13,6 @@ class ZipFileReferenceRule implements ValidationRule
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -23,6 +22,13 @@ 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,6 +265,12 @@ 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');
|
||||
|
||||
89
app/Search/Queries/EntityVectorGenerator.php
Normal file
89
app/Search/Queries/EntityVectorGenerator.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Activity\Models\Tag;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Search\Queries\Services\VectorQueryService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EntityVectorGenerator
|
||||
{
|
||||
public function __construct(
|
||||
protected VectorQueryServiceProvider $vectorQueryServiceProvider
|
||||
) {
|
||||
}
|
||||
|
||||
public function generateAndStore(Entity $entity): void
|
||||
{
|
||||
$vectorService = $this->vectorQueryServiceProvider->get();
|
||||
|
||||
$text = $this->entityToPlainText($entity);
|
||||
$chunks = $this->chunkText($text);
|
||||
$embeddings = $this->chunksToEmbeddings($chunks, $vectorService);
|
||||
|
||||
$this->deleteExistingEmbeddingsForEntity($entity);
|
||||
$this->storeEmbeddings($embeddings, $chunks, $entity);
|
||||
}
|
||||
|
||||
protected function deleteExistingEmbeddingsForEntity(Entity $entity): void
|
||||
{
|
||||
SearchVector::query()
|
||||
->where('entity_type', '=', $entity->getMorphClass())
|
||||
->where('entity_id', '=', $entity->id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
protected function storeEmbeddings(array $embeddings, array $textChunks, Entity $entity): void
|
||||
{
|
||||
$toInsert = [];
|
||||
|
||||
foreach ($embeddings as $index => $embedding) {
|
||||
$text = $textChunks[$index];
|
||||
$toInsert[] = [
|
||||
'entity_id' => $entity->id,
|
||||
'entity_type' => $entity->getMorphClass(),
|
||||
'embedding' => DB::raw('VEC_FROMTEXT("[' . implode(',', $embedding) . ']")'),
|
||||
'text' => $text,
|
||||
];
|
||||
}
|
||||
|
||||
$chunks = array_chunk($toInsert, 500);
|
||||
foreach ($chunks as $chunk) {
|
||||
SearchVector::query()->insert($chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $chunks
|
||||
* @return float[] array
|
||||
*/
|
||||
protected function chunksToEmbeddings(array $chunks, VectorQueryService $vectorQueryService): array
|
||||
{
|
||||
$embeddings = [];
|
||||
foreach ($chunks as $index => $chunk) {
|
||||
$embeddings[$index] = $vectorQueryService->generateEmbeddings($chunk);
|
||||
}
|
||||
return $embeddings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function chunkText(string $text): array
|
||||
{
|
||||
return (new TextChunker(500, ["\n", '.', ' ', '']))->chunk($text);
|
||||
}
|
||||
|
||||
protected function entityToPlainText(Entity $entity): string
|
||||
{
|
||||
$tags = $entity->tags()->get();
|
||||
$tagText = $tags->map(function (Tag $tag) {
|
||||
return $tag->name . ': ' . $tag->value;
|
||||
})->join('\n');
|
||||
|
||||
return $entity->name . "\n{$tagText}\n" . $entity->{$entity->textField};
|
||||
}
|
||||
}
|
||||
26
app/Search/Queries/LlmQueryRunner.php
Normal file
26
app/Search/Queries/LlmQueryRunner.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use Exception;
|
||||
|
||||
class LlmQueryRunner
|
||||
{
|
||||
public function __construct(
|
||||
protected VectorQueryServiceProvider $vectorQueryServiceProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a query against the configured LLM to produce a text response.
|
||||
* @param VectorSearchResult[] $vectorResults
|
||||
* @throws Exception
|
||||
*/
|
||||
public function run(string $query, array $vectorResults): string
|
||||
{
|
||||
$queryService = $this->vectorQueryServiceProvider->get();
|
||||
|
||||
$matchesText = array_values(array_map(fn (VectorSearchResult $result) => $result->matchText, $vectorResults));
|
||||
return $queryService->query($query, $matchesText);
|
||||
}
|
||||
}
|
||||
61
app/Search/Queries/QueryController.php
Normal file
61
app/Search/Queries/QueryController.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Search\SearchRunner;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class QueryController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected SearchRunner $searchRunner,
|
||||
) {
|
||||
// TODO - Check via testing
|
||||
$this->middleware(function ($request, $next) {
|
||||
if (!VectorQueryServiceProvider::isEnabled()) {
|
||||
$this->showPermissionError('/');
|
||||
}
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the view to start a vector/LLM-based query search.
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$query = $request->get('ask', '');
|
||||
|
||||
// TODO - Set page title
|
||||
|
||||
return view('search.query', [
|
||||
'query' => $query,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a vector/LLM-based query search.
|
||||
*/
|
||||
public function run(Request $request, VectorSearchRunner $searchRunner, LlmQueryRunner $llmRunner)
|
||||
{
|
||||
// TODO - Rate limiting
|
||||
$query = $request->get('query', '');
|
||||
|
||||
return response()->eventStream(function () use ($query, $searchRunner, $llmRunner) {
|
||||
$results = $query ? $searchRunner->run($query) : [];
|
||||
|
||||
$entities = [];
|
||||
foreach ($results as $result) {
|
||||
$entityKey = $result->entity->getMorphClass() . ':' . $result->entity->id;
|
||||
if (!isset($entities[$entityKey])) {
|
||||
$entities[$entityKey] = $result->entity;
|
||||
}
|
||||
}
|
||||
|
||||
yield ['view' => view('entities.list', ['entities' => $entities])->render()];
|
||||
|
||||
yield ['result' => $llmRunner->run($query, $results)];
|
||||
});
|
||||
}
|
||||
}
|
||||
26
app/Search/Queries/SearchVector.php
Normal file
26
app/Search/Queries/SearchVector.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Permissions\Models\JointPermission;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property string $entity_type
|
||||
* @property int $entity_id
|
||||
* @property string $text
|
||||
* @property string $embedding
|
||||
*/
|
||||
class SearchVector extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
public function jointPermissions(): HasMany
|
||||
{
|
||||
return $this->hasMany(JointPermission::class, 'entity_id', 'entity_id')
|
||||
->whereColumn('search_vectors.entity_type', '=', 'joint_permissions.entity_type');
|
||||
}
|
||||
}
|
||||
66
app/Search/Queries/Services/OpenAiVectorQueryService.php
Normal file
66
app/Search/Queries/Services/OpenAiVectorQueryService.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Search\Queries\Services;
|
||||
|
||||
use BookStack\Http\HttpRequestService;
|
||||
|
||||
class OpenAiVectorQueryService implements VectorQueryService
|
||||
{
|
||||
protected string $key;
|
||||
protected string $endpoint;
|
||||
protected string $embeddingModel;
|
||||
protected string $queryModel;
|
||||
|
||||
public function __construct(
|
||||
protected array $options,
|
||||
protected HttpRequestService $http,
|
||||
) {
|
||||
// TODO - Some kind of validation of options
|
||||
$this->key = $this->options['key'] ?? '';
|
||||
$this->endpoint = $this->options['endpoint'] ?? '';
|
||||
$this->embeddingModel = $this->options['embedding_model'] ?? '';
|
||||
$this->queryModel = $this->options['query_model'] ?? '';
|
||||
}
|
||||
|
||||
protected function jsonRequest(string $method, string $uri, array $data): array
|
||||
{
|
||||
$fullUrl = rtrim($this->endpoint, '/') . '/' . ltrim($uri, '/');
|
||||
$client = $this->http->buildClient(30);
|
||||
$request = $this->http->jsonRequest($method, $fullUrl, $data)
|
||||
->withHeader('Authorization', 'Bearer ' . $this->key);
|
||||
|
||||
$response = $client->sendRequest($request);
|
||||
return json_decode($response->getBody()->getContents(), true);
|
||||
}
|
||||
|
||||
public function generateEmbeddings(string $text): array
|
||||
{
|
||||
$response = $this->jsonRequest('POST', 'v1/embeddings', [
|
||||
'input' => $text,
|
||||
'model' => $this->embeddingModel,
|
||||
]);
|
||||
|
||||
return $response['data'][0]['embedding'];
|
||||
}
|
||||
|
||||
public function query(string $input, array $context): string
|
||||
{
|
||||
$formattedContext = implode("\n", $context);
|
||||
|
||||
$response = $this->jsonRequest('POST', 'v1/chat/completions', [
|
||||
'model' => $this->queryModel,
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'developer',
|
||||
'content' => 'You are a helpful assistant providing search query responses. Be specific, factual and to-the-point in response. Don\'t try to converse or continue the conversation.'
|
||||
],
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => "Provide a response to the below given QUERY using the below given CONTEXT. The CONTEXT is split into parts via lines. Ignore any nonsensical lines of CONTEXT.\nQUERY: {$input}\n\nCONTEXT: {$formattedContext}",
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
return $response['choices'][0]['message']['content'] ?? '';
|
||||
}
|
||||
}
|
||||
21
app/Search/Queries/Services/VectorQueryService.php
Normal file
21
app/Search/Queries/Services/VectorQueryService.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Search\Queries\Services;
|
||||
|
||||
interface VectorQueryService
|
||||
{
|
||||
/**
|
||||
* Generate embedding vectors from the given chunk of text.
|
||||
* @return float[]
|
||||
*/
|
||||
public function generateEmbeddings(string $text): array;
|
||||
|
||||
/**
|
||||
* Query the LLM service using the given user input, and
|
||||
* relevant context text retrieved locally via a vector search.
|
||||
* Returns the response output text from the LLM.
|
||||
*
|
||||
* @param string[] $context
|
||||
*/
|
||||
public function query(string $input, array $context): string;
|
||||
}
|
||||
30
app/Search/Queries/StoreEntityVectorsJob.php
Normal file
30
app/Search/Queries/StoreEntityVectorsJob.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class StoreEntityVectorsJob implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(
|
||||
protected Entity $entity
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(EntityVectorGenerator $generator): void
|
||||
{
|
||||
$generator->generateAndStore($this->entity);
|
||||
}
|
||||
}
|
||||
79
app/Search/Queries/TextChunker.php
Normal file
79
app/Search/Queries/TextChunker.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Splits a given string into smaller chunks based on specified delimiters
|
||||
* and a predefined maximum chunk size. This will work through the given delimiters
|
||||
* to break down text further and further to fit into the chunk size.
|
||||
*
|
||||
* The last delimiter is always an empty string to ensure text can always be broken down.
|
||||
*/
|
||||
class TextChunker
|
||||
{
|
||||
public function __construct(
|
||||
protected int $chunkSize,
|
||||
protected array $delimiterOrder,
|
||||
) {
|
||||
if (count($this->delimiterOrder) === 0 || $this->delimiterOrder[count($this->delimiterOrder) - 1] !== '') {
|
||||
$this->delimiterOrder[] = '';
|
||||
}
|
||||
|
||||
if ($this->chunkSize < 1) {
|
||||
throw new InvalidArgumentException('Chunk size must be greater than 0');
|
||||
}
|
||||
}
|
||||
|
||||
public function chunk(string $text): array
|
||||
{
|
||||
$delimiter = $this->delimiterOrder[0];
|
||||
$delimiterLength = strlen($delimiter);
|
||||
$lines = ($delimiter === '') ? str_split($text, $this->chunkSize) : explode($delimiter, $text);
|
||||
|
||||
$cChunk = ''; // Current chunk
|
||||
$cLength = 0; // Current chunk length
|
||||
$chunks = []; // Chunks to return
|
||||
$lDelim = ''; // Last delimiter
|
||||
|
||||
foreach ($lines as $index => $line) {
|
||||
$lineLength = strlen($line);
|
||||
if ($cLength + $lineLength + $delimiterLength <= $this->chunkSize) {
|
||||
$cChunk .= $line . $delimiter;
|
||||
$cLength += $lineLength + $delimiterLength;
|
||||
$lDelim = $delimiter;
|
||||
} else if ($lineLength <= $this->chunkSize) {
|
||||
$chunks[] = trim($cChunk, $delimiter);
|
||||
$cChunk = $line . $delimiter;
|
||||
$cLength = $lineLength + $delimiterLength;
|
||||
$lDelim = $delimiter;
|
||||
} else {
|
||||
$subChunks = new static($this->chunkSize, array_slice($this->delimiterOrder, 1));
|
||||
$subDelimiter = $this->delimiterOrder[1] ?? '';
|
||||
$subDelimiterLength = strlen($subDelimiter);
|
||||
foreach ($subChunks->chunk($line) as $subChunk) {
|
||||
$chunkLength = strlen($subChunk);
|
||||
if ($cLength + $chunkLength + $subDelimiterLength <= $this->chunkSize) {
|
||||
$cChunk .= $subChunk . $subDelimiter;
|
||||
$cLength += $chunkLength + $subDelimiterLength;
|
||||
$lDelim = $subDelimiter;
|
||||
} else {
|
||||
$chunks[] = trim($cChunk, $lDelim);
|
||||
$cChunk = $subChunk . $subDelimiter;
|
||||
$cLength = $chunkLength + $subDelimiterLength;
|
||||
$lDelim = $subDelimiter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($cChunk !== '') {
|
||||
$chunks[] = trim($cChunk, $lDelim);
|
||||
}
|
||||
|
||||
return $chunks;
|
||||
}
|
||||
}
|
||||
38
app/Search/Queries/VectorQueryServiceProvider.php
Normal file
38
app/Search/Queries/VectorQueryServiceProvider.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Http\HttpRequestService;
|
||||
use BookStack\Search\Queries\Services\OpenAiVectorQueryService;
|
||||
use BookStack\Search\Queries\Services\VectorQueryService;
|
||||
|
||||
class VectorQueryServiceProvider
|
||||
{
|
||||
public function __construct(
|
||||
protected HttpRequestService $http,
|
||||
) {
|
||||
}
|
||||
|
||||
public function get(): VectorQueryService
|
||||
{
|
||||
$service = $this->getServiceName();
|
||||
|
||||
if ($service === 'openai') {
|
||||
return new OpenAiVectorQueryService(config('services.openai'), $this->http);
|
||||
}
|
||||
|
||||
throw new \Exception("No '{$service}' LLM service found");
|
||||
}
|
||||
|
||||
protected static function getServiceName(): string
|
||||
{
|
||||
return strtolower(config('services.llm'));
|
||||
}
|
||||
|
||||
public static function isEnabled(): bool
|
||||
{
|
||||
return !empty(static::getServiceName());
|
||||
}
|
||||
}
|
||||
17
app/Search/Queries/VectorSearchResult.php
Normal file
17
app/Search/Queries/VectorSearchResult.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Entity;
|
||||
|
||||
readonly class VectorSearchResult
|
||||
{
|
||||
public function __construct(
|
||||
public Entity $entity,
|
||||
public float $distance,
|
||||
public string $matchText
|
||||
) {
|
||||
}
|
||||
}
|
||||
54
app/Search/Queries/VectorSearchRunner.php
Normal file
54
app/Search/Queries/VectorSearchRunner.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Search\Queries;
|
||||
|
||||
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use Exception;
|
||||
|
||||
class VectorSearchRunner
|
||||
{
|
||||
public function __construct(
|
||||
protected VectorQueryServiceProvider $vectorQueryServiceProvider,
|
||||
protected PermissionApplicator $permissions,
|
||||
protected MixedEntityListLoader $entityLoader,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a vector search query to find results across entities.
|
||||
* @return VectorSearchResult[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public function run(string $query): array
|
||||
{
|
||||
$queryService = $this->vectorQueryServiceProvider->get();
|
||||
$queryVector = $queryService->generateEmbeddings($query);
|
||||
|
||||
// TODO - Test permissions applied
|
||||
$topMatchesQuery = SearchVector::query()->select('text', 'entity_type', 'entity_id')
|
||||
->selectRaw('VEC_DISTANCE_COSINE(VEC_FROMTEXT("[' . implode(',', $queryVector) . ']"), embedding) as distance')
|
||||
->orderBy('distance', 'asc')
|
||||
->having('distance', '<', 0.6)
|
||||
->limit(10);
|
||||
|
||||
$query = $this->permissions->restrictEntityRelationQuery($topMatchesQuery, 'search_vectors', 'entity_id', 'entity_type');
|
||||
$topMatches = $query->get();
|
||||
|
||||
$this->entityLoader->loadIntoRelations($topMatches->all(), 'entity', true);
|
||||
|
||||
$results = [];
|
||||
|
||||
foreach ($topMatches as $match) {
|
||||
if ($match->relationLoaded('entity')) {
|
||||
$results[] = new VectorSearchResult(
|
||||
$match->getRelation('entity'),
|
||||
$match->getAttribute('distance'),
|
||||
$match->getAttribute('text'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Queries\QueryPopular;
|
||||
use BookStack\Entities\Tools\SiblingFetcher;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Search\Queries\VectorSearchRunner;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
@@ -78,8 +79,9 @@ class SearchController extends Controller
|
||||
|
||||
// Search for entities otherwise show most popular
|
||||
if ($searchTerm !== false) {
|
||||
$searchTerm .= ' {type:' . implode('|', $entityTypes) . '}';
|
||||
$entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results'];
|
||||
$options = SearchOptions::fromString($searchTerm);
|
||||
$options->setFilter('type', implode('|', $entityTypes));
|
||||
$entities = $this->searchRunner->searchEntities($options, 'all', 1, 20)['results'];
|
||||
} else {
|
||||
$entities = $queryPopular->run(20, 0, $entityTypes);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use BookStack\Activity\Models\Tag;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Search\Queries\StoreEntityVectorsJob;
|
||||
use BookStack\Search\Queries\VectorQueryServiceProvider;
|
||||
use BookStack\Util\HtmlDocument;
|
||||
use DOMNode;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -25,7 +27,7 @@ class SearchIndex
|
||||
public static string $softDelimiters = ".-";
|
||||
|
||||
public function __construct(
|
||||
protected EntityProvider $entityProvider
|
||||
protected EntityProvider $entityProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -37,6 +39,10 @@ class SearchIndex
|
||||
$this->deleteEntityTerms($entity);
|
||||
$terms = $this->entityToTermDataArray($entity);
|
||||
$this->insertTerms($terms);
|
||||
|
||||
if (VectorQueryServiceProvider::isEnabled()) {
|
||||
dispatch(new StoreEntityVectorsJob($entity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,9 +53,15 @@ class SearchIndex
|
||||
public function indexEntities(array $entities): void
|
||||
{
|
||||
$terms = [];
|
||||
$vectorQueryEnabled = VectorQueryServiceProvider::isEnabled();
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$entityTerms = $this->entityToTermDataArray($entity);
|
||||
array_push($terms, ...$entityTerms);
|
||||
|
||||
if ($vectorQueryEnabled) {
|
||||
dispatch(new StoreEntityVectorsJob($entity));
|
||||
}
|
||||
}
|
||||
|
||||
$this->insertTerms($terms);
|
||||
|
||||
@@ -82,4 +82,12 @@ 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,6 +35,7 @@ class SearchOptions
|
||||
{
|
||||
$instance = new self();
|
||||
$instance->addOptionsFromString($search);
|
||||
$instance->limitOptions();
|
||||
return $instance;
|
||||
}
|
||||
|
||||
@@ -87,6 +88,8 @@ class SearchOptions
|
||||
$instance->filters = $instance->filters->merge($extras->filters);
|
||||
}
|
||||
|
||||
$instance->limitOptions();
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
@@ -147,6 +150,25 @@ 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.
|
||||
*/
|
||||
|
||||
64
composer.lock
generated
64
composer.lock
generated
@@ -62,16 +62,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.369.2",
|
||||
"version": "3.369.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "5e3f541e344d71f3b9591fe1d94d9576530fa795"
|
||||
"reference": "2aa1ef195e90140d733382e4341732ce113024f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e3f541e344d71f3b9591fe1d94d9576530fa795",
|
||||
"reference": "5e3f541e344d71f3b9591fe1d94d9576530fa795",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2aa1ef195e90140d733382e4341732ce113024f5",
|
||||
"reference": "2aa1ef195e90140d733382e4341732ce113024f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -85,7 +85,7 @@
|
||||
"mtdowling/jmespath.php": "^2.8.0",
|
||||
"php": ">=8.1",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"symfony/filesystem": "^v6.4.3 || ^v7.1.0 || ^v8.0.0"
|
||||
"symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"andrewsville/php-token-reflection": "^1.4",
|
||||
@@ -153,9 +153,9 @@
|
||||
"support": {
|
||||
"forum": "https://github.com/aws/aws-sdk-php/discussions",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.369.2"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.369.4"
|
||||
},
|
||||
"time": "2025-12-23T19:21:43+00:00"
|
||||
"time": "2025-12-29T19:07:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -1055,24 +1055,24 @@
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.3",
|
||||
"version": "v1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"reference": "e01f4a821471308ba86aa202fed6698b6b695e3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3"
|
||||
"phpoption/phpoption": "^1.9.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
|
||||
"phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1101,7 +1101,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1113,7 +1113,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:45:45+00:00"
|
||||
"time": "2025-12-27T19:43:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
@@ -3886,16 +3886,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.4",
|
||||
"version": "1.9.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d"
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d",
|
||||
"reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"reference": "75365b91986c2405cf5e1e012c5595cd487a98be",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3945,7 +3945,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.4"
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3957,7 +3957,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-21T11:53:16+00:00"
|
||||
"time": "2025-12-27T19:41:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
@@ -7977,26 +7977,26 @@
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.2",
|
||||
"version": "v5.6.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
|
||||
"reference": "955e7815d677a3eaa7075231212f2110983adecc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc",
|
||||
"reference": "955e7815d677a3eaa7075231212f2110983adecc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.1.3",
|
||||
"graham-campbell/result-type": "^1.1.4",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3",
|
||||
"symfony/polyfill-ctype": "^1.24",
|
||||
"symfony/polyfill-mbstring": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24"
|
||||
"phpoption/phpoption": "^1.9.5",
|
||||
"symfony/polyfill-ctype": "^1.26",
|
||||
"symfony/polyfill-mbstring": "^1.26",
|
||||
"symfony/polyfill-php80": "^1.26"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
@@ -8045,7 +8045,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8057,7 +8057,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-30T23:37:27+00:00"
|
||||
"time": "2025-12-27T19:49:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "voku/portable-ascii",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
project_id: "377219"
|
||||
project_identifier: bookstack
|
||||
base_path: .
|
||||
preserve_hierarchy: false
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// TODO - Handle compatibility with older databases that don't support vectors
|
||||
Schema::create('search_vectors', function (Blueprint $table) {
|
||||
$table->string('entity_type', 100);
|
||||
$table->integer('entity_id');
|
||||
$table->text('text');
|
||||
|
||||
$table->index(['entity_type', 'entity_id']);
|
||||
});
|
||||
|
||||
$table = DB::getTablePrefix() . 'search_vectors';
|
||||
|
||||
// TODO - Vector size might need to be dynamic
|
||||
DB::statement("ALTER TABLE {$table} ADD COLUMN (embedding VECTOR(1536) NOT NULL)");
|
||||
DB::statement("ALTER TABLE {$table} ADD VECTOR INDEX (embedding) DISTANCE=cosine");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('search_vectors');
|
||||
}
|
||||
};
|
||||
@@ -14,6 +14,9 @@ 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 && \
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +106,7 @@ 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 فريداً لنوع الكائن داخل المِلَفّ المضغوط.',
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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' => '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',
|
||||
'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',
|
||||
|
||||
// Permissions and restrictions
|
||||
'permissions' => 'Oprávnění',
|
||||
|
||||
@@ -109,6 +109,7 @@ 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' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'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' => 'You have been mentioned in a comment on page: :pageName',
|
||||
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
|
||||
'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:',
|
||||
|
||||
'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' => 'Notify when I\'m mentioned in a comment',
|
||||
'notifications_opt_comment_mentions' => 'Upozornit, když mě někdo zmíní v komentáři',
|
||||
'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' => 'Lists & Sorting',
|
||||
'sorting_book_default' => 'Default Book Sort Rule',
|
||||
'sorting' => 'Seznamy a řazení',
|
||||
'sorting_book_default' => 'Výchozí řazení knih',
|
||||
'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' => '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.',
|
||||
'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.).',
|
||||
|
||||
// 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' => 'These permissions will technically also provide visibility & searching of users & roles in the system.',
|
||||
'role_permission_note_users_and_roles' => 'Tato oprávnění zároveň umožní zobrazit a vyhledat uživatele a role na webu.',
|
||||
'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' => 'These permissions will technically also provide visibility & searching of users in the system.',
|
||||
'role_asset_users_note' => 'Tato oprávnění zároveň umožní zobrazit a vyhledat uživatele v systému.',
|
||||
'role_all' => 'Vše',
|
||||
'role_own' => 'Vlastní',
|
||||
'role_controlled_by_asset' => 'Řídí se obsahem, do kterého jsou nahrávány',
|
||||
|
||||
@@ -106,6 +106,7 @@ return [
|
||||
'uploaded' => 'Nahrávání :attribute se nezdařilo.',
|
||||
|
||||
'zip_file' => ':attribute musí odkazovat na soubor v archivu ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'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,6 +106,7 @@ 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' => 'The file :attribute must not exceed :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,6 +109,7 @@ 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' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'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,6 +106,7 @@ 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' => 'The file :attribute must not exceed :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,6 +109,7 @@ 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 data.json content exceeds the configured application maximum upload size.',
|
||||
'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,6 +106,7 @@ 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' => 'The file :attribute must not exceed :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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +110,7 @@ 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,6 +106,7 @@ 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.',
|
||||
|
||||
@@ -109,6 +109,7 @@ 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' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'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.',
|
||||
|
||||
@@ -106,6 +106,7 @@ 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' => 'The file :attribute must not exceed :size MB.',
|
||||
'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' => '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',
|
||||
'sort_rule_create' => 'נוצר חוק מיון',
|
||||
'sort_rule_create_notification' => 'חוק מיון נוצר בהצלחה',
|
||||
'sort_rule_update' => 'חוק מיון עודכן',
|
||||
'sort_rule_update_notification' => 'חוק מיון עודכן בהצלחה',
|
||||
'sort_rule_delete' => 'חוק מיון נמחק',
|
||||
'sort_rule_delete_notification' => 'חוק מיון נמחק בהצלחה',
|
||||
|
||||
// Other
|
||||
'permissions_update' => 'הרשאות עודכנו',
|
||||
|
||||
@@ -106,12 +106,13 @@ return [
|
||||
'mfa_verify_access' => 'אשר גישה',
|
||||
'mfa_verify_access_desc' => 'חשבון המשתמש שלך דורש ממך לאת את הזהות שלך בשכבת הגנה נוספת על מנת לאפשר לך גישה. יש לאשר גישה דרך אחד האמצעים הקיימים על מנת להמשיך.',
|
||||
'mfa_verify_no_methods' => 'אין אפשרויות אימות דו-שלבי מוגדרות',
|
||||
'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_no_methods_desc' => 'לא נמצאו אפשרויות ווידוא זהות עבור המשתמש שלך.
|
||||
נדרש לקנפג לפחות אחד על מנת לקבל גישה.',
|
||||
'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' => 'Multi-factor method configured, Please now login again using the configured method.',
|
||||
'mfa_setup_login_notification' => 'אמצעי זיהוי זהות הוגדרו, אנא התחבר מחדש.',
|
||||
];
|
||||
|
||||
@@ -30,8 +30,8 @@ return [
|
||||
'create' => 'צור',
|
||||
'update' => 'עדכן',
|
||||
'edit' => 'ערוך',
|
||||
'archive' => 'Archive',
|
||||
'unarchive' => 'Un-Archive',
|
||||
'archive' => 'הכנס לארכיון',
|
||||
'unarchive' => 'הוצא מארכיון',
|
||||
'sort' => 'מיין',
|
||||
'move' => 'הזז',
|
||||
'copy' => 'העתק',
|
||||
|
||||
@@ -22,8 +22,8 @@ return [
|
||||
'meta_created_name' => 'נוצר :timeLength על ידי :user',
|
||||
'meta_updated' => 'עודכן :timeLength',
|
||||
'meta_updated_name' => 'עודכן :timeLength על ידי :user',
|
||||
'meta_owned_name' => 'Owned by :user',
|
||||
'meta_reference_count' => 'Referenced by :count item|Referenced by :count items',
|
||||
'meta_owned_name' => 'בבעלות של :user',
|
||||
'meta_reference_count' => '',
|
||||
'entity_select' => 'בחר יישות',
|
||||
'entity_select_lack_permission' => 'אין לך אישורים דרושים לבחירת פריט זה',
|
||||
'images' => 'תמונות',
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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.',
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +106,7 @@ 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 yang diunggah ke buku ini',
|
||||
'image_book_title' => 'Lihat gambar untuk 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' => 'Berkas gambar berhasil diperbarui',
|
||||
'image_replace_success' => 'Detail gambar berhasil diperbarui',
|
||||
'image_rebuild_thumbs' => 'Buat Ulang Variasi Ukuran',
|
||||
'image_rebuild_thumbs_success' => 'Variasi ukuran gambar berhasil dibuat ulang!',
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ 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' => 'ZIP data.json content exceeds the configured application maximum upload size.',
|
||||
'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.',
|
||||
|
||||
@@ -106,6 +106,7 @@ return [
|
||||
'uploaded' => 'Il file non può essere caricato. Il server potrebbe non accettare file di questa dimensione.',
|
||||
|
||||
'zip_file' => 'L\'attributo :attribute deve fare riferimento a un file all\'interno dello ZIP.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => 'Il campo :attribute deve fare riferimento a un file di tipo :validTypes, trovato :foundType.',
|
||||
'zip_model_expected' => 'Oggetto dati atteso ma ":type" trovato.',
|
||||
'zip_unique' => 'L\'attributo :attribute deve essere univoco per il tipo di oggetto all\'interno dello ZIP.',
|
||||
|
||||
@@ -109,6 +109,7 @@ return [
|
||||
'import_zip_cant_read' => 'ZIPファイルを読み込めません。',
|
||||
'import_zip_cant_decode_data' => 'ZIPファイル内に data.json が見つからないかデコードできませんでした。',
|
||||
'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' => 'ブックを作成するために必要な権限がありません。',
|
||||
|
||||
@@ -11,8 +11,8 @@ return [
|
||||
'updated_page_subject' => 'ページの更新: :pageName',
|
||||
'updated_page_intro' => ':appName でページが更新されました',
|
||||
'updated_page_debounce' => '大量の通知を防ぐために、しばらくの間は同じユーザがこのページをさらに編集しても通知は送信されません。',
|
||||
'comment_mention_subject' => 'You have been mentioned in a comment on page: :pageName',
|
||||
'comment_mention_intro' => 'You were mentioned in a comment on :appName:',
|
||||
'comment_mention_subject' => 'ページのコメントであなたにメンションされています: :pageName',
|
||||
'comment_mention_intro' => ':appName: のコメントであなたにメンションされました',
|
||||
|
||||
'detail_page_name' => 'ページ名:',
|
||||
'detail_page_path' => 'ページパス:',
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'notifications_desc' => 'システム内で特定のアクティビティが実行されたときに受信する電子メール通知を制御します。',
|
||||
'notifications_opt_own_page_changes' => '自分が所有するページの変更を通知する',
|
||||
'notifications_opt_own_page_comments' => '自分が所有するページへのコメントを通知する',
|
||||
'notifications_opt_comment_mentions' => 'Notify when I\'m mentioned in a comment',
|
||||
'notifications_opt_comment_mentions' => 'コメントでメンションされたときに通知する',
|
||||
'notifications_opt_comment_replies' => '自分のコメントへの返信を通知する',
|
||||
'notifications_save' => '設定を保存',
|
||||
'notifications_update_success' => '通知設定を更新しました。',
|
||||
|
||||
@@ -75,8 +75,8 @@ return [
|
||||
'reg_confirm_restrict_domain_placeholder' => '制限しない',
|
||||
|
||||
// Sorting Settings
|
||||
'sorting' => 'Lists & Sorting',
|
||||
'sorting_book_default' => 'Default Book Sort Rule',
|
||||
'sorting' => '一覧とソート',
|
||||
'sorting_book_default' => 'ブックのデフォルトソートルール',
|
||||
'sorting_book_default_desc' => '新しいブックに適用するデフォルトのソートルールを選択します。これは既存のブックには影響しません。ルールはブックごとに上書きすることができます。',
|
||||
'sorting_rules' => 'ソートルール',
|
||||
'sorting_rules_desc' => 'これらはシステム内のコンテンツに適用できる事前定義のソート操作です。',
|
||||
@@ -103,8 +103,8 @@ return [
|
||||
'sort_rule_op_updated_date' => '更新日時',
|
||||
'sort_rule_op_chapters_first' => 'チャプタを最初に',
|
||||
'sort_rule_op_chapters_last' => 'チャプタを最後に',
|
||||
'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.',
|
||||
'sorting_page_limits' => 'ページング表示制限',
|
||||
'sorting_page_limits_desc' => 'システム内の各種リストで1ページに表示するアイテム数を設定します。 通常、少ない数に設定するとパフォーマンスが向上し、多い数に設定するとページの移動操作が少なくなります。 3の倍数(18、24、30など)を使用することをお勧めします。',
|
||||
|
||||
// Maintenance settings
|
||||
'maint' => 'メンテナンス',
|
||||
@@ -197,13 +197,13 @@ return [
|
||||
'role_import_content' => 'コンテンツのインポート',
|
||||
'role_editor_change' => 'ページエディタの変更',
|
||||
'role_notifications' => '通知の受信と管理',
|
||||
'role_permission_note_users_and_roles' => 'These permissions will technically also provide visibility & searching of users & roles in the system.',
|
||||
'role_permission_note_users_and_roles' => '技術的には、これらの権限によりシステムのユーザーおよび役割の可視性と検索も提供されます。',
|
||||
'role_asset' => 'アセット権限',
|
||||
'roles_system_warning' => '上記の3つの権限のいずれかを付与することは、ユーザーが自分の特権またはシステム内の他のユーザーの特権を変更できる可能性があることに注意してください。これらの権限は信頼できるユーザーにのみ割り当ててください。',
|
||||
'role_asset_desc' => '各アセットに対するデフォルトの権限を設定します。ここで設定した権限が優先されます。',
|
||||
'role_asset_admins' => '管理者にはすべてのコンテンツへのアクセス権が自動的に付与されますが、これらのオプションはUIオプションを表示または非表示にする場合があります。',
|
||||
'role_asset_image_view_note' => 'これは画像マネージャー内の可視性に関連しています。アップロードされた画像ファイルへの実際のアクセスは、システムの画像保存オプションに依存します。',
|
||||
'role_asset_users_note' => 'These permissions will technically also provide visibility & searching of users in the system.',
|
||||
'role_asset_users_note' => '技術的には、これらの権限によりシステム内のユーザーの可視性と検索も提供されます。',
|
||||
'role_all' => '全て',
|
||||
'role_own' => '自身',
|
||||
'role_controlled_by_asset' => 'このアセットに対し、右記の操作を許可:',
|
||||
|
||||
@@ -106,6 +106,7 @@ return [
|
||||
'uploaded' => 'ファイルをアップロードできませんでした。サーバーがこのサイズのファイルを受け付けていない可能性があります。',
|
||||
|
||||
'zip_file' => ':attribute はZIP 内のファイルを参照する必要があります。',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => ':attribute は種別 :validType のファイルを参照する必要がありますが、種別 :foundType となっています。',
|
||||
'zip_model_expected' => 'データオブジェクトが期待されますが、":type" が見つかりました。',
|
||||
'zip_unique' => 'ZIP内のオブジェクトタイプに :attribute が一意である必要があります。',
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +106,7 @@ 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,6 +109,7 @@ return [
|
||||
'import_zip_cant_read' => 'ZIP 파일을 읽을 수 없습니다.',
|
||||
'import_zip_cant_decode_data' => 'ZIP data.json 콘텐츠를 찾아서 디코딩할 수 없습니다.',
|
||||
'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,6 +106,7 @@ return [
|
||||
'uploaded' => '파일 크기가 서버에서 허용하는 수치를 넘습니다.',
|
||||
|
||||
'zip_file' => ':attribute은(는) 컨텐츠 ZIP 파일 내의 객체 유형에 대해 고유해야 합니다.',
|
||||
'zip_file_size' => 'The file :attribute must not exceed :size MB.',
|
||||
'zip_file_mime' => ':attribute은(는) :validTypes, found :foundType 유형의 파일을 참조해야 합니다.',
|
||||
'zip_model_expected' => '데이터 객체가 필요하지만 ":type" 타입이 발견되었습니다.',
|
||||
'zip_unique' => ':attribute은(는) 컨텐츠 ZIP 파일 내의 객체 유형에 대해 고유해야 합니다.',
|
||||
|
||||
@@ -109,6 +109,7 @@ 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,6 +106,7 @@ 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.',
|
||||
|
||||
@@ -37,7 +37,7 @@ return [
|
||||
'blockquote' => 'Blockquote',
|
||||
'inline_code' => 'Inline code',
|
||||
'callouts' => 'Callouts',
|
||||
'callout_information' => 'Information',
|
||||
'callout_information' => 'Informacija',
|
||||
'callout_success' => 'Success',
|
||||
'callout_warning' => 'Warning',
|
||||
'callout_danger' => 'Danger',
|
||||
@@ -61,7 +61,7 @@ return [
|
||||
'list_task' => 'Task list',
|
||||
'indent_increase' => 'Increase indent',
|
||||
'indent_decrease' => 'Decrease indent',
|
||||
'table' => 'Table',
|
||||
'table' => 'Lentelė',
|
||||
'insert_image' => 'Insert image',
|
||||
'insert_image_title' => 'Insert/Edit Image',
|
||||
'insert_link' => 'Insert/edit link',
|
||||
@@ -150,11 +150,11 @@ return [
|
||||
'text_to_display' => 'Text to display',
|
||||
'title' => 'Title',
|
||||
'browse_links' => 'Browse links',
|
||||
'open_link' => 'Open link',
|
||||
'open_link_in' => 'Open link in...',
|
||||
'open_link' => 'Atverti nuorodą',
|
||||
'open_link_in' => 'Atverti nuorodą...',
|
||||
'open_link_current' => 'Current window',
|
||||
'open_link_new' => 'New window',
|
||||
'remove_link' => 'Remove link',
|
||||
'open_link_new' => 'Naujame lange',
|
||||
'remove_link' => 'Pašalinti nuorodą',
|
||||
'insert_collapsible' => 'Insert collapsible block',
|
||||
'collapsible_unwrap' => 'Unwrap',
|
||||
'edit_label' => 'Edit label',
|
||||
@@ -163,14 +163,14 @@ return [
|
||||
'toggle_label' => 'Toggle label',
|
||||
|
||||
// About view
|
||||
'about' => 'About the editor',
|
||||
'about_title' => 'About the WYSIWYG Editor',
|
||||
'about' => 'Apie redaktorių',
|
||||
'about_title' => 'Apie WYSIWYG redaktorių',
|
||||
'editor_license' => 'Editor License & Copyright',
|
||||
'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.',
|
||||
'editor_lexical_license_link' => 'Full license details can be found here.',
|
||||
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.',
|
||||
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
|
||||
'save_continue' => 'Save Page & Continue',
|
||||
'save_continue' => 'Išsaugoti puslapį ir tęsti',
|
||||
'callouts_cycle' => '(Keep pressing to toggle through types)',
|
||||
'link_selector' => 'Link to content',
|
||||
'shortcuts' => 'Shortcuts',
|
||||
|
||||
@@ -416,7 +416,7 @@ return [
|
||||
'comment_jump_to_thread' => 'Jump to thread',
|
||||
'comment_delete_confirm' => 'Esate tikri, kad norite ištrinti šį komentarą?',
|
||||
'comment_in_reply_to' => 'Atsakydamas į :commentId',
|
||||
'comment_reference' => 'Reference',
|
||||
'comment_reference' => 'Nuoroda',
|
||||
'comment_reference_outdated' => '(Outdated)',
|
||||
'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.',
|
||||
|
||||
@@ -446,7 +446,7 @@ return [
|
||||
'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
|
||||
|
||||
// References
|
||||
'references' => 'References',
|
||||
'references' => 'Nuorodos',
|
||||
'references_none' => 'There are no tracked references to this item.',
|
||||
'references_to_desc' => 'Listed below is all the known content in the system that links to this item.',
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ return [
|
||||
'saml_no_email_address' => 'Nerandamas šio naudotojo elektroninio pašto adresas išorinės autentifikavimo sistemos pateiktuose duomenyse',
|
||||
'saml_invalid_response_id' => 'Prašymas iš išorinės autentifikavimo sistemos nėra atpažintas proceso, kurį pradėjo ši programa. Naršymas po prisijungimo gali sukelti šią problemą.',
|
||||
'saml_fail_authed' => 'Prisijungimas, naudojant :system nepavyko, sistema nepateikė sėkmingo leidimo.',
|
||||
'oidc_already_logged_in' => 'Already logged in',
|
||||
'oidc_already_logged_in' => 'Jau prisijungta',
|
||||
'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
|
||||
'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
|
||||
'social_no_action_defined' => 'Neapibrėžtas joks veiksmas',
|
||||
@@ -97,7 +97,7 @@ return [
|
||||
'404_page_not_found' => 'Puslapis nerastas',
|
||||
'sorry_page_not_found' => 'Atleiskite, puslapis, kurio ieškote, nerastas.',
|
||||
'sorry_page_not_found_permission_warning' => 'Jei tikėjotės, kad šis puslapis egzistuoja, galbūt neturite leidimo jo peržiūrėti.',
|
||||
'image_not_found' => 'Image Not Found',
|
||||
'image_not_found' => 'Paveikslėlis nerastas',
|
||||
'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.',
|
||||
'image_not_found_details' => 'If you expected this image to exist it might have been deleted.',
|
||||
'return_home' => 'Grįžti į namus',
|
||||
@@ -109,6 +109,7 @@ 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.',
|
||||
|
||||
@@ -6,7 +6,7 @@ return [
|
||||
|
||||
'new_comment_subject' => 'New comment on page: :pageName',
|
||||
'new_comment_intro' => 'A user has commented on a page in :appName:',
|
||||
'new_page_subject' => 'New page: :pageName',
|
||||
'new_page_subject' => 'Naujas puslapis: :pageName',
|
||||
'new_page_intro' => 'A new page has been created in :appName:',
|
||||
'updated_page_subject' => 'Updated page: :pageName',
|
||||
'updated_page_intro' => 'A page has been updated in :appName:',
|
||||
@@ -14,15 +14,15 @@ return [
|
||||
'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' => 'Page Name:',
|
||||
'detail_page_name' => 'Puslapio pavadinimas:',
|
||||
'detail_page_path' => 'Page Path:',
|
||||
'detail_commenter' => 'Commenter:',
|
||||
'detail_comment' => 'Comment:',
|
||||
'detail_created_by' => 'Created By:',
|
||||
'detail_updated_by' => 'Updated By:',
|
||||
'detail_comment' => 'Komentaras:',
|
||||
'detail_created_by' => 'Sukurta:',
|
||||
'detail_updated_by' => 'Atnaujinta:',
|
||||
|
||||
'action_view_comment' => 'View Comment',
|
||||
'action_view_page' => 'View Page',
|
||||
'action_view_page' => 'Peržiūrėti puslapį',
|
||||
|
||||
'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.',
|
||||
'footer_reason_link' => 'your notification preferences',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user