From 4b30849a88389373b8d8e9d34617e0b79b507f50 Mon Sep 17 00:00:00 2001 From: veenone Date: Mon, 5 Oct 2026 06:02:31 +0700 Subject: [PATCH 1/2] Let "Import all" adopt every orphan the search matches, in a background job The header checkbox on Import orphan files selected only the 25 rows on screen, so an install with thousands of stray files had to import them a page at a time. Once a whole page is ticked, the selection bar now offers "Select all N matching files", and "Import all" takes every orphan the search matches, on every page. The import runs in a queued job because it is too slow for a request. Each file is hashed in full and written in three commits, so 5,000 files of 4 MB take about four minutes, and PHP stops a request after 30 s of CPU, around file 1,100. ImportOrphanFilesJob works on the default queue in chunks of about 45 s: each chunk rescans, imports what is still orphaned and queues the next one. That keeps every job inside the worker's 60 s timeout and the queue's 90 s retry_after, so no extra worker is needed, and mail queued in the meantime goes out between chunks. If a run dies part way, the next one picks up what is left. Only one run can be active at a time. OrphanImportProgress keeps its state in the cache and starts a run under a lock. While a run is active, every other import is refused, the per-row button included, so no file is adopted twice. The page polls files/orphans/import-status every 3 s and shows the run as running, finished, failed with the reason, or stalled after 5 minutes without progress, which usually means no worker is listening. Bulk delete still works one page at a time. The adoption itself moved to OrphanFileImporter so the request and the job share it, and the rule for what can be imported now lives in OrphanFileScanner::importable(). --- .../Controllers/OrphanFilesController.php | 102 ++++++++------ .../Files/Jobs/ImportOrphanFilesJob.php | 79 +++++++++++ app/Modules/Files/OrphanFileImporter.php | 62 +++++++++ app/Modules/Files/OrphanFileScanner.php | 20 +++ app/Modules/Files/OrphanImportProgress.php | 131 ++++++++++++++++++ resources/js/pages/files/orphans.tsx | Bin 20080 -> 26576 bytes routes/web.php | 1 + tests/Feature/Files/OrphanFilesTest.php | 100 +++++++++++++ 8 files changed, 456 insertions(+), 39 deletions(-) create mode 100644 app/Modules/Files/Jobs/ImportOrphanFilesJob.php create mode 100644 app/Modules/Files/OrphanFileImporter.php create mode 100644 app/Modules/Files/OrphanImportProgress.php diff --git a/app/Modules/Files/Http/Controllers/OrphanFilesController.php b/app/Modules/Files/Http/Controllers/OrphanFilesController.php index f6ef586f..ff4d4a0b 100644 --- a/app/Modules/Files/Http/Controllers/OrphanFilesController.php +++ b/app/Modules/Files/Http/Controllers/OrphanFilesController.php @@ -5,20 +5,24 @@ declare(strict_types=1); namespace App\Modules\Files\Http\Controllers; use App\Http\Controllers\Controller; +use App\Models\User; use App\Modules\Audit\Action; use App\Modules\Audit\ActivityLogger; +use App\Modules\Files\Jobs\ImportOrphanFilesJob; use App\Modules\Files\Models\File; +use App\Modules\Files\OrphanFileImporter; use App\Modules\Files\OrphanFileScanner; +use App\Modules\Files\OrphanImportProgress; use App\Modules\Files\Scanning\ScanStatus; -use App\Modules\Files\Uploads\StoreUploadedFile; use App\Support\Pagination; -use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Storage; use Illuminate\Validation\Rule; +use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; @@ -33,7 +37,8 @@ class OrphanFilesController extends Controller { public function __construct( private readonly OrphanFileScanner $scanner, - private readonly StoreUploadedFile $storeFile, + private readonly OrphanFileImporter $importer, + private readonly OrphanImportProgress $progress, private readonly ActivityLogger $activity, ) {} @@ -91,6 +96,7 @@ class OrphanFilesController extends Controller 'search' => $search, 'scanned_disks' => $this->scanner->scannedDisks(), 'missing_count' => File::query()->where('scan_status', ScanStatus::Missing)->count(), + 'import_run' => $this->progress->current(), ]); } @@ -138,14 +144,20 @@ class OrphanFilesController extends Controller $user = $request->user(); assert($user !== null); - $validated = $this->validateItems($request); + if ($request->boolean('all')) { + return $this->importAll($request, $user); + } + + // While a background run is adopting files, a second importer + // could adopt the same path twice. + if ($this->progress->isActive()) { + $this->alreadyRunning(); + } $imported = 0; $importedFile = null; - foreach ($validated['items'] as $item) { - $disk = Storage::disk($item['disk']); - + foreach ($this->validateItems($request)['items'] as $item) { // Re-validate against a fresh scan — never trust a // client-supplied disk/path just because an earlier scan // listed it. @@ -153,18 +165,7 @@ class OrphanFilesController extends Controller continue; } - $importedFile = $this->storeFile->create( - uploader: $user, - originalName: basename($item['path']), - path: $item['path'], - mimeType: $disk->mimeType($item['path']) ?: 'application/octet-stream', - size: $disk->size($item['path']), - checksum: $this->checksumOf($disk, $item['path']), - folderId: null, - disk: $item['disk'], - action: Action::FileImported, - ); - + $importedFile = $this->importer->import($user, $item['disk'], $item['path']); $imported++; } @@ -182,6 +183,49 @@ class OrphanFilesController extends Controller )); } + /** + * Every orphan the search matches, on every page. Handed to a queued + * job: thousands of files hashed in full take minutes, and a request + * is cut off after 30s of CPU — part-way through, with an error page + * for an import that was in fact half done. See ImportOrphanFilesJob. + */ + private function importAll(Request $request, User $user): RedirectResponse + { + $search = trim($request->validate(['search' => ['nullable', 'string', 'max:255']])['search'] ?? ''); + $search = $search !== '' ? $search : null; + + $total = count($this->scanner->importable($user, $search)); + + if ($total === 0) { + return back()->with('success', trans_choice(':count file imported.|:count files imported.', 0, ['count' => '0'])); + } + + if (! $this->progress->tryStart($total)) { + $this->alreadyRunning(); + } + + ImportOrphanFilesJob::dispatch($user->id, $search); + + return back()->with('success', trans_choice( + 'Importing :count file in the background.|Importing :count files in the background.', + $total, + ['count' => (string) $total], + )); + } + + private function alreadyRunning(): never + { + throw ValidationException::withMessages(['items' => __('An import is already running. Wait for it to finish.')]); + } + + /** + * Polled by the orphans screen while a background run is going. + */ + public function importStatus(): JsonResponse + { + return response()->json($this->progress->current()); + } + public function destroy(Request $request): RedirectResponse { $validated = $this->validateItems($request); @@ -218,24 +262,4 @@ class OrphanFilesController extends Controller 'items.*.path' => ['required', 'string'], ]); } - - /** - * Streamed rather than hash_file() on a local path — the only way to - * checksum a file that might live on a non-local disk (S3 has no - * local filesystem path to hand hash_file()). - */ - private function checksumOf(Filesystem $disk, string $path): string - { - $stream = $disk->readStream($path); - - if ($stream === null) { - return ''; - } - - $context = hash_init('sha256'); - hash_update_stream($context, $stream); - fclose($stream); - - return hash_final($context); - } } diff --git a/app/Modules/Files/Jobs/ImportOrphanFilesJob.php b/app/Modules/Files/Jobs/ImportOrphanFilesJob.php new file mode 100644 index 00000000..46231a8e --- /dev/null +++ b/app/Modules/Files/Jobs/ImportOrphanFilesJob.php @@ -0,0 +1,79 @@ +find($this->userId); + + if ($user === null) { + $progress->fail('The account that started the import no longer exists.'); + + return; + } + + $deadline = microtime(true) + $this->budgetSeconds; + + foreach ($scanner->importable($user, $this->search) as $i => $item) { + // At least one file per chunk, so a run always moves forward. + if ($i > 0 && microtime(true) >= $deadline) { + self::dispatch($this->userId, $this->search, $this->budgetSeconds); + + return; + } + + $importer->import($user, $item['disk'], $item['path']); + $progress->advance(); + } + + $progress->finish(); + } + + public function failed(Throwable $exception): void + { + app(OrphanImportProgress::class)->fail($exception->getMessage()); + } +} diff --git a/app/Modules/Files/OrphanFileImporter.php b/app/Modules/Files/OrphanFileImporter.php new file mode 100644 index 00000000..1fa65dd0 --- /dev/null +++ b/app/Modules/Files/OrphanFileImporter.php @@ -0,0 +1,62 @@ +storeFile->create( + uploader: $user, + originalName: basename($path), + path: $path, + mimeType: $disk->mimeType($path) ?: 'application/octet-stream', + size: $disk->size($path), + checksum: $this->checksumOf($disk, $path), + folderId: null, + disk: $diskName, + action: Action::FileImported, + ); + } + + /** + * Streamed rather than hash_file() on a local path — the only way to + * checksum a file that might live on a non-local disk (S3 has no + * local filesystem path to hand hash_file()). + */ + private function checksumOf(Filesystem $disk, string $path): string + { + $stream = $disk->readStream($path); + + if ($stream === null) { + return ''; + } + + $context = hash_init('sha256'); + hash_update_stream($context, $stream); + fclose($stream); + + return hash_final($context); + } +} diff --git a/app/Modules/Files/OrphanFileScanner.php b/app/Modules/Files/OrphanFileScanner.php index fddc6e80..d97cd19f 100644 --- a/app/Modules/Files/OrphanFileScanner.php +++ b/app/Modules/Files/OrphanFileScanner.php @@ -100,6 +100,26 @@ class OrphanFileScanner return $orphans; } + /** + * Every orphan the search matches that $viewer may import: the same + * rule as isImportable(), applied to a whole scan. Taken from a fresh + * scan, so unlike a client-supplied list it needs no per-path re-check. + * + * @return list + */ + public function importable(User $viewer, ?string $search = null): array + { + $importable = []; + + foreach ($this->scan($viewer, $search) as $orphan) { + if ($orphan['allowed'] && $orphan['size'] > 0) { + $importable[] = ['disk' => $orphan['disk'], 'path' => $orphan['path']]; + } + } + + return $importable; + } + /** * Re-validated at import/delete time — never trust a client-supplied * disk/path just because it was in an earlier scan response. $disk diff --git a/app/Modules/Files/OrphanImportProgress.php b/app/Modules/Files/OrphanImportProgress.php new file mode 100644 index 00000000..c278a4a1 --- /dev/null +++ b/app/Modules/Files/OrphanImportProgress.php @@ -0,0 +1,131 @@ +get(function () use ($total): bool { + if ($this->isActive()) { + return false; + } + + $this->put([ + 'status' => 'running', + 'total' => $total, + 'imported' => 0, + 'error' => null, + 'started_at' => now()->getTimestamp(), + ]); + + return true; + }); + } + + public function advance(): void + { + $run = $this->raw(); + + if ($run !== null) { + $run['imported']++; + $this->put($run); + } + } + + public function finish(): void + { + $this->end('finished'); + } + + public function fail(string $error): void + { + $this->end('failed', $error); + } + + public function isActive(): bool + { + return ($this->current()['status'] ?? null) === 'running'; + } + + /** + * The run as the page shows it. A running run that has gone quiet is + * reported as stalled. + * + * @return array{status: string, total: int, imported: int, error: ?string, started_at: int}|null + */ + public function current(): ?array + { + $run = $this->raw(); + + if ($run === null) { + return null; + } + + if ($run['status'] === 'running' && now()->getTimestamp() - $run['updated_at'] > self::STALL_SECONDS) { + $run['status'] = 'stalled'; + } + + unset($run['updated_at']); + + return $run; + } + + private function end(string $status, ?string $error = null): void + { + $run = $this->raw(); + + if ($run !== null) { + $this->put([...$run, 'status' => $status, 'error' => $error]); + } + } + + /** + * @return Run|null + */ + private function raw(): ?array + { + return Cache::get(self::KEY); + } + + /** + * @param array $run + */ + private function put(array $run): void + { + Cache::put(self::KEY, [...$run, 'updated_at' => now()->getTimestamp()], self::TTL_SECONDS); + } +} diff --git a/resources/js/pages/files/orphans.tsx b/resources/js/pages/files/orphans.tsx index 8a836c1303cafd42bd66422e74a38d9dfd79cb15..f4b7edae09a8769a0ce2850791235dee6abef35c 100644 GIT binary patch delta 5034 zcma)AL2nz!6^0VqRcJYpRZDj47#^v%mXygQB^xk6i&9j_iB(v3U>j`;)pb_nklbpy zvzZ-AR%wRGFR1J3p@+7&_S8cI9a^A3+e;7ap~){OiuTr%^9$PV%`C}fC8s7Jkvlu{ z=FRuM_r16K{AXidd_MN{>E_k6l>-KMrQ)?mp6XB77e_xG>yamR=_B}2PI>tR=7ZpRrklwS5_jv0hm41! z-Dol;Se^5L1^y1tGwDel?Dg-3Lec$}r*LwEK~;pYV(;I*ef!b+od-AXeS7WpqmBEl z!mgBFEtRhCe|fAlHjz)cp=ZZ`Z9FR$+07j;_n0vFGKFx&+>_NN*{?QvoM&EDia4f_ zu*hrh*u=z6lvr!(zzC4A-o; zC)mUu8?-H=g;iXw4X+>KIPI9A%1B zwI0rh-Lokl^K5f?^{{_N;QOu!0SF6K5k()`SG{2}Ic7GGRijKYxw&{5s3d4o;VYUDcLhauUKa;0k#rq6lUx z%!I{)JOtXOJG*{Z6T2>syH5GK;|1Alj#XBYRHfPUsdEHB3n@U19U=Aj%tX(mUBM#i zw|RBnHw!CMFXKB{p(q&;%|S z8L)9S%|^IEC`;E;CEH;l4ED-w6R?xg#=z1+y8EHQjjo2YbRGh~VVwIQ4ly;_VuoGd zj*uq7POs|b4=7fj?X=QTjUC34A>ov4^HD!6(H314EKd%&=GRj0cI^u6pJW=M^>F{M z<7;DQXyZ}Yu~MUtWWF_<;_6{@fc%}zpO2D>u0DBOWIF@q96r5&TlbFFu9;=lG0O>y zc1i%j4hu#SxJbk*tN$}S{g$DiO$`Vrk{zg!pplI0?@e6T|JU)tD<_6|%;x9!-+2AW zX{ST_F+We5MUs`&FGfU3i=*}~&FFtj9l5(aV7saGw5~!nx*$&?o|TSu5=TzUX?Apr zT$dGBGWyQaMg6Dzr2hHaGy9)i`sp$K{Znt~FY-tA?=PJ{d4)B+sIXU9WD1E;h+F!3 z+0okzuOF|AP!(Fd=C^6=vM$XY(IS7&O;r!%&5*pVspd!;R3iv4ueq`RL$230F43v< zjmy)|&YxQAVEO%I0)aA{Hizp2_E!2*=>|M(s3yVm?ED#hdi~^)zz?@|Vf}QeS@7B@ z)={A}2as++hqhA?tqtp2mZFyH>MzSK9cbCQVybSrirz@sf=ormC^#|_N=P@#2V65p zdpHm0Q@+fTj{8*bB~jN5lqZON5)%`+Ou2Q z+P&?Q+PnJl{Do)dCyja9YIW&?#`oFuHU>sZWcY1p^!&oK-YlG*IzVqa`GWpS;g$*K zAKyE6gwC!P7qd9ULVueRope~p;{I>)KRRmSNC!nc_g9w|-|A6Im0=6)J57#;gSwTk z>N0)wlJ{(vSVo17k_HoNWS(uKijXPl#n{%KF-DD$6SD|4*=2WOUhev_-3PK-6}=WR z&{pO_CZW?ozglmHRjNcoIXAfk0Y-}Chl#|xRKqZqsxjAxx66qb>w|I+SnleXzKb4a{vI7b5wgu zX`M0@j3G{)q`#yN*;u)nG|cEpY&ShH#vs8){j9$ULL_h*{l@xy&&T*2s|H>i-$8Y+ z%z0a}2vDwIDJm>66#J&aH>GjmS}L&yGSmfEeXkIBTRf-`s}nJDa^01_?vmuPiqS*>8q3Ah^pq|Ai;ZKG*3|HU zPJiN@4wfThwme{osI#&qSJGN0ad+8wMVnQ<5M82&)KN_z8H2KDoOyNWG#F??GlW0kVn5i){UHOA9tY2pH03a=4jy=4f~!S(P-i=I@=#Ug;x^jp zI37t$qA$d%U!@Ic8rK=wBAI`NMgLN|HQtr@5z}Zk+11a!c2|clxN!+nntRxsAu(mbZtO6pC$es$LYbp%&?}($}d?>?PWFqbUTc z{kjo4)1r9|kJMGr%LV0!WY$-Bje|JhO_g#D=+Tf{2FL%C-v@a*HInj3_ipCDA?R9} delta 148 zcmV;F0Biry&jIk90g!%?j3Ne9a$$35GO@sW0ken#QvJi10wi0cVOcP_XHWV!avvC%C7qg%)z6O)XH4T%nAq5a~Z6IcEZgeee zWn^h}Z6ZFC3pO7Sb7gF0V{~O?E^}#mWgvYieUpJUC6k~o5VIURZv~T(P9L+)KoT5L CVm3Gc diff --git a/routes/web.php b/routes/web.php index 38138e0e..1407cb4c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -126,6 +126,7 @@ Route::middleware(['auth'])->group(function () { Route::middleware(['staff', 'can:import_orphans'])->group(function () { Route::get('files/orphans', [OrphanFilesController::class, 'index'])->name('orphan-files.index'); Route::post('files/orphans/import', [OrphanFilesController::class, 'import'])->name('orphan-files.import'); + Route::get('files/orphans/import-status', [OrphanFilesController::class, 'importStatus'])->name('orphan-files.import-status'); Route::post('files/orphans/delete', [OrphanFilesController::class, 'destroy'])->name('orphan-files.delete'); }); diff --git a/tests/Feature/Files/OrphanFilesTest.php b/tests/Feature/Files/OrphanFilesTest.php index 0738673b..235a063e 100644 --- a/tests/Feature/Files/OrphanFilesTest.php +++ b/tests/Feature/Files/OrphanFilesTest.php @@ -5,9 +5,12 @@ declare(strict_types=1); use App\Models\User; use App\Modules\Audit\Action; use App\Modules\Audit\ActivityLog; +use App\Modules\Files\Jobs\ImportOrphanFilesJob; use App\Modules\Files\Models\File; +use App\Modules\Files\OrphanImportProgress; use App\Modules\Platform\Settings\Setting; use App\Modules\Platform\Settings\Settings; +use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Storage; use Illuminate\Testing\TestResponse; use Inertia\Testing\AssertableInertia; @@ -31,6 +34,23 @@ function makeAdoptedFile(User $uploader, string $path, string $disk = 'files'): ]); } +/** + * 30 importable .txt files under 2026/07/batch, plus one empty and one + * restricted file there and one importable file outside it. + */ +function makeBatchOrphans(): void +{ + app(Settings::class)->set(Setting::UploadTypeRestriction, 'all'); + app(Settings::class)->set(Setting::AllowedUploadExtensions, ['txt']); + + foreach (range(1, 30) as $i) { + makeOrphanFile(sprintf('2026/07/batch/%02d.txt', $i)); + } + makeOrphanFile('2026/07/batch/empty.txt', ''); + makeOrphanFile('2026/07/batch/shell.php'); + makeOrphanFile('2026/07/elsewhere.txt'); +} + function orphanImport(array $items): TestResponse { return test()->postJson('/files/orphans/import', ['items' => $items]); @@ -215,6 +235,86 @@ test('importing multiple orphans at once stays on the list rather than picking o } }); +test('import all queues a background run for every importable match instead of importing in the request', function () { + Queue::fake(); + makeBatchOrphans(); + + $this->actingAs($this->admin) + ->postJson('/files/orphans/import', ['all' => true, 'search' => 'batch']) + ->assertRedirect(); + + expect(File::query()->count())->toBe(0); + Queue::assertPushed(ImportOrphanFilesJob::class, 1); + + $this->getJson('/files/orphans/import-status') + ->assertOk() + ->assertJson(['status' => 'running', 'total' => 30, 'imported' => 0]); +}); + +test('the background run imports every match across as many chunks as it takes, then reports finished', function () { + makeBatchOrphans(); + + app(OrphanImportProgress::class)->tryStart(30); + + // A zero budget makes every chunk stop after one file, so the run has + // to hand over to the next chunk 29 times to finish. + ImportOrphanFilesJob::dispatch($this->admin->id, 'batch', budgetSeconds: 0); + + expect(File::query()->count())->toBe(30) + ->and(File::query()->where('path', '2026/07/elsewhere.txt')->exists())->toBeFalse() + ->and(File::query()->where('uploaded_by', $this->admin->id)->count())->toBe(30); + + $this->actingAs($this->admin)->getJson('/files/orphans/import-status') + ->assertJson(['status' => 'finished', 'total' => 30, 'imported' => 30]); +}); + +test('while a run is in progress no other import is accepted, so nothing is adopted twice', function () { + Queue::fake(); + makeOrphanFile('2026/07/one.txt'); + + app(OrphanImportProgress::class)->tryStart(1); + + $this->actingAs($this->admin); + orphanImport([['disk' => 'files', 'path' => '2026/07/one.txt']])->assertUnprocessable(); + $this->postJson('/files/orphans/import', ['all' => true])->assertUnprocessable(); + + expect(File::query()->count())->toBe(0); + Queue::assertNothingPushed(); +}); + +test('a run that stops making progress is reported as stalled and no longer blocks a new one', function () { + Queue::fake(); + makeOrphanFile('2026/07/one.txt'); + + app(OrphanImportProgress::class)->tryStart(5); + $this->travel(6)->minutes(); + + $this->actingAs($this->admin)->getJson('/files/orphans/import-status') + ->assertJson(['status' => 'stalled', 'total' => 5, 'imported' => 0]); + + $this->postJson('/files/orphans/import', ['all' => true])->assertRedirect(); + Queue::assertPushed(ImportOrphanFilesJob::class, 1); +}); + +test('a run whose job fails is reported as failed with the reason', function () { + app(OrphanImportProgress::class)->tryStart(5); + + (new ImportOrphanFilesJob($this->admin->id, null))->failed(new RuntimeException('Disk unreachable')); + + $this->actingAs($this->admin)->getJson('/files/orphans/import-status') + ->assertJson(['status' => 'failed', 'error' => 'Disk unreachable']); +}); + +test('import all with nothing importable queues nothing', function () { + Queue::fake(); + makeOrphanFile('2026/07/empty.txt', ''); + + $this->actingAs($this->admin)->postJson('/files/orphans/import', ['all' => true])->assertRedirect(); + + Queue::assertNothingPushed(); + $this->getJson('/files/orphans/import-status')->assertOk()->assertExactJson([]); +}); + test('a 0-byte orphan cannot be imported but can still be deleted', function () { makeOrphanFile('2026/07/empty.txt', ''); From b8108cdf70fb450bb971b21ac37ad6385190a7ca Mon Sep 17 00:00:00 2001 From: veenone Date: Mon, 5 Oct 2026 06:02:31 +0700 Subject: [PATCH 2/2] Translate the strings added for "Import all" on the orphans screen Fourteen strings, in all sixteen locales: the select-all link, the note about skipped files, the Import all button, the four states of a background run, and the messages for a queued or already running import. For the queued message, Russian, Polish and Czech get all three plural forms Laravel picks from in those languages. With only two, a count such as 5000 would use the singular. --- lang/ca.json | 16 +++++++++++++++- lang/cs.json | 16 +++++++++++++++- lang/de.json | 16 +++++++++++++++- lang/es.json | 16 +++++++++++++++- lang/fr.json | 16 +++++++++++++++- lang/id.json | 16 +++++++++++++++- lang/it.json | 16 +++++++++++++++- lang/ja.json | 16 +++++++++++++++- lang/nl.json | 16 +++++++++++++++- lang/pl.json | 16 +++++++++++++++- lang/pt_BR.json | 16 +++++++++++++++- lang/ru.json | 16 +++++++++++++++- lang/sw.json | 16 +++++++++++++++- lang/tr.json | 16 +++++++++++++++- lang/vi.json | 16 +++++++++++++++- lang/zh_CN.json | 16 +++++++++++++++- 16 files changed, 240 insertions(+), 16 deletions(-) diff --git a/lang/ca.json b/lang/ca.json index 7143ba56..687911a5 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Es mostra fins a 320 × 128 píxels, de manera que hi caben tant un logotip ample com un de quadrat. PNG, JPG, GIF o WebP, fins a 2 MB.", "Show the site name under the logo": "Mostra el nom del lloc sota el logotip", "They will no longer be available to anyone they were shared with.": "Deixaran d'estar disponibles per a tothom amb qui s'havien compartit.", - "Uploading into :folder": "Pujant a :folder" + "Uploading into :folder": "Pujant a :folder", + "Select all :count matching files": "Selecciona els :count fitxers coincidents", + "Restricted and empty files are skipped.": "Els fitxers restringits i els buits s’ometen.", + "Import all": "Importa-ho tot", + "Importing in the background": "S’està important en segon pla", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported de :total fitxers importats. Pots sortir d’aquesta pàgina: la importació continua.", + "Import finished": "Importació acabada", + ":imported of :total files imported.": ":imported de :total fitxers importats.", + "The import stopped": "La importació s’ha aturat", + "The import stopped making progress": "La importació ha deixat d’avançar", + ":imported of :total files were imported before it failed: :error": "S’han importat :imported de :total fitxers abans de l’error: :error", + ":imported of :total files were imported. The queue worker may not be running.": "S’han importat :imported de :total fitxers. Potser el procés en segon pla no s’està executant.", + "Choose Import all again to continue with the rest.": "Torna a triar «Importa-ho tot» per continuar amb la resta.", + "An import is already running. Wait for it to finish.": "Ja hi ha una importació en curs. Espera que acabi.", + "Importing :count file in the background.|Importing :count files in the background.": "S’està important :count fitxer en segon pla.|S’estan important :count fitxers en segon pla." } diff --git a/lang/cs.json b/lang/cs.json index eaa90f36..5921c481 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Zobrazuje se až v rozměru 320 × 128 pixelů, takže se vejde široké i čtvercové logo. PNG, JPG, GIF nebo WebP, do 2 MB.", "Show the site name under the logo": "Zobrazit název webu pod logem", "They will no longer be available to anyone they were shared with.": "Přestanou být dostupné všem, se kterými byly sdíleny.", - "Uploading into :folder": "Nahrávání do: :folder" + "Uploading into :folder": "Nahrávání do: :folder", + "Select all :count matching files": "Vybrat všech :count odpovídajících souborů", + "Restricted and empty files are skipped.": "Soubory s omezenou příponou a prázdné soubory se přeskočí.", + "Import all": "Naimportovat vše", + "Importing in the background": "Import probíhá na pozadí", + ":imported of :total files imported. You can leave this page — the import keeps going.": "Naimportováno :imported z :total souborů. Tuto stránku můžete opustit — import poběží dál.", + "Import finished": "Import dokončen", + ":imported of :total files imported.": "Naimportováno :imported z :total souborů.", + "The import stopped": "Import se zastavil", + "The import stopped making progress": "Import přestal postupovat", + ":imported of :total files were imported before it failed: :error": "Před chybou se naimportovalo :imported z :total souborů: :error", + ":imported of :total files were imported. The queue worker may not be running.": "Naimportováno :imported z :total souborů. Proces na pozadí možná neběží.", + "Choose Import all again to continue with the rest.": "Pro pokračování se zbytkem zvolte znovu „Naimportovat vše“.", + "An import is already running. Wait for it to finish.": "Import už probíhá. Počkejte, až skončí.", + "Importing :count file in the background.|Importing :count files in the background.": "Na pozadí se importuje :count soubor.|Na pozadí se importují :count soubory.|Na pozadí se importuje :count souborů." } diff --git a/lang/de.json b/lang/de.json index 81bd7d29..fc5993fe 100644 --- a/lang/de.json +++ b/lang/de.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Wird bis zu 320 × 128 Pixel groß angezeigt, sodass breite und quadratische Logos gleichermaßen passen. PNG, JPG, GIF oder WebP, bis 2 MB.", "Show the site name under the logo": "Seitennamen unter dem Logo anzeigen", "They will no longer be available to anyone they were shared with.": "Sie stehen dann niemandem mehr zur Verfügung, mit dem sie geteilt wurden.", - "Uploading into :folder": "Hochladen in :folder" + "Uploading into :folder": "Hochladen in :folder", + "Select all :count matching files": "Alle :count passenden Dateien auswählen", + "Restricted and empty files are skipped.": "Dateien mit eingeschränkter Endung und leere Dateien werden übersprungen.", + "Import all": "Alle importieren", + "Importing in the background": "Import läuft im Hintergrund", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported von :total Dateien importiert. Sie können diese Seite verlassen — der Import läuft weiter.", + "Import finished": "Import abgeschlossen", + ":imported of :total files imported.": ":imported von :total Dateien importiert.", + "The import stopped": "Der Import wurde abgebrochen", + "The import stopped making progress": "Der Import kommt nicht mehr voran", + ":imported of :total files were imported before it failed: :error": ":imported von :total Dateien wurden importiert, bevor ein Fehler auftrat: :error", + ":imported of :total files were imported. The queue worker may not be running.": ":imported von :total Dateien wurden importiert. Möglicherweise läuft der Hintergrundprozess nicht.", + "Choose Import all again to continue with the rest.": "Wählen Sie erneut „Alle importieren“, um mit den übrigen fortzufahren.", + "An import is already running. Wait for it to finish.": "Es läuft bereits ein Import. Warten Sie, bis er abgeschlossen ist.", + "Importing :count file in the background.|Importing :count files in the background.": ":count Datei wird im Hintergrund importiert.|:count Dateien werden im Hintergrund importiert." } diff --git a/lang/es.json b/lang/es.json index 80012047..6fca6484 100644 --- a/lang/es.json +++ b/lang/es.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Se muestra hasta 320 × 128 píxeles, así que entran tanto un logo ancho como uno cuadrado. PNG, JPG, GIF o WebP, hasta 2 MB.", "Show the site name under the logo": "Mostrar el nombre del sitio debajo del logo", "They will no longer be available to anyone they were shared with.": "Dejarán de estar disponibles para quienes fueron compartidos.", - "Uploading into :folder": "Subiendo a :folder" + "Uploading into :folder": "Subiendo a :folder", + "Select all :count matching files": "Seleccionar los :count archivos coincidentes", + "Restricted and empty files are skipped.": "Se omiten los archivos restringidos y los vacíos.", + "Import all": "Importar todo", + "Importing in the background": "Importando en segundo plano", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported de :total archivos importados. Puedes salir de esta página: la importación sigue en marcha.", + "Import finished": "Importación terminada", + ":imported of :total files imported.": ":imported de :total archivos importados.", + "The import stopped": "La importación se detuvo", + "The import stopped making progress": "La importación dejó de avanzar", + ":imported of :total files were imported before it failed: :error": "Se importaron :imported de :total archivos antes del error: :error", + ":imported of :total files were imported. The queue worker may not be running.": "Se importaron :imported de :total archivos. Puede que el proceso en segundo plano no esté funcionando.", + "Choose Import all again to continue with the rest.": "Vuelve a elegir «Importar todo» para seguir con el resto.", + "An import is already running. Wait for it to finish.": "Ya hay una importación en curso. Espera a que termine.", + "Importing :count file in the background.|Importing :count files in the background.": "Importando :count archivo en segundo plano.|Importando :count archivos en segundo plano." } diff --git a/lang/fr.json b/lang/fr.json index d4136f9e..6151b0a6 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Affiché jusqu'à 320 × 128 pixels : un logo large comme un logo carré y trouvent leur place. PNG, JPG, GIF ou WebP, jusqu'à 2 Mo.", "Show the site name under the logo": "Afficher le nom du site sous le logo", "They will no longer be available to anyone they were shared with.": "Ils ne seront plus accessibles aux personnes avec qui ils étaient partagés.", - "Uploading into :folder": "Envoi dans :folder" + "Uploading into :folder": "Envoi dans :folder", + "Select all :count matching files": "Sélectionner les :count fichiers correspondants", + "Restricted and empty files are skipped.": "Les fichiers à extension restreinte et les fichiers vides sont ignorés.", + "Import all": "Tout importer", + "Importing in the background": "Importation en arrière-plan", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported fichiers importés sur :total. Vous pouvez quitter cette page — l’importation continue.", + "Import finished": "Importation terminée", + ":imported of :total files imported.": ":imported fichiers importés sur :total.", + "The import stopped": "L’importation s’est arrêtée", + "The import stopped making progress": "L’importation n’avance plus", + ":imported of :total files were imported before it failed: :error": ":imported fichiers sur :total ont été importés avant l’échec : :error", + ":imported of :total files were imported. The queue worker may not be running.": ":imported fichiers sur :total ont été importés. Le processus d’arrière-plan ne tourne peut-être pas.", + "Choose Import all again to continue with the rest.": "Choisissez à nouveau « Tout importer » pour continuer avec le reste.", + "An import is already running. Wait for it to finish.": "Une importation est déjà en cours. Attendez qu’elle se termine.", + "Importing :count file in the background.|Importing :count files in the background.": "Importation de :count fichier en arrière-plan.|Importation de :count fichiers en arrière-plan." } diff --git a/lang/id.json b/lang/id.json index c625017f..fc3af579 100644 --- a/lang/id.json +++ b/lang/id.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Ditampilkan hingga 320 × 128 piksel, jadi logo lebar maupun persegi sama-sama muat. PNG, JPG, GIF, atau WebP, hingga 2 MB.", "Show the site name under the logo": "Tampilkan nama situs di bawah logo", "They will no longer be available to anyone they were shared with.": "Berkas tersebut tidak akan tersedia lagi bagi siapa pun yang sebelumnya menerimanya.", - "Uploading into :folder": "Mengunggah ke :folder" + "Uploading into :folder": "Mengunggah ke :folder", + "Select all :count matching files": "Pilih semua :count berkas yang cocok", + "Restricted and empty files are skipped.": "Berkas dengan ekstensi yang dibatasi dan berkas kosong dilewati.", + "Import all": "Impor semua", + "Importing in the background": "Mengimpor di latar belakang", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported dari :total berkas diimpor. Anda bisa meninggalkan halaman ini — impor tetap berjalan.", + "Import finished": "Impor selesai", + ":imported of :total files imported.": ":imported dari :total berkas diimpor.", + "The import stopped": "Impor berhenti", + "The import stopped making progress": "Impor tidak lagi berjalan maju", + ":imported of :total files were imported before it failed: :error": ":imported dari :total berkas diimpor sebelum gagal: :error", + ":imported of :total files were imported. The queue worker may not be running.": ":imported dari :total berkas diimpor. Proses latar mungkin tidak berjalan.", + "Choose Import all again to continue with the rest.": "Pilih “Impor semua” lagi untuk melanjutkan sisanya.", + "An import is already running. Wait for it to finish.": "Impor lain sedang berjalan. Tunggu sampai selesai.", + "Importing :count file in the background.|Importing :count files in the background.": "Mengimpor :count berkas di latar belakang.|Mengimpor :count berkas di latar belakang." } diff --git a/lang/it.json b/lang/it.json index 793f19df..d4653f68 100644 --- a/lang/it.json +++ b/lang/it.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Mostrato fino a 320 × 128 pixel, così ci stanno sia un logo largo sia uno quadrato. PNG, JPG, GIF o WebP, fino a 2 MB.", "Show the site name under the logo": "Mostra il nome del sito sotto il logo", "They will no longer be available to anyone they were shared with.": "Non saranno più disponibili per le persone con cui erano condivisi.", - "Uploading into :folder": "Caricamento in :folder" + "Uploading into :folder": "Caricamento in :folder", + "Select all :count matching files": "Seleziona tutti i :count file corrispondenti", + "Restricted and empty files are skipped.": "I file con estensione limitata e quelli vuoti vengono saltati.", + "Import all": "Importa tutto", + "Importing in the background": "Importazione in background", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported di :total file importati. Puoi lasciare questa pagina: l’importazione continua.", + "Import finished": "Importazione completata", + ":imported of :total files imported.": ":imported di :total file importati.", + "The import stopped": "L’importazione si è interrotta", + "The import stopped making progress": "L’importazione non avanza più", + ":imported of :total files were imported before it failed: :error": "Sono stati importati :imported di :total file prima dell’errore: :error", + ":imported of :total files were imported. The queue worker may not be running.": "Sono stati importati :imported di :total file. Forse il processo in background non è in esecuzione.", + "Choose Import all again to continue with the rest.": "Scegli di nuovo «Importa tutto» per continuare con il resto.", + "An import is already running. Wait for it to finish.": "C’è già un’importazione in corso. Aspetta che finisca.", + "Importing :count file in the background.|Importing :count files in the background.": "Importazione di :count file in background.|Importazione di :count file in background." } diff --git a/lang/ja.json b/lang/ja.json index b6c12183..45719fe6 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "最大 320 × 128 ピクセルで表示されるため、横長のロゴも正方形のロゴも収まります。PNG、JPG、GIF、WebP、2 MB まで。", "Show the site name under the logo": "ロゴの下にサイト名を表示する", "They will no longer be available to anyone they were shared with.": "共有していた相手全員から利用できなくなります。", - "Uploading into :folder": "アップロード先: :folder" + "Uploading into :folder": "アップロード先: :folder", + "Select all :count matching files": "該当する :count 件のファイルをすべて選択", + "Restricted and empty files are skipped.": "制限された拡張子のファイルと空のファイルはスキップされます。", + "Import all": "すべてインポート", + "Importing in the background": "バックグラウンドでインポート中", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":total 件中 :imported 件をインポートしました。このページを離れても、インポートは続行されます。", + "Import finished": "インポートが完了しました", + ":imported of :total files imported.": ":total 件中 :imported 件をインポートしました。", + "The import stopped": "インポートが停止しました", + "The import stopped making progress": "インポートが進まなくなりました", + ":imported of :total files were imported before it failed: :error": "エラーで停止するまでに :total 件中 :imported 件をインポートしました: :error", + ":imported of :total files were imported. The queue worker may not be running.": ":total 件中 :imported 件をインポートしました。バックグラウンド処理が動いていない可能性があります。", + "Choose Import all again to continue with the rest.": "残りを続けるには、もう一度「すべてインポート」を選んでください。", + "An import is already running. Wait for it to finish.": "すでにインポートを実行中です。終わるまでお待ちください。", + "Importing :count file in the background.|Importing :count files in the background.": ":count 件のファイルをバックグラウンドでインポートしています。|:count 件のファイルをバックグラウンドでインポートしています。" } diff --git a/lang/nl.json b/lang/nl.json index 66f736ed..fea38d27 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Getoond tot 320 × 128 pixels, dus zowel een breed als een vierkant logo past. PNG, JPG, GIF of WebP, tot 2 MB.", "Show the site name under the logo": "Sitenaam onder het logo tonen", "They will no longer be available to anyone they were shared with.": "Ze zijn niet langer beschikbaar voor iedereen met wie ze gedeeld waren.", - "Uploading into :folder": "Uploaden naar :folder" + "Uploading into :folder": "Uploaden naar :folder", + "Select all :count matching files": "Alle :count overeenkomende bestanden selecteren", + "Restricted and empty files are skipped.": "Bestanden met een beperkte extensie en lege bestanden worden overgeslagen.", + "Import all": "Alles importeren", + "Importing in the background": "Importeren op de achtergrond", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported van :total bestanden geïmporteerd. Je kunt deze pagina verlaten — het importeren gaat door.", + "Import finished": "Importeren voltooid", + ":imported of :total files imported.": ":imported van :total bestanden geïmporteerd.", + "The import stopped": "Het importeren is gestopt", + "The import stopped making progress": "Het importeren schiet niet meer op", + ":imported of :total files were imported before it failed: :error": ":imported van :total bestanden zijn geïmporteerd voordat het misging: :error", + ":imported of :total files were imported. The queue worker may not be running.": ":imported van :total bestanden zijn geïmporteerd. Mogelijk draait het achtergrondproces niet.", + "Choose Import all again to continue with the rest.": "Kies opnieuw ‘Alles importeren’ om met de rest verder te gaan.", + "An import is already running. Wait for it to finish.": "Er loopt al een import. Wacht tot die klaar is.", + "Importing :count file in the background.|Importing :count files in the background.": ":count bestand wordt op de achtergrond geïmporteerd.|:count bestanden worden op de achtergrond geïmporteerd." } diff --git a/lang/pl.json b/lang/pl.json index 58b14b8e..762b6cc5 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Wyświetlane w rozmiarze do 320 × 128 pikseli, więc zmieści się zarówno szerokie, jak i kwadratowe logo. PNG, JPG, GIF lub WebP, do 2 MB.", "Show the site name under the logo": "Pokaż nazwę witryny pod logo", "They will no longer be available to anyone they were shared with.": "Przestaną być dostępne dla wszystkich, którym je udostępniono.", - "Uploading into :folder": "Przesyłanie do: :folder" + "Uploading into :folder": "Przesyłanie do: :folder", + "Select all :count matching files": "Zaznacz wszystkie pasujące pliki (:count)", + "Restricted and empty files are skipped.": "Pliki z ograniczonym rozszerzeniem i puste pliki są pomijane.", + "Import all": "Importuj wszystko", + "Importing in the background": "Importowanie w tle", + ":imported of :total files imported. You can leave this page — the import keeps going.": "Zaimportowano :imported z :total plików. Możesz opuścić tę stronę — import będzie trwał dalej.", + "Import finished": "Import zakończony", + ":imported of :total files imported.": "Zaimportowano :imported z :total plików.", + "The import stopped": "Import został przerwany", + "The import stopped making progress": "Import przestał postępować", + ":imported of :total files were imported before it failed: :error": "Przed błędem zaimportowano :imported z :total plików: :error", + ":imported of :total files were imported. The queue worker may not be running.": "Zaimportowano :imported z :total plików. Być może proces w tle nie działa.", + "Choose Import all again to continue with the rest.": "Wybierz ponownie „Importuj wszystko”, aby kontynuować z resztą.", + "An import is already running. Wait for it to finish.": "Import już trwa. Poczekaj, aż się zakończy.", + "Importing :count file in the background.|Importing :count files in the background.": "Importowanie :count pliku w tle.|Importowanie :count plików w tle.|Importowanie :count plików w tle." } diff --git a/lang/pt_BR.json b/lang/pt_BR.json index 93a66da0..6a97eff5 100644 --- a/lang/pt_BR.json +++ b/lang/pt_BR.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Exibido em até 320 × 128 pixels, então cabem tanto um logo largo quanto um quadrado. PNG, JPG, GIF ou WebP, até 2 MB.", "Show the site name under the logo": "Mostrar o nome do site abaixo do logo", "They will no longer be available to anyone they were shared with.": "Eles deixarão de estar disponíveis para todos com quem foram compartilhados.", - "Uploading into :folder": "Enviando para :folder" + "Uploading into :folder": "Enviando para :folder", + "Select all :count matching files": "Selecionar todos os :count arquivos correspondentes", + "Restricted and empty files are skipped.": "Arquivos com extensão restrita e arquivos vazios são ignorados.", + "Import all": "Importar todos", + "Importing in the background": "Importando em segundo plano", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":imported de :total arquivos importados. Você pode sair desta página — a importação continua.", + "Import finished": "Importação concluída", + ":imported of :total files imported.": ":imported de :total arquivos importados.", + "The import stopped": "A importação parou", + "The import stopped making progress": "A importação parou de avançar", + ":imported of :total files were imported before it failed: :error": ":imported de :total arquivos foram importados antes da falha: :error", + ":imported of :total files were imported. The queue worker may not be running.": ":imported de :total arquivos foram importados. Talvez o processo em segundo plano não esteja rodando.", + "Choose Import all again to continue with the rest.": "Escolha “Importar todos” de novo para continuar com o restante.", + "An import is already running. Wait for it to finish.": "Já existe uma importação em andamento. Espere ela terminar.", + "Importing :count file in the background.|Importing :count files in the background.": "Importando :count arquivo em segundo plano.|Importando :count arquivos em segundo plano." } diff --git a/lang/ru.json b/lang/ru.json index dd600a80..9d2750ce 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Показывается размером до 320 × 128 пикселей, поэтому поместится и широкий, и квадратный логотип. PNG, JPG, GIF или WebP, до 2 МБ.", "Show the site name under the logo": "Показывать название сайта под логотипом", "They will no longer be available to anyone they were shared with.": "Они больше не будут доступны никому, кому были открыты.", - "Uploading into :folder": "Загрузка в: :folder" + "Uploading into :folder": "Загрузка в: :folder", + "Select all :count matching files": "Выбрать все подходящие файлы (:count)", + "Restricted and empty files are skipped.": "Файлы с ограниченным расширением и пустые файлы пропускаются.", + "Import all": "Импортировать все", + "Importing in the background": "Импорт в фоновом режиме", + ":imported of :total files imported. You can leave this page — the import keeps going.": "Импортировано файлов: :imported из :total. Можно уйти с этой страницы — импорт продолжится.", + "Import finished": "Импорт завершён", + ":imported of :total files imported.": "Импортировано файлов: :imported из :total.", + "The import stopped": "Импорт остановлен", + "The import stopped making progress": "Импорт перестал продвигаться", + ":imported of :total files were imported before it failed: :error": "До ошибки импортировано файлов: :imported из :total. Ошибка: :error", + ":imported of :total files were imported. The queue worker may not be running.": "Импортировано файлов: :imported из :total. Возможно, фоновый обработчик не запущен.", + "Choose Import all again to continue with the rest.": "Чтобы продолжить с оставшимися, снова выберите «Импортировать все».", + "An import is already running. Wait for it to finish.": "Импорт уже идёт. Дождитесь его завершения.", + "Importing :count file in the background.|Importing :count files in the background.": "Импорт :count файла в фоновом режиме.|Импорт :count файлов в фоновом режиме.|Импорт :count файлов в фоновом режиме." } diff --git a/lang/sw.json b/lang/sw.json index fd375132..9927eee0 100644 --- a/lang/sw.json +++ b/lang/sw.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Huonyeshwa hadi pikseli 320 × 128, kwa hivyo nembo pana na ya mraba zote zinatosha. PNG, JPG, GIF au WebP, hadi MB 2.", "Show the site name under the logo": "Onyesha jina la tovuti chini ya nembo", "They will no longer be available to anyone they were shared with.": "Hayatapatikana tena kwa yeyote aliyeshirikishwa nayo.", - "Uploading into :folder": "Inapakia kwenye :folder" + "Uploading into :folder": "Inapakia kwenye :folder", + "Select all :count matching files": "Chagua mafaili yote :count yanayolingana", + "Restricted and empty files are skipped.": "Mafaili yenye kiendelezi kilichozuiwa na mafaili matupu yanarukwa.", + "Import all": "Ingiza yote", + "Importing in the background": "Inaingiza chinichini", + ":imported of :total files imported. You can leave this page — the import keeps going.": "Mafaili :imported kati ya :total yameingizwa. Unaweza kuondoka kwenye ukurasa huu — uingizaji unaendelea.", + "Import finished": "Uingizaji umekamilika", + ":imported of :total files imported.": "Mafaili :imported kati ya :total yameingizwa.", + "The import stopped": "Uingizaji umesimama", + "The import stopped making progress": "Uingizaji umeacha kusonga mbele", + ":imported of :total files were imported before it failed: :error": "Mafaili :imported kati ya :total yaliingizwa kabla ya hitilafu: :error", + ":imported of :total files were imported. The queue worker may not be running.": "Mafaili :imported kati ya :total yameingizwa. Huenda mchakato wa nyuma hauendeshwi.", + "Choose Import all again to continue with the rest.": "Chagua “Ingiza yote” tena ili kuendelea na yaliyosalia.", + "An import is already running. Wait for it to finish.": "Uingizaji mwingine tayari unaendelea. Subiri umalizike.", + "Importing :count file in the background.|Importing :count files in the background.": "Inaingiza faili :count chinichini.|Inaingiza mafaili :count chinichini." } diff --git a/lang/tr.json b/lang/tr.json index 875200cb..c97f24f1 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "En fazla 320 × 128 piksel olarak gösterilir; böylece hem geniş hem kare logolar sığar. PNG, JPG, GIF veya WebP, en fazla 2 MB.", "Show the site name under the logo": "Site adını logonun altında göster", "They will no longer be available to anyone they were shared with.": "Paylaşıldıkları hiç kimse için artık erişilebilir olmayacaklar.", - "Uploading into :folder": "Yükleme hedefi: :folder" + "Uploading into :folder": "Yükleme hedefi: :folder", + "Select all :count matching files": "Eşleşen :count dosyanın tümünü seç", + "Restricted and empty files are skipped.": "Kısıtlı uzantılı ve boş dosyalar atlanır.", + "Import all": "Tümünü içe aktar", + "Importing in the background": "Arka planda içe aktarılıyor", + ":imported of :total files imported. You can leave this page — the import keeps going.": ":total dosyadan :imported tanesi içe aktarıldı. Bu sayfadan ayrılabilirsiniz — içe aktarma devam eder.", + "Import finished": "İçe aktarma tamamlandı", + ":imported of :total files imported.": ":total dosyadan :imported tanesi içe aktarıldı.", + "The import stopped": "İçe aktarma durdu", + "The import stopped making progress": "İçe aktarma ilerlemiyor", + ":imported of :total files were imported before it failed: :error": "Hata oluşmadan önce :total dosyadan :imported tanesi içe aktarıldı: :error", + ":imported of :total files were imported. The queue worker may not be running.": ":total dosyadan :imported tanesi içe aktarıldı. Arka plan işleyicisi çalışmıyor olabilir.", + "Choose Import all again to continue with the rest.": "Kalanlarla devam etmek için yeniden “Tümünü içe aktar”ı seçin.", + "An import is already running. Wait for it to finish.": "Zaten bir içe aktarma sürüyor. Bitmesini bekleyin.", + "Importing :count file in the background.|Importing :count files in the background.": ":count dosya arka planda içe aktarılıyor.|:count dosya arka planda içe aktarılıyor." } diff --git a/lang/vi.json b/lang/vi.json index 9530ff50..8db3fc26 100644 --- a/lang/vi.json +++ b/lang/vi.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "Hiển thị tối đa 320 × 128 pixel, nên cả logo ngang lẫn logo vuông đều vừa. PNG, JPG, GIF hoặc WebP, tối đa 2 MB.", "Show the site name under the logo": "Hiển thị tên trang dưới logo", "They will no longer be available to anyone they were shared with.": "Chúng sẽ không còn khả dụng với bất kỳ ai từng được chia sẻ.", - "Uploading into :folder": "Đang tải lên vào :folder" + "Uploading into :folder": "Đang tải lên vào :folder", + "Select all :count matching files": "Chọn tất cả :count tệp khớp", + "Restricted and empty files are skipped.": "Các tệp có phần mở rộng bị hạn chế và tệp rỗng sẽ được bỏ qua.", + "Import all": "Nhập tất cả", + "Importing in the background": "Đang nhập trong nền", + ":imported of :total files imported. You can leave this page — the import keeps going.": "Đã nhập :imported/:total tệp. Bạn có thể rời trang này — việc nhập vẫn tiếp tục.", + "Import finished": "Đã nhập xong", + ":imported of :total files imported.": "Đã nhập :imported/:total tệp.", + "The import stopped": "Việc nhập đã dừng", + "The import stopped making progress": "Việc nhập không còn tiến triển", + ":imported of :total files were imported before it failed: :error": "Đã nhập :imported/:total tệp trước khi gặp lỗi: :error", + ":imported of :total files were imported. The queue worker may not be running.": "Đã nhập :imported/:total tệp. Có thể tiến trình nền không chạy.", + "Choose Import all again to continue with the rest.": "Hãy chọn lại “Nhập tất cả” để tiếp tục với phần còn lại.", + "An import is already running. Wait for it to finish.": "Đang có một lượt nhập chạy. Hãy đợi nó xong.", + "Importing :count file in the background.|Importing :count files in the background.": "Đang nhập :count tệp trong nền.|Đang nhập :count tệp trong nền." } diff --git a/lang/zh_CN.json b/lang/zh_CN.json index 3f74028f..548926b2 100644 --- a/lang/zh_CN.json +++ b/lang/zh_CN.json @@ -2283,5 +2283,19 @@ "Shown up to 320 × 128 pixels, so a wide logo and a square one both fit. PNG, JPG, GIF or WebP, up to 2 MB.": "最大按 320 × 128 像素显示,宽标志和方形标志都能放下。PNG、JPG、GIF 或 WebP,最大 2 MB。", "Show the site name under the logo": "在标志下方显示站点名称", "They will no longer be available to anyone they were shared with.": "这些文件将不再对任何已共享的人开放。", - "Uploading into :folder": "上传到 :folder" + "Uploading into :folder": "上传到 :folder", + "Select all :count matching files": "选择全部 :count 个匹配的文件", + "Restricted and empty files are skipped.": "受限扩展名的文件和空文件会被跳过。", + "Import all": "全部导入", + "Importing in the background": "正在后台导入", + ":imported of :total files imported. You can leave this page — the import keeps going.": "已导入 :imported / :total 个文件。你可以离开此页面——导入会继续进行。", + "Import finished": "导入完成", + ":imported of :total files imported.": "已导入 :imported / :total 个文件。", + "The import stopped": "导入已停止", + "The import stopped making progress": "导入不再有进展", + ":imported of :total files were imported before it failed: :error": "失败前已导入 :imported / :total 个文件::error", + ":imported of :total files were imported. The queue worker may not be running.": "已导入 :imported / :total 个文件。后台进程可能没有在运行。", + "Choose Import all again to continue with the rest.": "再次选择“全部导入”即可继续导入剩余文件。", + "An import is already running. Wait for it to finish.": "已有一个导入正在进行。请等它完成。", + "Importing :count file in the background.|Importing :count files in the background.": "正在后台导入 :count 个文件。|正在后台导入 :count 个文件。" }