feat(i18n): add Paraglide i18n with 5 locales (v2.3.22)
Some checks failed
CI / Backend (pull_request) Successful in 41s
CI / UI (pull_request) Failing after 27s
CI / UI (push) Failing after 27s
CI / Backend (push) Successful in 48s
Release / Test backend (push) Successful in 23s
Release / Check ui (push) Failing after 33s
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Failing after 52s
Release / Docker / backend (push) Failing after 11s
Release / Docker / runner (push) Successful in 1m51s
Release / Gitea Release (push) Has been skipped
Some checks failed
CI / Backend (pull_request) Successful in 41s
CI / UI (pull_request) Failing after 27s
CI / UI (push) Failing after 27s
CI / Backend (push) Successful in 48s
Release / Test backend (push) Successful in 23s
Release / Check ui (push) Failing after 33s
Release / Docker / ui (push) Has been skipped
Release / Docker / caddy (push) Failing after 52s
Release / Docker / backend (push) Failing after 11s
Release / Docker / runner (push) Successful in 1m51s
Release / Gitea Release (push) Has been skipped
- Install @inlang/paraglide-js v2.15.1; configure project.inlang settings - Add en/ru/id/pt-BR/fr message catalogues (~140 keys each) - Wire paraglideVitePlugin in vite.config.ts, reroute hook in hooks.ts, and paraglideHandle middleware in hooks.server.ts - Migrate all routes and shared components to use m.*() message calls - Fix duplicate onMount body in chapters/[n]/+page.svelte - Build passes; svelte-check: 0 errors, 3 pre-existing warnings
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import type { PageData } from './$types';
|
||||
import type { ScrapingTask } from '$lib/server/pocketbase';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
@@ -228,15 +229,15 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Scrape tasks — libnovel admin</title>
|
||||
<title>{m.admin_scrape_page_title()}</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<h1 class="text-xl font-semibold text-(--color-text) flex-1">Scrape</h1>
|
||||
<h1 class="text-xl font-semibold text-(--color-text) flex-1">{m.admin_scrape_heading()}</h1>
|
||||
<span class="text-xs {running ? 'text-(--color-brand) animate-pulse' : 'text-green-500'}">
|
||||
● {running ? 'Running' : 'Idle'}
|
||||
● {running ? m.admin_scrape_status_running() : m.admin_scrape_status_idle()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -244,20 +245,20 @@
|
||||
<div class="divide-y divide-(--color-border) border border-(--color-border) rounded-xl overflow-hidden">
|
||||
<!-- Full catalogue -->
|
||||
<div class="flex items-center gap-4 px-4 py-3 bg-(--color-surface)">
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">Full catalogue</span>
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">{m.admin_scrape_full_catalogue()}</span>
|
||||
<button
|
||||
onclick={triggerCatalogueScrape}
|
||||
disabled={running || cataloguing}
|
||||
class="px-3 py-1.5 rounded-md bg-(--color-brand) text-(--color-surface) font-semibold text-xs hover:bg-(--color-brand-dim) transition-colors disabled:opacity-50"
|
||||
>
|
||||
{cataloguing ? 'Queuing…' : running ? 'Running…' : 'Start scrape'}
|
||||
{cataloguing ? m.admin_scrape_queuing() : running ? m.admin_scrape_running() : m.admin_scrape_start()}
|
||||
</button>
|
||||
{#if catalogueError}<span class="text-xs text-(--color-danger)">{catalogueError}</span>{/if}
|
||||
</div>
|
||||
|
||||
<!-- Single book -->
|
||||
<div id="book-form" class="flex items-center gap-3 px-4 py-3 bg-(--color-surface)">
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">Single book</span>
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">{m.admin_scrape_single_book()}</span>
|
||||
<input
|
||||
type="url"
|
||||
bind:value={scrapeUrl}
|
||||
@@ -269,14 +270,14 @@
|
||||
disabled={!scrapeUrl.trim() || running || scraping}
|
||||
class="shrink-0 px-3 py-1.5 rounded-md bg-(--color-surface-3) text-(--color-text) font-medium text-xs hover:bg-zinc-600 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{scraping ? 'Queuing…' : 'Scrape'}
|
||||
{scraping ? m.admin_scrape_queuing() : m.admin_scrape_submit()}
|
||||
</button>
|
||||
{#if scrapeError}<span class="text-xs text-(--color-danger)">{scrapeError}</span>{/if}
|
||||
</div>
|
||||
|
||||
<!-- Range scrape -->
|
||||
<div id="range-form" class="flex items-center gap-3 px-4 py-3 bg-(--color-surface) flex-wrap">
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">Chapter range</span>
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">{m.admin_scrape_range()}</span>
|
||||
<input
|
||||
type="url"
|
||||
bind:value={rangeUrl}
|
||||
@@ -302,14 +303,14 @@
|
||||
disabled={!rangeUrl.trim() || rangeFrom === null || running || ranging}
|
||||
class="shrink-0 px-3 py-1.5 rounded-md bg-(--color-surface-3) text-(--color-text) font-medium text-xs hover:bg-zinc-600 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{ranging ? 'Queuing…' : 'Go'}
|
||||
{ranging ? m.admin_scrape_queuing() : 'Go'}
|
||||
</button>
|
||||
{#if rangeError}<span class="text-xs text-(--color-danger) w-full pl-40">{rangeError}</span>{/if}
|
||||
</div>
|
||||
|
||||
<!-- Quick genre chips -->
|
||||
<div class="flex items-center gap-3 px-4 py-3 bg-(--color-surface) flex-wrap">
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">Quick genres</span>
|
||||
<span class="text-sm text-(--color-muted) w-36 shrink-0">{m.admin_scrape_quick_genres()}</span>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{#each quickScrapes as qs}
|
||||
<button
|
||||
@@ -334,18 +335,18 @@
|
||||
<!-- Tasks table -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<h2 class="text-sm font-semibold text-(--color-muted) flex-1 uppercase tracking-widest">Task history</h2>
|
||||
<h2 class="text-sm font-semibold text-(--color-muted) flex-1 uppercase tracking-widest">{m.admin_scrape_task_history()}</h2>
|
||||
<input
|
||||
type="search"
|
||||
bind:value={q}
|
||||
placeholder="Filter by kind, status or URL…"
|
||||
placeholder={m.admin_scrape_filter_placeholder()}
|
||||
class="w-full max-w-xs bg-(--color-surface-2) border border-(--color-border) rounded-lg px-3 py-2 text-(--color-text) text-sm placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-(--color-brand)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if filtered.length === 0}
|
||||
<p class="text-(--color-muted) text-sm py-8 text-center">
|
||||
{q.trim() ? 'No matching tasks.' : 'No scrape tasks yet.'}
|
||||
{q.trim() ? m.admin_scrape_no_matching() : m.admin_tasks_empty()}
|
||||
</p>
|
||||
{:else}
|
||||
<!-- Desktop table -->
|
||||
@@ -393,7 +394,7 @@
|
||||
disabled={cancellingIds.has(task.id)}
|
||||
class="px-2 py-1 rounded text-xs font-medium bg-(--color-surface-3) text-(--color-text) hover:bg-red-900 hover:text-red-300 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{cancellingIds.has(task.id) ? 'Cancelling…' : 'Cancel'}
|
||||
{cancellingIds.has(task.id) ? 'Cancelling…' : m.admin_scrape_cancel()}
|
||||
</button>
|
||||
{/if}
|
||||
{#if task.kind === 'book_range' && task.status !== 'pending' && task.status !== 'running' && (task.chapters_scraped ?? 0) > 0}
|
||||
@@ -461,7 +462,7 @@
|
||||
disabled={cancellingIds.has(task.id)}
|
||||
class="flex-1 px-3 py-1.5 rounded-lg text-xs font-medium bg-(--color-surface-3) text-(--color-text) hover:bg-red-900 hover:text-red-300 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{cancellingIds.has(task.id) ? 'Cancelling…' : 'Cancel task'}
|
||||
{cancellingIds.has(task.id) ? 'Cancelling…' : m.admin_scrape_cancel()} task
|
||||
</button>
|
||||
{/if}
|
||||
{#if task.kind === 'book_range' && task.status !== 'pending' && task.status !== 'running' && (task.chapters_scraped ?? 0) > 0}
|
||||
|
||||
Reference in New Issue
Block a user