Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
809dc8d898 | ||
|
|
e9c3426fbe | ||
|
|
8e611840d1 | ||
|
|
b9383570e3 | ||
|
|
eac9358c6f | ||
|
|
9cb11bc5e4 | ||
|
|
7196f8e930 | ||
|
|
a771405db8 | ||
|
|
1e9a96aa0f | ||
|
|
23ae1ed500 | ||
|
|
e7cb460f9b | ||
|
|
392248e8a6 | ||
|
|
68ea2d2808 | ||
|
|
7b1df9b592 | ||
|
|
f4089fe111 | ||
|
|
87b5ad1460 | ||
|
|
168cb52ed0 | ||
|
|
e1621a3ec2 | ||
|
|
10c7a48bc6 | ||
|
|
8b597c0bd2 | ||
|
|
28cafe2aa8 | ||
|
|
65f0425b61 | ||
|
|
4e70a2981d | ||
|
|
004cb95e56 | ||
|
|
aca649039c | ||
|
|
8d95411139 | ||
|
|
f9a4a0e416 | ||
|
|
a4d94f522a | ||
|
|
34c8fab358 | ||
|
|
d54769ab12 | ||
|
|
d2a4edba43 | ||
|
|
4e7f8c6266 | ||
|
|
b0a4cb8b3d | ||
|
|
f136ce6a60 | ||
|
|
3bd1112a63 | ||
|
|
278e292956 | ||
|
|
76de5eb491 | ||
|
|
c6597c8d19 | ||
|
|
e8d7108753 | ||
|
|
90dbecfa17 | ||
|
|
2deb306419 | ||
|
|
fd283bf6c6 |
@@ -2,11 +2,8 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "backend/**"
|
||||
- "ui/**"
|
||||
- ".gitea/workflows/ci.yaml"
|
||||
pull_request:
|
||||
tags-ignore:
|
||||
- "v*"
|
||||
paths:
|
||||
- "backend/**"
|
||||
- "ui/**"
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,6 +6,8 @@
|
||||
|
||||
# ── Compiled binaries ──────────────────────────────────────────────────────────
|
||||
backend/bin/
|
||||
backend/backend
|
||||
backend/runner
|
||||
|
||||
# ── Environment & secrets ──────────────────────────────────────────────────────
|
||||
# Secrets are managed by Doppler — never commit .env files.
|
||||
|
||||
28
Caddyfile
28
Caddyfile
@@ -58,16 +58,22 @@
|
||||
|
||||
# ── Redis TCP proxy via layer4 ────────────────────────────────────────────
|
||||
# Exposes prod Redis over TLS for Asynq job enqueueing from the homelab runner.
|
||||
# Listens on :6380 (all interfaces). TLS is terminated here using the cert
|
||||
# Listens on :6380 (all interfaces). TLS is terminated here using the cert
|
||||
# for redis.libnovel.cc; traffic is proxied to the local Redis sidecar.
|
||||
# Requires the caddy-l4 module in the custom Caddy build.
|
||||
# Requires the caddy-l4 module in the custom Caddy build.
|
||||
layer4 {
|
||||
:6380 {
|
||||
route {
|
||||
tls {
|
||||
proxy {
|
||||
connection_policy {
|
||||
match {
|
||||
sni redis.libnovel.cc
|
||||
}
|
||||
}
|
||||
}
|
||||
proxy {
|
||||
upstream redis:6379
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,6 +217,11 @@
|
||||
file_server
|
||||
}
|
||||
handle_errors 500 {
|
||||
root * /srv/errors
|
||||
rewrite * /500.html
|
||||
file_server
|
||||
}
|
||||
handle_errors 502 {
|
||||
root * /srv/errors
|
||||
rewrite * /502.html
|
||||
file_server
|
||||
@@ -269,3 +280,12 @@ search.libnovel.cc {
|
||||
reverse_proxy meilisearch:7700
|
||||
}
|
||||
|
||||
# ── Redis TLS cert anchor ─────────────────────────────────────────────────────
|
||||
# This virtual host exists solely so Caddy obtains and caches a TLS certificate
|
||||
# for redis.libnovel.cc. The layer4 block above uses that cert to terminate TLS
|
||||
# on :6380 (Asynq job-queue channel from prod → homelab Redis).
|
||||
# The HTTP route itself just returns 404 — no real traffic expected here.
|
||||
redis.libnovel.cc {
|
||||
respond 404
|
||||
}
|
||||
}
|
||||
|
||||
BIN
backend/backend
BIN
backend/backend
Binary file not shown.
@@ -15,6 +15,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -133,7 +134,7 @@ func run() error {
|
||||
if parseErr != nil {
|
||||
return fmt.Errorf("parse REDIS_ADDR: %w", parseErr)
|
||||
}
|
||||
asynqProducer := asynqqueue.NewProducer(store, redisOpt)
|
||||
asynqProducer := asynqqueue.NewProducer(store, redisOpt, log)
|
||||
defer asynqProducer.Close() //nolint:errcheck
|
||||
producer = asynqProducer
|
||||
log.Info("backend: asynq task dispatch enabled", "addr", cfg.Redis.Addr)
|
||||
@@ -195,6 +196,14 @@ func (n *noopKokoro) GenerateAudio(_ context.Context, _, _ string) ([]byte, erro
|
||||
return nil, fmt.Errorf("kokoro not configured (KOKORO_URL is empty)")
|
||||
}
|
||||
|
||||
func (n *noopKokoro) StreamAudioMP3(_ context.Context, _, _ string) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("kokoro not configured (KOKORO_URL is empty)")
|
||||
}
|
||||
|
||||
func (n *noopKokoro) StreamAudioWAV(_ context.Context, _, _ string) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("kokoro not configured (KOKORO_URL is empty)")
|
||||
}
|
||||
|
||||
func (n *noopKokoro) ListVoices(_ context.Context) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -222,6 +223,14 @@ func (n *noopKokoro) GenerateAudio(_ context.Context, _, _ string) ([]byte, erro
|
||||
return nil, fmt.Errorf("kokoro not configured (KOKORO_URL is empty)")
|
||||
}
|
||||
|
||||
func (n *noopKokoro) StreamAudioMP3(_ context.Context, _, _ string) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("kokoro not configured (KOKORO_URL is empty)")
|
||||
}
|
||||
|
||||
func (n *noopKokoro) StreamAudioWAV(_ context.Context, _, _ string) (io.ReadCloser, error) {
|
||||
return nil, fmt.Errorf("kokoro not configured (KOKORO_URL is empty)")
|
||||
}
|
||||
|
||||
func (n *noopKokoro) ListVoices(_ context.Context) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -10,14 +10,13 @@ import (
|
||||
|
||||
// Consumer wraps the PocketBase-backed Consumer for result write-back only.
|
||||
//
|
||||
// When using Asynq, the runner no longer polls for work — Asynq delivers
|
||||
// tasks via the ServeMux handlers. The only Consumer operations the handlers
|
||||
// need are:
|
||||
// - FinishAudioTask / FinishScrapeTask — write result back to PocketBase
|
||||
// - FailTask — mark PocketBase record as failed
|
||||
// When using Asynq, the runner no longer polls for scrape/audio work — Asynq
|
||||
// delivers those tasks via the ServeMux handlers. However translation tasks
|
||||
// live in PocketBase (not Redis), so ClaimNextTranslationTask and HeartbeatTask
|
||||
// still delegate to the underlying PocketBase consumer.
|
||||
//
|
||||
// ClaimNextAudioTask, ClaimNextScrapeTask, HeartbeatTask, and ReapStaleTasks
|
||||
// are all no-ops here because Asynq owns those responsibilities.
|
||||
// ClaimNextAudioTask, ClaimNextScrapeTask are no-ops here because Asynq owns
|
||||
// those responsibilities.
|
||||
type Consumer struct {
|
||||
pb taskqueue.Consumer // underlying PocketBase consumer (for write-back)
|
||||
}
|
||||
@@ -55,10 +54,18 @@ func (c *Consumer) ClaimNextAudioTask(_ context.Context, _ string) (domain.Audio
|
||||
return domain.AudioTask{}, false, nil
|
||||
}
|
||||
|
||||
func (c *Consumer) ClaimNextTranslationTask(_ context.Context, _ string) (domain.TranslationTask, bool, error) {
|
||||
return domain.TranslationTask{}, false, nil
|
||||
// ClaimNextTranslationTask delegates to PocketBase because translation tasks
|
||||
// are stored in PocketBase (not Redis/Asynq) and must still be polled directly.
|
||||
func (c *Consumer) ClaimNextTranslationTask(ctx context.Context, workerID string) (domain.TranslationTask, bool, error) {
|
||||
return c.pb.ClaimNextTranslationTask(ctx, workerID)
|
||||
}
|
||||
|
||||
func (c *Consumer) HeartbeatTask(_ context.Context, _ string) error { return nil }
|
||||
func (c *Consumer) HeartbeatTask(ctx context.Context, id string) error {
|
||||
return c.pb.HeartbeatTask(ctx, id)
|
||||
}
|
||||
|
||||
func (c *Consumer) ReapStaleTasks(_ context.Context, _ time.Duration) (int, error) { return 0, nil }
|
||||
// ReapStaleTasks delegates to PocketBase so stale translation tasks are reset
|
||||
// to pending and can be reclaimed.
|
||||
func (c *Consumer) ReapStaleTasks(ctx context.Context, staleAfter time.Duration) (int, error) {
|
||||
return c.pb.ReapStaleTasks(ctx, staleAfter)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/libnovel/backend/internal/taskqueue"
|
||||
@@ -14,13 +15,15 @@ import (
|
||||
type Producer struct {
|
||||
pb taskqueue.Producer // underlying PocketBase producer
|
||||
client *asynq.Client
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// NewProducer wraps an existing PocketBase Producer with Asynq dispatch.
|
||||
func NewProducer(pb taskqueue.Producer, redisOpt asynq.RedisConnOpt) *Producer {
|
||||
func NewProducer(pb taskqueue.Producer, redisOpt asynq.RedisConnOpt, log *slog.Logger) *Producer {
|
||||
return &Producer{
|
||||
pb: pb,
|
||||
client: asynq.NewClient(redisOpt),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +52,9 @@ func (p *Producer) CreateScrapeTask(ctx context.Context, kind, targetURL string,
|
||||
}
|
||||
if err := p.enqueue(ctx, taskType, payload); err != nil {
|
||||
// Non-fatal: PB record exists; runner will pick it up on next poll.
|
||||
return id, fmt.Errorf("asynq enqueue scrape (task still in PB): %w", err)
|
||||
p.log.Warn("asynq enqueue scrape failed (task still in PB, runner will poll)",
|
||||
"task_id", id, "err", err)
|
||||
return id, nil
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -68,7 +73,10 @@ func (p *Producer) CreateAudioTask(ctx context.Context, slug string, chapter int
|
||||
Voice: voice,
|
||||
}
|
||||
if err := p.enqueue(ctx, TypeAudioGenerate, payload); err != nil {
|
||||
return id, fmt.Errorf("asynq enqueue audio (task still in PB): %w", err)
|
||||
// Non-fatal: PB record exists; runner will pick it up on next poll.
|
||||
p.log.Warn("asynq enqueue audio failed (task still in PB, runner will poll)",
|
||||
"task_id", id, "err", err)
|
||||
return id, nil
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -85,6 +93,12 @@ func (p *Producer) CancelTask(ctx context.Context, id string) error {
|
||||
return p.pb.CancelTask(ctx, id)
|
||||
}
|
||||
|
||||
// CancelAudioTasksBySlug delegates to PocketBase to cancel all pending/running
|
||||
// audio tasks for slug.
|
||||
func (p *Producer) CancelAudioTasksBySlug(ctx context.Context, slug string) (int, error) {
|
||||
return p.pb.CancelAudioTasksBySlug(ctx, slug)
|
||||
}
|
||||
|
||||
// enqueue serialises payload and dispatches it to Asynq.
|
||||
func (p *Producer) enqueue(_ context.Context, taskType string, payload any) error {
|
||||
b, err := json.Marshal(payload)
|
||||
|
||||
@@ -8,7 +8,7 @@ package backend
|
||||
// handleBrowse, handleSearch
|
||||
// handleGetRanking, handleGetCover
|
||||
// handleBookPreview, handleChapterText, handleChapterTextPreview, handleChapterMarkdown, handleReindex
|
||||
// handleAudioGenerate, handleAudioStatus, handleAudioProxy
|
||||
// handleAudioGenerate, handleAudioStatus, handleAudioProxy, handleAudioStream
|
||||
// handleVoices
|
||||
// handlePresignChapter, handlePresignAudio, handlePresignVoiceSample
|
||||
// handlePresignAvatarUpload, handlePresignAvatar
|
||||
@@ -703,6 +703,170 @@ func (s *Server) handleAudioProxy(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, presignURL, http.StatusFound)
|
||||
}
|
||||
|
||||
// handleAudioStream handles GET /api/audio-stream/{slug}/{n}.
|
||||
//
|
||||
// Fast path: if audio already exists in MinIO, redirects to the presigned URL
|
||||
// (same as handleAudioProxy) — the client plays from storage immediately.
|
||||
//
|
||||
// Slow path (first request): streams audio directly to the client while
|
||||
// simultaneously uploading it to MinIO. After the stream completes, subsequent
|
||||
// requests hit the fast path and skip TTS generation entirely.
|
||||
//
|
||||
// Query params:
|
||||
//
|
||||
// voice (optional, defaults to DefaultVoice)
|
||||
// format (optional, "mp3" or "wav"; defaults to "mp3")
|
||||
//
|
||||
// Using format=wav skips the ffmpeg transcode for pocket-tts voices, delivering
|
||||
// raw WAV frames to the client with lower latency at the cost of larger files.
|
||||
func (s *Server) handleAudioStream(w http.ResponseWriter, r *http.Request) {
|
||||
slug := r.PathValue("slug")
|
||||
n, err := strconv.Atoi(r.PathValue("n"))
|
||||
if err != nil || n < 1 {
|
||||
jsonError(w, http.StatusBadRequest, "invalid chapter")
|
||||
return
|
||||
}
|
||||
|
||||
voice := r.URL.Query().Get("voice")
|
||||
if voice == "" {
|
||||
voice = s.cfg.DefaultVoice
|
||||
}
|
||||
|
||||
format := r.URL.Query().Get("format")
|
||||
if format != "wav" {
|
||||
format = "mp3"
|
||||
}
|
||||
|
||||
contentType := "audio/mpeg"
|
||||
if format == "wav" {
|
||||
contentType = "audio/wav"
|
||||
}
|
||||
|
||||
audioKey := s.deps.AudioStore.AudioObjectKeyExt(slug, n, voice, format)
|
||||
|
||||
// ── Fast path: already in MinIO ───────────────────────────────────────────
|
||||
if s.deps.AudioStore.AudioExists(r.Context(), audioKey) {
|
||||
presignURL, err := s.deps.PresignStore.PresignAudio(r.Context(), audioKey, 1*time.Hour)
|
||||
if err != nil {
|
||||
s.deps.Log.Error("handleAudioStream: PresignAudio failed", "slug", slug, "n", n, "err", err)
|
||||
jsonError(w, http.StatusInternalServerError, "presign failed")
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, presignURL, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// ── Slow path: generate + stream + save ───────────────────────────────────
|
||||
|
||||
// Read the chapter text.
|
||||
raw, err := s.deps.BookReader.ReadChapter(r.Context(), slug, n)
|
||||
if err != nil {
|
||||
s.deps.Log.Error("handleAudioStream: ReadChapter failed", "slug", slug, "n", n, "err", err)
|
||||
jsonError(w, http.StatusNotFound, "chapter not found")
|
||||
return
|
||||
}
|
||||
text := stripMarkdown(raw)
|
||||
if text == "" {
|
||||
jsonError(w, http.StatusUnprocessableEntity, "chapter text is empty")
|
||||
return
|
||||
}
|
||||
|
||||
// Open the TTS stream (WAV or MP3 depending on format param).
|
||||
var audioStream io.ReadCloser
|
||||
if format == "wav" {
|
||||
if pockettts.IsPocketTTSVoice(voice) {
|
||||
if s.deps.PocketTTS == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "pocket-tts not configured")
|
||||
return
|
||||
}
|
||||
audioStream, err = s.deps.PocketTTS.StreamAudioWAV(r.Context(), text, voice)
|
||||
} else {
|
||||
if s.deps.Kokoro == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "kokoro not configured")
|
||||
return
|
||||
}
|
||||
audioStream, err = s.deps.Kokoro.StreamAudioWAV(r.Context(), text, voice)
|
||||
}
|
||||
} else {
|
||||
if pockettts.IsPocketTTSVoice(voice) {
|
||||
if s.deps.PocketTTS == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "pocket-tts not configured")
|
||||
return
|
||||
}
|
||||
audioStream, err = s.deps.PocketTTS.StreamAudioMP3(r.Context(), text, voice)
|
||||
} else {
|
||||
if s.deps.Kokoro == nil {
|
||||
jsonError(w, http.StatusServiceUnavailable, "kokoro not configured")
|
||||
return
|
||||
}
|
||||
audioStream, err = s.deps.Kokoro.StreamAudioMP3(r.Context(), text, voice)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
s.deps.Log.Error("handleAudioStream: TTS stream failed", "slug", slug, "n", n, "voice", voice, "format", format, "err", err)
|
||||
jsonError(w, http.StatusInternalServerError, "tts stream failed")
|
||||
return
|
||||
}
|
||||
defer audioStream.Close()
|
||||
|
||||
// Tee: every byte read from audioStream is written to both the HTTP
|
||||
// response and a pipe that feeds the MinIO upload goroutine.
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
// MinIO upload runs concurrently. Size -1 triggers multipart upload.
|
||||
uploadDone := make(chan error, 1)
|
||||
go func() {
|
||||
uploadDone <- s.deps.AudioStore.PutAudioStream(
|
||||
context.Background(), // use background — request ctx may cancel after client disconnects
|
||||
audioKey, pr, -1, contentType,
|
||||
)
|
||||
}()
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("X-Accel-Buffering", "no") // disable nginx/caddy buffering
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
flusher, canFlush := w.(http.Flusher)
|
||||
|
||||
tee := io.TeeReader(audioStream, pw)
|
||||
buf := make([]byte, 32*1024)
|
||||
for {
|
||||
nr, readErr := tee.Read(buf)
|
||||
if nr > 0 {
|
||||
if _, writeErr := w.Write(buf[:nr]); writeErr != nil {
|
||||
// Client disconnected — abort upload pipe so goroutine exits.
|
||||
pw.CloseWithError(writeErr)
|
||||
<-uploadDone
|
||||
return
|
||||
}
|
||||
if canFlush {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
if readErr != nil {
|
||||
if readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
s.deps.Log.Warn("handleAudioStream: read error mid-stream", "err", readErr)
|
||||
pw.CloseWithError(readErr)
|
||||
<-uploadDone
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Signal end of stream to the MinIO upload goroutine.
|
||||
pw.Close()
|
||||
if uploadErr := <-uploadDone; uploadErr != nil {
|
||||
s.deps.Log.Error("handleAudioStream: MinIO upload failed", "key", audioKey, "err", uploadErr)
|
||||
// Audio was already streamed to the client — just log; don't error.
|
||||
// The next request will re-stream since the object is absent.
|
||||
}
|
||||
// Note: we do not call FinishAudioTask here — the backend has no Consumer.
|
||||
// handleAudioStatus fast-paths on AudioExists, so the UI will see "done"
|
||||
// on its next poll as soon as the MinIO object is present.
|
||||
}
|
||||
|
||||
// ── Translation ────────────────────────────────────────────────────────────────
|
||||
|
||||
// supportedTranslationLangs is the set of target locales the backend accepts.
|
||||
@@ -948,6 +1112,166 @@ func (s *Server) handleAdminTranslationBulk(w http.ResponseWriter, r *http.Reque
|
||||
})
|
||||
}
|
||||
|
||||
// ── Admin Audio ────────────────────────────────────────────────────────────────
|
||||
|
||||
// handleAdminAudioJobs handles GET /api/admin/audio/jobs.
|
||||
// Returns all audio jobs, optionally filtered by slug (?slug=...).
|
||||
// Sorted by started descending.
|
||||
func (s *Server) handleAdminAudioJobs(w http.ResponseWriter, r *http.Request) {
|
||||
tasks, err := s.deps.TaskReader.ListAudioTasks(r.Context())
|
||||
if err != nil {
|
||||
s.deps.Log.Error("handleAdminAudioJobs: ListAudioTasks failed", "err", err)
|
||||
jsonError(w, http.StatusInternalServerError, "failed to list audio jobs")
|
||||
return
|
||||
}
|
||||
|
||||
// Optional slug filter.
|
||||
slugFilter := r.URL.Query().Get("slug")
|
||||
|
||||
type jobRow struct {
|
||||
ID string `json:"id"`
|
||||
CacheKey string `json:"cache_key"`
|
||||
Slug string `json:"slug"`
|
||||
Chapter int `json:"chapter"`
|
||||
Voice string `json:"voice"`
|
||||
Status string `json:"status"`
|
||||
WorkerID string `json:"worker_id"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
Started string `json:"started"`
|
||||
Finished string `json:"finished"`
|
||||
}
|
||||
rows := make([]jobRow, 0, len(tasks))
|
||||
for _, t := range tasks {
|
||||
if slugFilter != "" && t.Slug != slugFilter {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, jobRow{
|
||||
ID: t.ID,
|
||||
CacheKey: t.CacheKey,
|
||||
Slug: t.Slug,
|
||||
Chapter: t.Chapter,
|
||||
Voice: t.Voice,
|
||||
Status: string(t.Status),
|
||||
WorkerID: t.WorkerID,
|
||||
ErrorMessage: t.ErrorMessage,
|
||||
Started: t.Started.Format(time.RFC3339),
|
||||
Finished: t.Finished.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
writeJSON(w, 0, map[string]any{"jobs": rows, "total": len(rows)})
|
||||
}
|
||||
|
||||
// handleAdminAudioBulk handles POST /api/admin/audio/bulk.
|
||||
// Body: {"slug": "...", "voice": "af_bella", "from": 1, "to": 100, "skip_existing": true}
|
||||
//
|
||||
// Enqueues one audio task per chapter in [from, to].
|
||||
// skip_existing (default true): skip chapters already cached in MinIO — use this
|
||||
// to resume a previously interrupted bulk job.
|
||||
// force: if true, enqueue even when a pending/running task already exists.
|
||||
// Max 1000 chapters per request.
|
||||
func (s *Server) handleAdminAudioBulk(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct {
|
||||
Slug string `json:"slug"`
|
||||
Voice string `json:"voice"`
|
||||
From int `json:"from"`
|
||||
To int `json:"to"`
|
||||
SkipExisting *bool `json:"skip_existing"` // pointer so we can detect omission
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
if body.Slug == "" {
|
||||
jsonError(w, http.StatusBadRequest, "slug is required")
|
||||
return
|
||||
}
|
||||
if body.Voice == "" {
|
||||
body.Voice = s.cfg.DefaultVoice
|
||||
}
|
||||
if body.From < 1 || body.To < body.From {
|
||||
jsonError(w, http.StatusBadRequest, "from must be >= 1 and to must be >= from")
|
||||
return
|
||||
}
|
||||
if body.To-body.From > 999 {
|
||||
jsonError(w, http.StatusBadRequest, "range too large; max 1000 chapters per request")
|
||||
return
|
||||
}
|
||||
|
||||
// skip_existing defaults to true (resume-friendly).
|
||||
skipExisting := true
|
||||
if body.SkipExisting != nil {
|
||||
skipExisting = *body.SkipExisting
|
||||
}
|
||||
|
||||
var taskIDs []string
|
||||
skipped := 0
|
||||
|
||||
for n := body.From; n <= body.To; n++ {
|
||||
// Skip chapters already cached in MinIO.
|
||||
if skipExisting {
|
||||
audioKey := s.deps.AudioStore.AudioObjectKey(body.Slug, n, body.Voice)
|
||||
if s.deps.AudioStore.AudioExists(r.Context(), audioKey) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Skip chapters with an active (pending/running) task unless force=true.
|
||||
if !body.Force {
|
||||
cacheKey := fmt.Sprintf("%s/%d/%s", body.Slug, n, body.Voice)
|
||||
existing, found, _ := s.deps.TaskReader.GetAudioTask(r.Context(), cacheKey)
|
||||
if found && (existing.Status == domain.TaskStatusPending || existing.Status == domain.TaskStatusRunning) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
id, err := s.deps.Producer.CreateAudioTask(r.Context(), body.Slug, n, body.Voice)
|
||||
if err != nil {
|
||||
s.deps.Log.Error("handleAdminAudioBulk: CreateAudioTask failed",
|
||||
"slug", body.Slug, "chapter", n, "voice", body.Voice, "err", err)
|
||||
jsonError(w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("failed to create task for chapter %d: %s", n, err))
|
||||
return
|
||||
}
|
||||
taskIDs = append(taskIDs, id)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusAccepted, map[string]any{
|
||||
"enqueued": len(taskIDs),
|
||||
"skipped": skipped,
|
||||
"task_ids": taskIDs,
|
||||
})
|
||||
}
|
||||
|
||||
// handleAdminAudioCancelBulk handles POST /api/admin/audio/cancel-bulk.
|
||||
// Body: {"slug": "..."}
|
||||
// Cancels all pending and running audio tasks for the given slug.
|
||||
func (s *Server) handleAdminAudioCancelBulk(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct {
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
jsonError(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
if body.Slug == "" {
|
||||
jsonError(w, http.StatusBadRequest, "slug is required")
|
||||
return
|
||||
}
|
||||
|
||||
cancelled, err := s.deps.Producer.CancelAudioTasksBySlug(r.Context(), body.Slug)
|
||||
if err != nil {
|
||||
s.deps.Log.Error("handleAdminAudioCancelBulk: CancelAudioTasksBySlug failed",
|
||||
"slug", body.Slug, "err", err)
|
||||
jsonError(w, http.StatusInternalServerError, "failed to cancel tasks")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, 0, map[string]any{"cancelled": cancelled})
|
||||
}
|
||||
|
||||
// ── Voices ─────────────────────────────────────────────────────────────────────
|
||||
// Returns {"voices": [...]} — merged list from Kokoro and pocket-tts.
|
||||
func (s *Server) handleVoices(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -161,6 +161,9 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
|
||||
mux.HandleFunc("POST /api/audio/{slug}/{n}", s.handleAudioGenerate)
|
||||
mux.HandleFunc("GET /api/audio/status/{slug}/{n}", s.handleAudioStatus)
|
||||
mux.HandleFunc("GET /api/audio-proxy/{slug}/{n}", s.handleAudioProxy)
|
||||
// Streaming audio: serves from MinIO if cached, else streams live TTS
|
||||
// while simultaneously uploading to MinIO for future requests.
|
||||
mux.HandleFunc("GET /api/audio-stream/{slug}/{n}", s.handleAudioStream)
|
||||
|
||||
// Translation task creation (backend creates task; runner executes via LibreTranslate)
|
||||
mux.HandleFunc("POST /api/translation/{slug}/{n}", s.handleTranslationGenerate)
|
||||
@@ -171,6 +174,11 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
|
||||
mux.HandleFunc("GET /api/admin/translation/jobs", s.handleAdminTranslationJobs)
|
||||
mux.HandleFunc("POST /api/admin/translation/bulk", s.handleAdminTranslationBulk)
|
||||
|
||||
// Admin audio endpoints
|
||||
mux.HandleFunc("GET /api/admin/audio/jobs", s.handleAdminAudioJobs)
|
||||
mux.HandleFunc("POST /api/admin/audio/bulk", s.handleAdminAudioBulk)
|
||||
mux.HandleFunc("POST /api/admin/audio/cancel-bulk", s.handleAdminAudioCancelBulk)
|
||||
|
||||
// Voices list
|
||||
mux.HandleFunc("GET /api/voices", s.handleVoices)
|
||||
|
||||
@@ -199,7 +207,7 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
|
||||
Addr: s.cfg.Addr,
|
||||
Handler: handler,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 60 * time.Second,
|
||||
WriteTimeout: 15 * time.Minute, // audio-stream can take several minutes for a full chapter
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ package bookstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/libnovel/backend/internal/domain"
|
||||
@@ -79,14 +80,24 @@ type RankingStore interface {
|
||||
|
||||
// AudioStore covers audio object storage (runner writes; backend reads).
|
||||
type AudioStore interface {
|
||||
// AudioObjectKey returns the MinIO object key for a cached audio file.
|
||||
// AudioObjectKey returns the MinIO object key for a cached MP3 audio file.
|
||||
// Format: {slug}/{n}/{voice}.mp3
|
||||
AudioObjectKey(slug string, n int, voice string) string
|
||||
|
||||
// AudioObjectKeyExt returns the MinIO object key for a cached audio file
|
||||
// with a custom extension (e.g. "mp3" or "wav").
|
||||
AudioObjectKeyExt(slug string, n int, voice, ext string) string
|
||||
|
||||
// AudioExists returns true when the audio object is present in MinIO.
|
||||
AudioExists(ctx context.Context, key string) bool
|
||||
|
||||
// PutAudio stores raw audio bytes under the given MinIO object key.
|
||||
PutAudio(ctx context.Context, key string, data []byte) error
|
||||
|
||||
// PutAudioStream uploads audio from r to MinIO under key.
|
||||
// size must be the exact byte length of r, or -1 to use multipart upload.
|
||||
// contentType should be "audio/mpeg" or "audio/wav".
|
||||
PutAudioStream(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
|
||||
}
|
||||
|
||||
// PresignStore generates short-lived URLs — used exclusively by the backend.
|
||||
|
||||
@@ -2,6 +2,7 @@ package bookstore_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -51,9 +52,13 @@ func (m *mockStore) RankingFreshEnough(_ context.Context, _ time.Duration) (bool
|
||||
}
|
||||
|
||||
// AudioStore
|
||||
func (m *mockStore) AudioObjectKey(_ string, _ int, _ string) string { return "" }
|
||||
func (m *mockStore) AudioExists(_ context.Context, _ string) bool { return false }
|
||||
func (m *mockStore) PutAudio(_ context.Context, _ string, _ []byte) error { return nil }
|
||||
func (m *mockStore) AudioObjectKey(_ string, _ int, _ string) string { return "" }
|
||||
func (m *mockStore) AudioObjectKeyExt(_ string, _ int, _, _ string) string { return "" }
|
||||
func (m *mockStore) AudioExists(_ context.Context, _ string) bool { return false }
|
||||
func (m *mockStore) PutAudio(_ context.Context, _ string, _ []byte) error { return nil }
|
||||
func (m *mockStore) PutAudioStream(_ context.Context, _ string, _ io.Reader, _ int64, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PresignStore
|
||||
func (m *mockStore) PresignChapter(_ context.Context, _ string, _ int, _ time.Duration) (string, error) {
|
||||
|
||||
@@ -203,6 +203,11 @@ func Load() Config {
|
||||
URL: envOr("POCKET_TTS_URL", ""),
|
||||
},
|
||||
|
||||
LibreTranslate: LibreTranslate{
|
||||
URL: envOr("LIBRETRANSLATE_URL", ""),
|
||||
APIKey: envOr("LIBRETRANSLATE_API_KEY", ""),
|
||||
},
|
||||
|
||||
HTTP: HTTP{
|
||||
Addr: envOr("BACKEND_HTTP_ADDR", ":8080"),
|
||||
},
|
||||
|
||||
@@ -21,6 +21,17 @@ type Client interface {
|
||||
// GenerateAudio synthesises text using voice and returns raw MP3 bytes.
|
||||
GenerateAudio(ctx context.Context, text, voice string) ([]byte, error)
|
||||
|
||||
// StreamAudioMP3 synthesises text and returns an io.ReadCloser that streams
|
||||
// MP3-encoded audio incrementally. Uses the kokoro-fastapi streaming mode
|
||||
// (stream:true), which delivers MP3 frames as they are generated without
|
||||
// waiting for the full output. The caller must always close the ReadCloser.
|
||||
StreamAudioMP3(ctx context.Context, text, voice string) (io.ReadCloser, error)
|
||||
|
||||
// StreamAudioWAV synthesises text and returns an io.ReadCloser that streams
|
||||
// WAV-encoded audio incrementally using kokoro-fastapi's streaming mode with
|
||||
// response_format:"wav". The caller must always close the ReadCloser.
|
||||
StreamAudioWAV(ctx context.Context, text, voice string) (io.ReadCloser, error)
|
||||
|
||||
// ListVoices returns the available voice IDs. Falls back to an empty slice
|
||||
// on error — callers should treat an empty list as "service unavailable".
|
||||
ListVoices(ctx context.Context) ([]string, error)
|
||||
@@ -118,6 +129,90 @@ func (c *httpClient) GenerateAudio(ctx context.Context, text, voice string) ([]b
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// StreamAudioMP3 calls POST /v1/audio/speech with stream:true and returns an
|
||||
// io.ReadCloser that delivers MP3 frames as kokoro generates them.
|
||||
// kokoro-fastapi emits raw MP3 bytes when stream mode is enabled — no download
|
||||
// redirect; the response body IS the audio stream.
|
||||
func (c *httpClient) StreamAudioMP3(ctx context.Context, text, voice string) (io.ReadCloser, error) {
|
||||
if text == "" {
|
||||
return nil, fmt.Errorf("kokoro: empty text")
|
||||
}
|
||||
if voice == "" {
|
||||
voice = "af_bella"
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(map[string]any{
|
||||
"model": "kokoro",
|
||||
"input": text,
|
||||
"voice": voice,
|
||||
"response_format": "mp3",
|
||||
"speed": 1.0,
|
||||
"stream": true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kokoro: marshal stream request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||
c.baseURL+"/v1/audio/speech", bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kokoro: build stream request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kokoro: stream request: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("kokoro: stream returned %d", resp.StatusCode)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// StreamAudioWAV calls POST /v1/audio/speech with stream:true and response_format:wav,
|
||||
// returning an io.ReadCloser that delivers WAV bytes as kokoro generates them.
|
||||
func (c *httpClient) StreamAudioWAV(ctx context.Context, text, voice string) (io.ReadCloser, error) {
|
||||
if text == "" {
|
||||
return nil, fmt.Errorf("kokoro: empty text")
|
||||
}
|
||||
if voice == "" {
|
||||
voice = "af_bella"
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(map[string]any{
|
||||
"model": "kokoro",
|
||||
"input": text,
|
||||
"voice": voice,
|
||||
"response_format": "wav",
|
||||
"speed": 1.0,
|
||||
"stream": true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kokoro: marshal wav stream request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||
c.baseURL+"/v1/audio/speech", bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kokoro: build wav stream request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kokoro: wav stream request: %w", err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("kokoro: wav stream returned %d", resp.StatusCode)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// ListVoices calls GET /v1/audio/voices and returns the list of voice IDs.
|
||||
func (c *httpClient) ListVoices(ctx context.Context) ([]string, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
// so callers receive MP3 bytes — the same format as the kokoro client — and the
|
||||
// rest of the pipeline does not need to care which TTS engine was used.
|
||||
//
|
||||
// StreamAudioMP3 is the streaming variant: it returns an io.ReadCloser that
|
||||
// yields MP3-encoded audio incrementally as pocket-tts generates it, without
|
||||
// buffering the full output.
|
||||
//
|
||||
// Predefined voices (pass the bare name as the voice parameter):
|
||||
//
|
||||
// alba, marius, javert, jean, fantine, cosette, eponine, azelma,
|
||||
@@ -50,6 +54,17 @@ type Client interface {
|
||||
// Voice must be one of the predefined pocket-tts voice names.
|
||||
GenerateAudio(ctx context.Context, text, voice string) ([]byte, error)
|
||||
|
||||
// StreamAudioMP3 synthesises text and returns an io.ReadCloser that streams
|
||||
// MP3-encoded audio incrementally via a live ffmpeg transcode pipe.
|
||||
// The caller must always close the returned ReadCloser.
|
||||
StreamAudioMP3(ctx context.Context, text, voice string) (io.ReadCloser, error)
|
||||
|
||||
// StreamAudioWAV synthesises text and returns an io.ReadCloser that streams
|
||||
// raw WAV audio directly from pocket-tts without any transcoding.
|
||||
// The stream begins with a WAV header followed by 16-bit PCM frames at 16 kHz.
|
||||
// The caller must always close the returned ReadCloser.
|
||||
StreamAudioWAV(ctx context.Context, text, voice string) (io.ReadCloser, error)
|
||||
|
||||
// ListVoices returns the available predefined voice names.
|
||||
ListVoices(ctx context.Context) ([]string, error)
|
||||
}
|
||||
@@ -79,14 +94,116 @@ func (c *httpClient) GenerateAudio(ctx context.Context, text, voice string) ([]b
|
||||
voice = "alba"
|
||||
}
|
||||
|
||||
// ── Build multipart form ──────────────────────────────────────────────────
|
||||
resp, err := c.postTTS(ctx, text, voice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
wavData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pockettts: read response body: %w", err)
|
||||
}
|
||||
|
||||
// ── Transcode WAV → MP3 via ffmpeg ────────────────────────────────────────
|
||||
mp3Data, err := wavToMP3(ctx, wavData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pockettts: transcode to mp3: %w", err)
|
||||
}
|
||||
return mp3Data, nil
|
||||
}
|
||||
|
||||
// StreamAudioMP3 posts to POST /tts and returns an io.ReadCloser that delivers
|
||||
// MP3 bytes as pocket-tts generates WAV frames. ffmpeg runs as a subprocess
|
||||
// with stdin connected to the live WAV stream and stdout piped to the caller.
|
||||
// The caller must always close the returned ReadCloser.
|
||||
func (c *httpClient) StreamAudioMP3(ctx context.Context, text, voice string) (io.ReadCloser, error) {
|
||||
if text == "" {
|
||||
return nil, fmt.Errorf("pockettts: empty text")
|
||||
}
|
||||
if voice == "" {
|
||||
voice = "alba"
|
||||
}
|
||||
|
||||
resp, err := c.postTTS(ctx, text, voice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start ffmpeg: read WAV from stdin (the live HTTP body), write MP3 to stdout.
|
||||
cmd := exec.CommandContext(ctx,
|
||||
"ffmpeg",
|
||||
"-hide_banner", "-loglevel", "error",
|
||||
"-i", "pipe:0", // WAV from stdin
|
||||
"-f", "mp3", // output format
|
||||
"-q:a", "2", // VBR ~190 kbps
|
||||
"pipe:1", // MP3 to stdout
|
||||
)
|
||||
cmd.Stdin = resp.Body
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
cmd.Stdout = pw
|
||||
|
||||
var stderrBuf bytes.Buffer
|
||||
cmd.Stderr = &stderrBuf
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("pockettts: start ffmpeg: %w", err)
|
||||
}
|
||||
|
||||
// Close the write end of the pipe when ffmpeg exits, propagating any error.
|
||||
go func() {
|
||||
waitErr := cmd.Wait()
|
||||
resp.Body.Close()
|
||||
if waitErr != nil {
|
||||
pw.CloseWithError(fmt.Errorf("ffmpeg: %w (stderr: %s)", waitErr, stderrBuf.String()))
|
||||
} else {
|
||||
pw.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// StreamAudioWAV posts to POST /tts and returns an io.ReadCloser that delivers
|
||||
// raw WAV bytes directly from pocket-tts — no ffmpeg transcoding required.
|
||||
// The first bytes will be a WAV header (RIFF/fmt chunk) followed by PCM frames.
|
||||
// The caller must always close the returned ReadCloser.
|
||||
func (c *httpClient) StreamAudioWAV(ctx context.Context, text, voice string) (io.ReadCloser, error) {
|
||||
if text == "" {
|
||||
return nil, fmt.Errorf("pockettts: empty text")
|
||||
}
|
||||
if voice == "" {
|
||||
voice = "alba"
|
||||
}
|
||||
|
||||
resp, err := c.postTTS(ctx, text, voice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// ListVoices returns the statically known predefined voice names.
|
||||
// pocket-tts has no REST endpoint for listing voices.
|
||||
func (c *httpClient) ListVoices(_ context.Context) ([]string, error) {
|
||||
voices := make([]string, 0, len(PredefinedVoices))
|
||||
for v := range PredefinedVoices {
|
||||
voices = append(voices, v)
|
||||
}
|
||||
return voices, nil
|
||||
}
|
||||
|
||||
// postTTS sends a multipart POST /tts request and returns the raw response.
|
||||
// The caller is responsible for closing resp.Body.
|
||||
func (c *httpClient) postTTS(ctx context.Context, text, voice string) (*http.Response, error) {
|
||||
var body bytes.Buffer
|
||||
mw := multipart.NewWriter(&body)
|
||||
|
||||
if err := mw.WriteField("text", text); err != nil {
|
||||
return nil, fmt.Errorf("pockettts: write text field: %w", err)
|
||||
}
|
||||
// pocket-tts accepts a predefined voice name as voice_url.
|
||||
if err := mw.WriteField("voice_url", voice); err != nil {
|
||||
return nil, fmt.Errorf("pockettts: write voice_url field: %w", err)
|
||||
}
|
||||
@@ -105,34 +222,12 @@ func (c *httpClient) GenerateAudio(ctx context.Context, text, voice string) ([]b
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pockettts: request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("pockettts: server returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
wavData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pockettts: read response body: %w", err)
|
||||
}
|
||||
|
||||
// ── Transcode WAV → MP3 via ffmpeg ────────────────────────────────────────
|
||||
mp3Data, err := wavToMP3(ctx, wavData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pockettts: transcode to mp3: %w", err)
|
||||
}
|
||||
return mp3Data, nil
|
||||
}
|
||||
|
||||
// ListVoices returns the statically known predefined voice names.
|
||||
// pocket-tts has no REST endpoint for listing voices.
|
||||
func (c *httpClient) ListVoices(_ context.Context) ([]string, error) {
|
||||
voices := make([]string, 0, len(PredefinedVoices))
|
||||
for v := range PredefinedVoices {
|
||||
voices = append(voices, v)
|
||||
}
|
||||
return voices, nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// wavToMP3 converts raw WAV bytes to MP3 using ffmpeg.
|
||||
|
||||
@@ -78,7 +78,7 @@ func (r *Runner) runAsynq(ctx context.Context) error {
|
||||
// Write /tmp/runner.alive every 30s so Docker healthcheck passes in asynq mode.
|
||||
// This mirrors the heartbeat file behavior from the poll() loop.
|
||||
go func() {
|
||||
heartbeatTick := time.NewTicker(r.cfg.StaleTaskThreshold)
|
||||
heartbeatTick := time.NewTicker(r.cfg.StaleTaskThreshold / 2)
|
||||
defer heartbeatTick.Stop()
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package runner_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -124,11 +126,18 @@ type stubAudioStore struct {
|
||||
func (s *stubAudioStore) AudioObjectKey(slug string, n int, voice string) string {
|
||||
return slug + "/" + string(rune('0'+n)) + "/" + voice + ".mp3"
|
||||
}
|
||||
func (s *stubAudioStore) AudioObjectKeyExt(slug string, n int, voice, ext string) string {
|
||||
return slug + "/" + string(rune('0'+n)) + "/" + voice + "." + ext
|
||||
}
|
||||
func (s *stubAudioStore) AudioExists(_ context.Context, _ string) bool { return false }
|
||||
func (s *stubAudioStore) PutAudio(_ context.Context, _ string, _ []byte) error {
|
||||
s.putCalled.Add(1)
|
||||
return s.putErr
|
||||
}
|
||||
func (s *stubAudioStore) PutAudioStream(_ context.Context, _ string, _ io.Reader, _ int64, _ string) error {
|
||||
s.putCalled.Add(1)
|
||||
return s.putErr
|
||||
}
|
||||
|
||||
// stubNovelScraper satisfies scraper.NovelScraper minimally.
|
||||
type stubNovelScraper struct {
|
||||
@@ -185,6 +194,22 @@ func (s *stubKokoro) GenerateAudio(_ context.Context, _, _ string) ([]byte, erro
|
||||
return s.data, s.genErr
|
||||
}
|
||||
|
||||
func (s *stubKokoro) StreamAudioMP3(_ context.Context, _, _ string) (io.ReadCloser, error) {
|
||||
s.called.Add(1)
|
||||
if s.genErr != nil {
|
||||
return nil, s.genErr
|
||||
}
|
||||
return io.NopCloser(bytes.NewReader(s.data)), nil
|
||||
}
|
||||
|
||||
func (s *stubKokoro) StreamAudioWAV(_ context.Context, _, _ string) (io.ReadCloser, error) {
|
||||
s.called.Add(1)
|
||||
if s.genErr != nil {
|
||||
return nil, s.genErr
|
||||
}
|
||||
return io.NopCloser(bytes.NewReader(s.data)), nil
|
||||
}
|
||||
|
||||
func (s *stubKokoro) ListVoices(_ context.Context) ([]string, error) {
|
||||
return []string{"af_bella"}, nil
|
||||
}
|
||||
|
||||
@@ -109,10 +109,17 @@ func ChapterObjectKey(slug string, n int) string {
|
||||
return fmt.Sprintf("%s/chapter-%06d.md", slug, n)
|
||||
}
|
||||
|
||||
// AudioObjectKey returns the MinIO object key for a cached audio file.
|
||||
// AudioObjectKeyExt returns the MinIO object key for a cached audio file
|
||||
// with a custom extension (e.g. "mp3" or "wav").
|
||||
// Format: {slug}/{n}/{voice}.{ext}
|
||||
func AudioObjectKeyExt(slug string, n int, voice, ext string) string {
|
||||
return fmt.Sprintf("%s/%d/%s.%s", slug, n, voice, ext)
|
||||
}
|
||||
|
||||
// AudioObjectKey returns the MinIO object key for a cached MP3 audio file.
|
||||
// Format: {slug}/{n}/{voice}.mp3
|
||||
func AudioObjectKey(slug string, n int, voice string) string {
|
||||
return fmt.Sprintf("%s/%d/%s.mp3", slug, n, voice)
|
||||
return AudioObjectKeyExt(slug, n, voice, "mp3")
|
||||
}
|
||||
|
||||
// AvatarObjectKey returns the MinIO object key for a user avatar image.
|
||||
@@ -155,6 +162,14 @@ func (m *minioClient) putObject(ctx context.Context, bucket, key, contentType st
|
||||
return err
|
||||
}
|
||||
|
||||
// putObjectStream uploads from r with known size (or -1 for multipart).
|
||||
func (m *minioClient) putObjectStream(ctx context.Context, bucket, key, contentType string, r io.Reader, size int64) error {
|
||||
_, err := m.client.PutObject(ctx, bucket, key, r, size,
|
||||
minio.PutObjectOptions{ContentType: contentType},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *minioClient) getObject(ctx context.Context, bucket, key string) ([]byte, error) {
|
||||
obj, err := m.client.GetObject(ctx, bucket, key, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
|
||||
@@ -26,6 +26,11 @@ import (
|
||||
// ErrNotFound is returned by single-record lookups when no record exists.
|
||||
var ErrNotFound = errors.New("storage: record not found")
|
||||
|
||||
// pbHTTPClient is a shared HTTP client with a 30 s timeout so that a slow or
|
||||
// hung PocketBase never stalls the backend/runner process indefinitely.
|
||||
// http.DefaultClient has no timeout and must not be used for PocketBase calls.
|
||||
var pbHTTPClient = &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// pbClient is the internal PocketBase REST admin client.
|
||||
type pbClient struct {
|
||||
baseURL string
|
||||
@@ -66,7 +71,7 @@ func (c *pbClient) authToken(ctx context.Context) (string, error) {
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
resp, err := pbHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("pb auth: %w", err)
|
||||
}
|
||||
@@ -104,7 +109,7 @@ func (c *pbClient) do(ctx context.Context, method, path string, body io.Reader)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
resp, err := pbHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pb: %s %s: %w", method, path, err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -73,12 +74,24 @@ func (s *Store) WriteMetadata(ctx context.Context, meta domain.BookMeta) error {
|
||||
"rating": meta.Rating,
|
||||
}
|
||||
// Upsert via filter: if exists PATCH, otherwise POST.
|
||||
// Use a conflict-retry pattern to handle concurrent scrapes racing to insert
|
||||
// the same slug: if POST fails (or another concurrent writer beat us to it),
|
||||
// re-fetch and PATCH instead.
|
||||
existing, err := s.getBookBySlug(ctx, meta.Slug)
|
||||
if err != nil && err != ErrNotFound {
|
||||
return fmt.Errorf("WriteMetadata: %w", err)
|
||||
}
|
||||
if err == ErrNotFound {
|
||||
return s.pb.post(ctx, "/api/collections/books/records", payload, nil)
|
||||
postErr := s.pb.post(ctx, "/api/collections/books/records", payload, nil)
|
||||
if postErr == nil {
|
||||
return nil
|
||||
}
|
||||
// POST failed — a concurrent writer may have inserted the same slug.
|
||||
// Re-fetch and fall through to PATCH.
|
||||
existing, err = s.getBookBySlug(ctx, meta.Slug)
|
||||
if err != nil {
|
||||
return postErr // original POST error is more informative
|
||||
}
|
||||
}
|
||||
return s.pb.patch(ctx, fmt.Sprintf("/api/collections/books/records/%s", existing.ID), payload)
|
||||
}
|
||||
@@ -375,6 +388,10 @@ func (s *Store) AudioObjectKey(slug string, n int, voice string) string {
|
||||
return AudioObjectKey(slug, n, voice)
|
||||
}
|
||||
|
||||
func (s *Store) AudioObjectKeyExt(slug string, n int, voice, ext string) string {
|
||||
return AudioObjectKeyExt(slug, n, voice, ext)
|
||||
}
|
||||
|
||||
func (s *Store) AudioExists(ctx context.Context, key string) bool {
|
||||
return s.mc.objectExists(ctx, s.mc.bucketAudio, key)
|
||||
}
|
||||
@@ -383,6 +400,10 @@ func (s *Store) PutAudio(ctx context.Context, key string, data []byte) error {
|
||||
return s.mc.putObject(ctx, s.mc.bucketAudio, key, "audio/mpeg", data)
|
||||
}
|
||||
|
||||
func (s *Store) PutAudioStream(ctx context.Context, key string, r io.Reader, size int64, contentType string) error {
|
||||
return s.mc.putObjectStream(ctx, s.mc.bucketAudio, key, contentType, r, size)
|
||||
}
|
||||
|
||||
// ── PresignStore ──────────────────────────────────────────────────────────────
|
||||
|
||||
func (s *Store) PresignChapter(ctx context.Context, slug string, n int, expires time.Duration) (string, error) {
|
||||
@@ -569,6 +590,28 @@ func (s *Store) CancelTask(ctx context.Context, id string) error {
|
||||
map[string]string{"status": string(domain.TaskStatusCancelled)})
|
||||
}
|
||||
|
||||
func (s *Store) CancelAudioTasksBySlug(ctx context.Context, slug string) (int, error) {
|
||||
filter := fmt.Sprintf(`slug='%s'&&(status='pending'||status='running')`, slug)
|
||||
items, err := s.pb.listAll(ctx, "audio_jobs", filter, "")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("CancelAudioTasksBySlug list: %w", err)
|
||||
}
|
||||
cancelled := 0
|
||||
for _, raw := range items {
|
||||
var rec struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if json.Unmarshal(raw, &rec) == nil && rec.ID != "" {
|
||||
if patchErr := s.pb.patch(ctx,
|
||||
fmt.Sprintf("/api/collections/audio_jobs/records/%s", rec.ID),
|
||||
map[string]string{"status": string(domain.TaskStatusCancelled)}); patchErr == nil {
|
||||
cancelled++
|
||||
}
|
||||
}
|
||||
}
|
||||
return cancelled, nil
|
||||
}
|
||||
|
||||
// ── taskqueue.Consumer ────────────────────────────────────────────────────────
|
||||
|
||||
func (s *Store) ClaimNextScrapeTask(ctx context.Context, workerID string) (domain.ScrapeTask, bool, error) {
|
||||
|
||||
@@ -36,6 +36,10 @@ type Producer interface {
|
||||
// CancelTask transitions a pending task to status=cancelled.
|
||||
// Returns ErrNotFound if the task does not exist.
|
||||
CancelTask(ctx context.Context, id string) error
|
||||
|
||||
// CancelAudioTasksBySlug cancels all pending or running audio tasks for slug.
|
||||
// Returns the number of tasks cancelled.
|
||||
CancelAudioTasksBySlug(ctx context.Context, slug string) (int, error)
|
||||
}
|
||||
|
||||
// Consumer is the read/claim side of the task queue used by the runner.
|
||||
|
||||
@@ -26,7 +26,8 @@ func (s *stubStore) CreateAudioTask(_ context.Context, _ string, _ int, _ string
|
||||
func (s *stubStore) CreateTranslationTask(_ context.Context, _ string, _ int, _ string) (string, error) {
|
||||
return "translation-1", nil
|
||||
}
|
||||
func (s *stubStore) CancelTask(_ context.Context, _ string) error { return nil }
|
||||
func (s *stubStore) CancelTask(_ context.Context, _ string) error { return nil }
|
||||
func (s *stubStore) CancelAudioTasksBySlug(_ context.Context, _ string) (int, error) { return 0, nil }
|
||||
|
||||
func (s *stubStore) ClaimNextScrapeTask(_ context.Context, _ string) (domain.ScrapeTask, bool, error) {
|
||||
return domain.ScrapeTask{ID: "task-1", Status: domain.TaskStatusRunning}, true, nil
|
||||
|
||||
BIN
backend/runner
BIN
backend/runner
Binary file not shown.
@@ -7,3 +7,4 @@ RUN xcaddy build \
|
||||
|
||||
FROM caddy:2-alpine
|
||||
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
||||
COPY errors/ /srv/errors/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>404 — Page Not Found — LibNovel</title>
|
||||
<title>404 — Page Not Found — libnovel</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@@ -27,11 +27,10 @@
|
||||
.logo {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #e4e4e7;
|
||||
color: #f59e0b;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo span { color: #f59e0b; }
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
@@ -114,7 +113,7 @@
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a class="logo" href="/">Lib<span>Novel</span></a>
|
||||
<a class="logo" href="/">libnovel</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
203
caddy/errors/500.html
Normal file
203
caddy/errors/500.html
Normal file
@@ -0,0 +1,203 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>500 — Internal Error — libnovel</title>
|
||||
<meta http-equiv="refresh" content="20">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: #09090b;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: ui-sans-serif, system-ui, sans-serif;
|
||||
color: #a1a1aa;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid #27272a;
|
||||
}
|
||||
.logo {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #f59e0b;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.watermark {
|
||||
font-size: clamp(5rem, 22vw, 9rem);
|
||||
font-weight: 800;
|
||||
color: #18181b;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04em;
|
||||
user-select: none;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #f59e0b;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.4; transform: scale(0.75); }
|
||||
}
|
||||
.status-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #e4e4e7;
|
||||
letter-spacing: -0.02em;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9375rem;
|
||||
max-width: 38ch;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.625rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #f59e0b;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
text-decoration: none;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.btn:hover { background: #d97706; }
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: #a1a1aa;
|
||||
border: 1px solid #27272a;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-secondary:hover { background: #18181b; color: #e4e4e7; }
|
||||
|
||||
.refresh-note {
|
||||
margin-top: 1.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: #52525b;
|
||||
}
|
||||
#countdown { color: #71717a; }
|
||||
|
||||
footer {
|
||||
padding: 1.5rem 2rem;
|
||||
border-top: 1px solid #27272a;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
color: #3f3f46;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a class="logo" href="/">libnovel</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Book with lightning bolt SVG -->
|
||||
<svg class="illustration" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<!-- Book cover -->
|
||||
<rect x="14" y="12" width="50" height="68" rx="4" fill="#27272a" stroke="#3f3f46" stroke-width="1.5"/>
|
||||
<!-- Spine -->
|
||||
<rect x="10" y="12" width="8" height="68" rx="2" fill="#18181b" stroke="#3f3f46" stroke-width="1.5"/>
|
||||
<!-- Pages edge -->
|
||||
<rect x="62" y="14" width="4" height="64" rx="1" fill="#1c1c1f"/>
|
||||
<!-- Lightning bolt -->
|
||||
<path d="M44 22 L34 46 H42 L36 70 L58 42 H48 L56 22 Z" fill="#f59e0b" opacity="0.9"/>
|
||||
<!-- Text lines -->
|
||||
<rect x="22" y="58" width="28" height="2.5" rx="1.25" fill="#3f3f46"/>
|
||||
<rect x="22" y="63" width="18" height="2.5" rx="1.25" fill="#3f3f46"/>
|
||||
<rect x="22" y="68" width="24" height="2.5" rx="1.25" fill="#3f3f46"/>
|
||||
</svg>
|
||||
|
||||
<div class="watermark">500</div>
|
||||
|
||||
<div class="status-row">
|
||||
<div class="dot"></div>
|
||||
<span class="status-label">Internal error</span>
|
||||
</div>
|
||||
|
||||
<h1>Something went wrong</h1>
|
||||
<p>An unexpected error occurred on our end. We're on it — try again in a moment.</p>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn" href="/">Go home</a>
|
||||
<button class="btn btn-secondary" onclick="location.reload()">Retry</button>
|
||||
</div>
|
||||
|
||||
<p class="refresh-note">Auto-refreshing in <span id="countdown">20</span>s</p>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
© LibNovel
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
var s = 20;
|
||||
var el = document.getElementById('countdown');
|
||||
var t = setInterval(function () {
|
||||
s--;
|
||||
el.textContent = s;
|
||||
if (s <= 0) { clearInterval(t); location.reload(); }
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>502 — Service Unavailable — LibNovel</title>
|
||||
<title>502 — Service Unavailable — libnovel</title>
|
||||
<meta http-equiv="refresh" content="20">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@@ -27,11 +28,10 @@
|
||||
.logo {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #e4e4e7;
|
||||
color: #f59e0b;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo span { color: #f59e0b; }
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
@@ -126,7 +126,7 @@
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a class="logo" href="/">Lib<span>Novel</span></a>
|
||||
<a class="logo" href="/">libnovel</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Under Maintenance — LibNovel</title>
|
||||
<title>Under Maintenance — libnovel</title>
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@@ -28,11 +29,10 @@
|
||||
.logo {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #e4e4e7;
|
||||
color: #f59e0b;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo span { color: #f59e0b; }
|
||||
|
||||
/* ── Main ── */
|
||||
main {
|
||||
@@ -129,7 +129,7 @@
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a class="logo" href="/">Lib<span>Novel</span></a>
|
||||
<a class="logo" href="/">libnovel</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>504 — Gateway Timeout — LibNovel</title>
|
||||
<title>504 — Gateway Timeout — libnovel</title>
|
||||
<meta http-equiv="refresh" content="20">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@@ -27,11 +28,10 @@
|
||||
.logo {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #e4e4e7;
|
||||
color: #f59e0b;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo span { color: #f59e0b; }
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
@@ -126,7 +126,7 @@
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a class="logo" href="/">Lib<span>Novel</span></a>
|
||||
<a class="logo" href="/">libnovel</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
@@ -126,6 +126,26 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ─── Redis (Asynq task queue — accessed locally by backend, remotely by homelab runner) ──
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
redis-server
|
||||
--appendonly yes
|
||||
--requirepass "${REDIS_PASSWORD}"
|
||||
# No public port — backend reaches it via internal network.
|
||||
# Homelab runner reaches it via Caddy TLS proxy on :6380 → redis:6379.
|
||||
expose:
|
||||
- "6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ─── Backend API ──────────────────────────────────────────────────────────────
|
||||
backend:
|
||||
image: kalekber/libnovel-backend:${GIT_TAG:-latest}
|
||||
@@ -151,6 +171,8 @@ services:
|
||||
condition: service_healthy
|
||||
valkey:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
# No public port — all traffic is routed via Caddy.
|
||||
expose:
|
||||
- "8080"
|
||||
@@ -164,10 +186,9 @@ services:
|
||||
GLITCHTIP_DSN: "${GLITCHTIP_DSN}"
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: "${OTEL_EXPORTER_OTLP_ENDPOINT}"
|
||||
OTEL_SERVICE_NAME: "backend"
|
||||
# Asynq task queue — backend enqueues jobs to homelab Redis via Caddy TLS proxy.
|
||||
# Set to "rediss://:password@redis.libnovel.cc:6380" in Doppler prd config.
|
||||
# Leave empty to fall back to PocketBase polling.
|
||||
REDIS_ADDR: "${REDIS_ADDR}"
|
||||
# Asynq task queue — backend enqueues jobs to local Redis sidecar.
|
||||
# Homelab runner connects to the same Redis via Caddy TLS proxy on :6380.
|
||||
REDIS_ADDR: "redis:6379"
|
||||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||||
healthcheck:
|
||||
test: ["CMD", "/healthcheck", "http://localhost:8080/health"]
|
||||
@@ -269,6 +290,7 @@ services:
|
||||
POCKETBASE_ADMIN_EMAIL: "${POCKETBASE_ADMIN_EMAIL}"
|
||||
POCKETBASE_ADMIN_PASSWORD: "${POCKETBASE_ADMIN_PASSWORD}"
|
||||
AUTH_SECRET: "${AUTH_SECRET}"
|
||||
DEBUG_LOGIN_TOKEN: "${DEBUG_LOGIN_TOKEN}"
|
||||
PUBLIC_MINIO_PUBLIC_URL: "${MINIO_PUBLIC_ENDPOINT}"
|
||||
# Valkey
|
||||
VALKEY_ADDR: "valkey:6379"
|
||||
@@ -382,12 +404,10 @@ services:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # HTTP/3 (QUIC)
|
||||
- "6380:6380" # Redis TCP proxy (TLS) for homelab → Asynq
|
||||
- "6380:6380" # Redis TCP proxy (TLS) for homelab runner → Asynq
|
||||
environment:
|
||||
DOMAIN: "${DOMAIN}"
|
||||
CADDY_ACME_EMAIL: "${CADDY_ACME_EMAIL}"
|
||||
# Homelab Redis address — Caddy TCP-proxies inbound :6380 to this.
|
||||
HOMELAB_REDIS_ADDR: "${HOMELAB_REDIS_ADDR:?HOMELAB_REDIS_ADDR required for Redis TCP proxy}"
|
||||
env_file:
|
||||
- path: ./crowdsec/.crowdsec.env
|
||||
required: false
|
||||
@@ -421,6 +441,7 @@ volumes:
|
||||
pb_data:
|
||||
meili_data:
|
||||
valkey_data:
|
||||
redis_data:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
caddy_logs:
|
||||
|
||||
@@ -58,6 +58,14 @@ services:
|
||||
VALKEY_ADDR: ""
|
||||
GODEBUG: "preferIPv4=1"
|
||||
|
||||
# ── LibreTranslate (internal Docker network) ──────────────────────────
|
||||
LIBRETRANSLATE_URL: "http://libretranslate:5000"
|
||||
LIBRETRANSLATE_API_KEY: "${LIBRETRANSLATE_API_KEY}"
|
||||
|
||||
# ── Asynq / Redis ─────────────────────────────────────────────────────
|
||||
REDIS_ADDR: "redis:6379"
|
||||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||||
|
||||
KOKORO_URL: "http://kokoro-fastapi:8880"
|
||||
KOKORO_VOICE: "${KOKORO_VOICE}"
|
||||
|
||||
@@ -67,6 +75,7 @@ services:
|
||||
RUNNER_POLL_INTERVAL: "${RUNNER_POLL_INTERVAL}"
|
||||
RUNNER_MAX_CONCURRENT_SCRAPE: "${RUNNER_MAX_CONCURRENT_SCRAPE}"
|
||||
RUNNER_MAX_CONCURRENT_AUDIO: "${RUNNER_MAX_CONCURRENT_AUDIO}"
|
||||
RUNNER_MAX_CONCURRENT_TRANSLATION: "${RUNNER_MAX_CONCURRENT_TRANSLATION}"
|
||||
RUNNER_TIMEOUT: "${RUNNER_TIMEOUT}"
|
||||
RUNNER_METRICS_ADDR: "${RUNNER_METRICS_ADDR}"
|
||||
RUNNER_SKIP_INITIAL_CATALOGUE_REFRESH: "true"
|
||||
@@ -280,6 +289,48 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ── Redis (Asynq task queue) ────────────────────────────────────────────────
|
||||
# Dedicated Redis instance for Asynq job dispatch.
|
||||
# The prod backend enqueues jobs via redis.libnovel.cc:6380 (Caddy TLS proxy →
|
||||
# host:6379). The runner reads from this instance directly on the Docker network.
|
||||
# Port is bound to 0.0.0.0:6379 so the Caddy layer4 proxy on prod can reach it.
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD}"]
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ── LibreTranslate ──────────────────────────────────────────────────────────
|
||||
# Self-hosted machine translation. Runner connects via http://libretranslate:5000.
|
||||
# Only English → configured target languages are loaded to save RAM.
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LT_API_KEYS: "true"
|
||||
LT_API_KEYS_DB_PATH: "/app/db/api_keys.db"
|
||||
LT_LOAD_ONLY: "en,ru,id,pt,fr"
|
||||
LT_DISABLE_WEB_UI: "true"
|
||||
LT_UPDATE_MODELS: "false"
|
||||
expose:
|
||||
- "5000"
|
||||
volumes:
|
||||
- libretranslate_data:/app/db
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:5000/languages"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
|
||||
# ── Valkey ──────────────────────────────────────────────────────────────────
|
||||
# Used by GlitchTip for task queuing.
|
||||
valkey:
|
||||
@@ -460,6 +511,8 @@ services:
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
libretranslate_data:
|
||||
valkey_data:
|
||||
uptime_kuma_data:
|
||||
gotify_data:
|
||||
|
||||
@@ -11,25 +11,10 @@
|
||||
# - MEILI_URL → https://search.libnovel.cc (Caddy-proxied)
|
||||
# - VALKEY_ADDR → unset (not exposed publicly)
|
||||
# - RUNNER_SKIP_INITIAL_CATALOGUE_REFRESH=true
|
||||
# - Redis service for Asynq task queue (local to homelab, exposed to prod via Caddy TCP proxy)
|
||||
# - REDIS_ADDR → rediss://redis.libnovel.cc:6380 (prod Redis via Caddy TLS proxy)
|
||||
# - LibreTranslate service for machine translation (internal network only)
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: >
|
||||
redis-server
|
||||
--appendonly yes
|
||||
--requirepass "${REDIS_PASSWORD}"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate:latest
|
||||
restart: unless-stopped
|
||||
@@ -43,22 +28,13 @@ services:
|
||||
volumes:
|
||||
- libretranslate_models:/home/libretranslate/.local/share/argos-translate
|
||||
- libretranslate_db:/app/db
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:5000/languages || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 120s
|
||||
|
||||
runner:
|
||||
image: kalekber/libnovel-runner:latest
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 135s
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
libretranslate:
|
||||
condition: service_healthy
|
||||
- libretranslate
|
||||
environment:
|
||||
# ── PocketBase ──────────────────────────────────────────────────────────
|
||||
POCKETBASE_URL: "https://pb.libnovel.cc"
|
||||
@@ -91,9 +67,10 @@ services:
|
||||
LIBRETRANSLATE_URL: "http://libretranslate:5000"
|
||||
LIBRETRANSLATE_API_KEY: "${LIBRETRANSLATE_API_KEY}"
|
||||
|
||||
# ── Asynq / Redis (local service) ───────────────────────────────────────
|
||||
# The runner connects to the local Redis sidecar.
|
||||
REDIS_ADDR: "redis:6379"
|
||||
# ── Asynq / Redis (prod Redis via Caddy TLS proxy) ──────────────────────
|
||||
# The runner connects to prod Redis over TLS: rediss://redis.libnovel.cc:6380.
|
||||
# Caddy on prod terminates TLS and proxies to the local redis:6379 sidecar.
|
||||
REDIS_ADDR: "${REDIS_ADDR}"
|
||||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||||
|
||||
# ── Runner tuning ───────────────────────────────────────────────────────
|
||||
@@ -117,6 +94,5 @@ services:
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
libretranslate_models:
|
||||
libretranslate_db:
|
||||
|
||||
@@ -259,6 +259,14 @@ create "translation_jobs" '{
|
||||
{"name":"heartbeat_at", "type":"date"}
|
||||
]}'
|
||||
|
||||
create "discovery_votes" '{
|
||||
"name":"discovery_votes","type":"base","fields":[
|
||||
{"name":"session_id","type":"text","required":true},
|
||||
{"name":"user_id", "type":"text"},
|
||||
{"name":"slug", "type":"text","required":true},
|
||||
{"name":"action", "type":"text","required":true}
|
||||
]}'
|
||||
|
||||
# ── 5. Field migrations (idempotent — adds fields missing from older installs) ─
|
||||
add_field "scraping_tasks" "heartbeat_at" "date"
|
||||
add_field "audio_jobs" "heartbeat_at" "date"
|
||||
|
||||
@@ -160,6 +160,9 @@
|
||||
"profile_theme_amber": "Amber",
|
||||
"profile_theme_slate": "Slate",
|
||||
"profile_theme_rose": "Rose",
|
||||
"profile_theme_light": "Light",
|
||||
"profile_theme_light_slate": "Light Blue",
|
||||
"profile_theme_light_rose": "Light Rose",
|
||||
"profile_reading_heading": "Reading settings",
|
||||
"profile_voice_label": "Default voice",
|
||||
"profile_speed_label": "Playback speed",
|
||||
@@ -354,6 +357,16 @@
|
||||
|
||||
"admin_pages_label": "Pages",
|
||||
"admin_tools_label": "Tools",
|
||||
"admin_nav_scrape": "Scrape",
|
||||
"admin_nav_audio": "Audio",
|
||||
"admin_nav_translation": "Translation",
|
||||
"admin_nav_changelog": "Changelog",
|
||||
"admin_nav_feedback": "Feedback",
|
||||
"admin_nav_errors": "Errors",
|
||||
"admin_nav_analytics": "Analytics",
|
||||
"admin_nav_logs": "Logs",
|
||||
"admin_nav_uptime": "Uptime",
|
||||
"admin_nav_push": "Push",
|
||||
|
||||
"admin_scrape_status_idle": "Idle",
|
||||
"admin_scrape_status_running": "Running",
|
||||
|
||||
@@ -160,6 +160,9 @@
|
||||
"profile_theme_amber": "Ambre",
|
||||
"profile_theme_slate": "Ardoise",
|
||||
"profile_theme_rose": "Rose",
|
||||
"profile_theme_light": "Light",
|
||||
"profile_theme_light_slate": "Light Blue",
|
||||
"profile_theme_light_rose": "Light Rose",
|
||||
"profile_reading_heading": "Paramètres de lecture",
|
||||
"profile_voice_label": "Voix par défaut",
|
||||
"profile_speed_label": "Vitesse de lecture",
|
||||
@@ -354,6 +357,16 @@
|
||||
|
||||
"admin_pages_label": "Pages",
|
||||
"admin_tools_label": "Outils",
|
||||
"admin_nav_scrape": "Scrape",
|
||||
"admin_nav_audio": "Audio",
|
||||
"admin_nav_translation": "Traduction",
|
||||
"admin_nav_changelog": "Modifications",
|
||||
"admin_nav_feedback": "Retours",
|
||||
"admin_nav_errors": "Erreurs",
|
||||
"admin_nav_analytics": "Analytique",
|
||||
"admin_nav_logs": "Journaux",
|
||||
"admin_nav_uptime": "Disponibilité",
|
||||
"admin_nav_push": "Notifications",
|
||||
|
||||
"admin_scrape_status_idle": "Inactif",
|
||||
"admin_scrape_full_catalogue": "Catalogue complet",
|
||||
|
||||
@@ -160,6 +160,9 @@
|
||||
"profile_theme_amber": "Amber",
|
||||
"profile_theme_slate": "Abu-abu",
|
||||
"profile_theme_rose": "Mawar",
|
||||
"profile_theme_light": "Light",
|
||||
"profile_theme_light_slate": "Light Blue",
|
||||
"profile_theme_light_rose": "Light Rose",
|
||||
"profile_reading_heading": "Pengaturan membaca",
|
||||
"profile_voice_label": "Suara default",
|
||||
"profile_speed_label": "Kecepatan pemutaran",
|
||||
@@ -354,6 +357,16 @@
|
||||
|
||||
"admin_pages_label": "Halaman",
|
||||
"admin_tools_label": "Alat",
|
||||
"admin_nav_scrape": "Scrape",
|
||||
"admin_nav_audio": "Audio",
|
||||
"admin_nav_translation": "Terjemahan",
|
||||
"admin_nav_changelog": "Perubahan",
|
||||
"admin_nav_feedback": "Masukan",
|
||||
"admin_nav_errors": "Kesalahan",
|
||||
"admin_nav_analytics": "Analitik",
|
||||
"admin_nav_logs": "Log",
|
||||
"admin_nav_uptime": "Uptime",
|
||||
"admin_nav_push": "Notifikasi",
|
||||
|
||||
"admin_scrape_status_idle": "Menunggu",
|
||||
"admin_scrape_full_catalogue": "Katalog penuh",
|
||||
|
||||
@@ -160,6 +160,9 @@
|
||||
"profile_theme_amber": "Âmbar",
|
||||
"profile_theme_slate": "Ardósia",
|
||||
"profile_theme_rose": "Rosa",
|
||||
"profile_theme_light": "Light",
|
||||
"profile_theme_light_slate": "Light Blue",
|
||||
"profile_theme_light_rose": "Light Rose",
|
||||
"profile_reading_heading": "Configurações de leitura",
|
||||
"profile_voice_label": "Voz padrão",
|
||||
"profile_speed_label": "Velocidade de reprodução",
|
||||
@@ -354,6 +357,16 @@
|
||||
|
||||
"admin_pages_label": "Páginas",
|
||||
"admin_tools_label": "Ferramentas",
|
||||
"admin_nav_scrape": "Scrape",
|
||||
"admin_nav_audio": "Áudio",
|
||||
"admin_nav_translation": "Tradução",
|
||||
"admin_nav_changelog": "Alterações",
|
||||
"admin_nav_feedback": "Feedback",
|
||||
"admin_nav_errors": "Erros",
|
||||
"admin_nav_analytics": "Análise",
|
||||
"admin_nav_logs": "Logs",
|
||||
"admin_nav_uptime": "Uptime",
|
||||
"admin_nav_push": "Notificações",
|
||||
|
||||
"admin_scrape_status_idle": "Ocioso",
|
||||
"admin_scrape_full_catalogue": "Catálogo completo",
|
||||
@@ -160,6 +160,9 @@
|
||||
"profile_theme_amber": "Янтарь",
|
||||
"profile_theme_slate": "Сланец",
|
||||
"profile_theme_rose": "Роза",
|
||||
"profile_theme_light": "Light",
|
||||
"profile_theme_light_slate": "Light Blue",
|
||||
"profile_theme_light_rose": "Light Rose",
|
||||
"profile_reading_heading": "Настройки чтения",
|
||||
"profile_voice_label": "Голос по умолчанию",
|
||||
"profile_speed_label": "Скорость воспроизведения",
|
||||
@@ -354,6 +357,16 @@
|
||||
|
||||
"admin_pages_label": "Страницы",
|
||||
"admin_tools_label": "Инструменты",
|
||||
"admin_nav_scrape": "Скрейпинг",
|
||||
"admin_nav_audio": "Аудио",
|
||||
"admin_nav_translation": "Перевод",
|
||||
"admin_nav_changelog": "Изменения",
|
||||
"admin_nav_feedback": "Отзывы",
|
||||
"admin_nav_errors": "Ошибки",
|
||||
"admin_nav_analytics": "Аналитика",
|
||||
"admin_nav_logs": "Логи",
|
||||
"admin_nav_uptime": "Мониторинг",
|
||||
"admin_nav_push": "Уведомления",
|
||||
|
||||
"admin_scrape_status_idle": "Ожидание",
|
||||
"admin_scrape_full_catalogue": "Полный каталог",
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide && svelte-kit sync || echo ''",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"paraglide": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide && node -e \"const fs=require('fs'),f='./src/lib/paraglide/messages.js',c=fs.readFileSync(f,'utf8').split('\\n').filter(l=>!l.includes('export * as m')&&!l.includes('enabling auto-import')).join('\\n');fs.writeFileSync(f,c)\"",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://inlang.com/schema/project-settings",
|
||||
"baseLocale": "en",
|
||||
"locales": ["en", "ru", "id", "pt-BR", "fr"],
|
||||
"locales": ["en", "ru", "id", "pt", "fr"],
|
||||
"modules": [
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format/dist/index.js"
|
||||
],
|
||||
|
||||
@@ -55,6 +55,48 @@
|
||||
--color-success: #4ade80; /* green-400 */
|
||||
}
|
||||
|
||||
/* ── Light amber theme ────────────────────────────────────────────────── */
|
||||
[data-theme="light"] {
|
||||
--color-brand: #d97706; /* amber-600 */
|
||||
--color-brand-dim: #b45309; /* amber-700 */
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-2: #f4f4f5; /* zinc-100 */
|
||||
--color-surface-3: #e4e4e7; /* zinc-200 */
|
||||
--color-muted: #71717a; /* zinc-500 */
|
||||
--color-text: #18181b; /* zinc-900 */
|
||||
--color-border: #d4d4d8; /* zinc-300 */
|
||||
--color-danger: #dc2626; /* red-600 */
|
||||
--color-success: #16a34a; /* green-600 */
|
||||
}
|
||||
|
||||
/* ── Light slate theme ────────────────────────────────────────────────── */
|
||||
[data-theme="light-slate"] {
|
||||
--color-brand: #4f46e5; /* indigo-600 */
|
||||
--color-brand-dim: #4338ca; /* indigo-700 */
|
||||
--color-surface: #f8fafc; /* slate-50 */
|
||||
--color-surface-2: #f1f5f9; /* slate-100 */
|
||||
--color-surface-3: #e2e8f0; /* slate-200 */
|
||||
--color-muted: #64748b; /* slate-500 */
|
||||
--color-text: #0f172a; /* slate-900 */
|
||||
--color-border: #cbd5e1; /* slate-300 */
|
||||
--color-danger: #dc2626; /* red-600 */
|
||||
--color-success: #16a34a; /* green-600 */
|
||||
}
|
||||
|
||||
/* ── Light rose theme ─────────────────────────────────────────────────── */
|
||||
[data-theme="light-rose"] {
|
||||
--color-brand: #e11d48; /* rose-600 */
|
||||
--color-brand-dim: #be123c; /* rose-700 */
|
||||
--color-surface: #fff1f2; /* rose-50 */
|
||||
--color-surface-2: #ffe4e6; /* rose-100 */
|
||||
--color-surface-3: #fecdd3; /* rose-200 */
|
||||
--color-muted: #9f1239; /* rose-800 at 60% */
|
||||
--color-text: #0f0a0b; /* near black */
|
||||
--color-border: #fda4af; /* rose-300 */
|
||||
--color-danger: #dc2626; /* red-600 */
|
||||
--color-success: #16a34a; /* green-600 */
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
@@ -105,6 +147,15 @@ html {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
/* ── Hide scrollbars (used on horizontal carousels) ────────────────── */
|
||||
.scrollbar-none {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE / Edge legacy */
|
||||
}
|
||||
.scrollbar-none::-webkit-scrollbar {
|
||||
display: none; /* Chrome / Safari / WebKit */
|
||||
}
|
||||
|
||||
/* ── Navigation progress bar ───────────────────────────────────────── */
|
||||
@keyframes progress-bar {
|
||||
0% { width: 0%; opacity: 1; }
|
||||
|
||||
209
ui/src/error.html
Normal file
209
ui/src/error.html
Normal file
@@ -0,0 +1,209 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>%sveltekit.status% — LibNovel</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: #09090b;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: ui-sans-serif, system-ui, sans-serif;
|
||||
color: #a1a1aa;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid #27272a;
|
||||
}
|
||||
.logo {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
color: #e4e4e7;
|
||||
letter-spacing: -0.02em;
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo span { color: #f59e0b; }
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Inline SVG book illustration */
|
||||
.illustration {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.watermark {
|
||||
font-size: clamp(5rem, 22vw, 9rem);
|
||||
font-weight: 800;
|
||||
color: #18181b;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04em;
|
||||
user-select: none;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #f59e0b;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.4; transform: scale(0.75); }
|
||||
}
|
||||
.status-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #e4e4e7;
|
||||
letter-spacing: -0.02em;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.9375rem;
|
||||
max-width: 38ch;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.625rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #f59e0b;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
text-decoration: none;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.btn:hover { background: #d97706; }
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: #a1a1aa;
|
||||
border: 1px solid #27272a;
|
||||
}
|
||||
.btn-secondary:hover { background: #18181b; color: #e4e4e7; }
|
||||
|
||||
.refresh-note {
|
||||
margin-top: 1.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: #52525b;
|
||||
}
|
||||
#countdown { color: #71717a; }
|
||||
|
||||
footer {
|
||||
padding: 1.5rem 2rem;
|
||||
border-top: 1px solid #27272a;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
color: #3f3f46;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a class="logo" href="/">Lib<span>Novel</span></a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Book with broken spine SVG -->
|
||||
<svg class="illustration" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<!-- Book cover -->
|
||||
<rect x="14" y="12" width="50" height="68" rx="4" fill="#27272a" stroke="#3f3f46" stroke-width="1.5"/>
|
||||
<!-- Spine -->
|
||||
<rect x="10" y="12" width="8" height="68" rx="2" fill="#18181b" stroke="#3f3f46" stroke-width="1.5"/>
|
||||
<!-- Pages edge -->
|
||||
<rect x="62" y="14" width="4" height="64" rx="1" fill="#1c1c1f"/>
|
||||
<!-- Crack / broken lines -->
|
||||
<path d="M22 38 L38 34 L34 48 L50 44" stroke="#f59e0b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<!-- Text lines (faded) -->
|
||||
<rect x="22" y="24" width="28" height="3" rx="1.5" fill="#3f3f46"/>
|
||||
<rect x="22" y="30" width="22" height="3" rx="1.5" fill="#3f3f46"/>
|
||||
<rect x="22" y="56" width="28" height="3" rx="1.5" fill="#3f3f46"/>
|
||||
<rect x="22" y="62" width="18" height="3" rx="1.5" fill="#3f3f46"/>
|
||||
<rect x="22" y="68" width="24" height="3" rx="1.5" fill="#3f3f46"/>
|
||||
<!-- Exclamation dot -->
|
||||
<circle cx="72" cy="22" r="10" fill="#18181b" stroke="#f59e0b" stroke-width="1.5"/>
|
||||
<rect x="71" y="16" width="2" height="8" rx="1" fill="#f59e0b"/>
|
||||
<rect x="71" y="26" width="2" height="2" rx="1" fill="#f59e0b"/>
|
||||
</svg>
|
||||
|
||||
<div class="watermark">%sveltekit.status%</div>
|
||||
|
||||
<div class="status-row">
|
||||
<div class="dot"></div>
|
||||
<span class="status-label">Something went wrong</span>
|
||||
</div>
|
||||
|
||||
<h1>The page couldn't load</h1>
|
||||
<p>An unexpected error occurred. We're looking into it — try again in a moment.</p>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn" href="/">Go home</a>
|
||||
<button class="btn btn-secondary" onclick="location.reload()">Retry</button>
|
||||
</div>
|
||||
|
||||
<p class="refresh-note">Auto-refreshing in <span id="countdown">20</span>s</p>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
© LibNovel
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
var s = 20;
|
||||
var el = document.getElementById('countdown');
|
||||
var t = setInterval(function () {
|
||||
s--;
|
||||
el.textContent = s;
|
||||
if (s <= 0) { clearInterval(t); location.reload(); }
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -141,7 +141,7 @@ export function parseAuthToken(token: string): { id: string; username: string; r
|
||||
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
function getTextDirection(locale: string): string {
|
||||
// All supported locales (en, ru, id, pt-BR, fr) are LTR
|
||||
// All supported locales (en, ru, id, pt, fr) are LTR
|
||||
return 'ltr';
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
let {
|
||||
slug,
|
||||
chapter = 0,
|
||||
isLoggedIn = false,
|
||||
currentUserId = ''
|
||||
}: {
|
||||
slug: string;
|
||||
chapter?: number; // 0 = book-level, N = chapter N
|
||||
isLoggedIn?: boolean;
|
||||
currentUserId?: string;
|
||||
} = $props();
|
||||
@@ -47,7 +49,7 @@
|
||||
loadError = '';
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/comments/${encodeURIComponent(slug)}?sort=${sort}`
|
||||
`/api/comments/${encodeURIComponent(slug)}?sort=${sort}${chapter > 0 ? `&chapter=${chapter}` : ''}`
|
||||
);
|
||||
if (!res.ok) throw new Error(`${res.status}`);
|
||||
const data = await res.json();
|
||||
@@ -85,7 +87,7 @@
|
||||
const res = await fetch(`/api/comments/${encodeURIComponent(slug)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ body: text })
|
||||
body: JSON.stringify({ body: text, ...(chapter > 0 ? { chapter } : {}) })
|
||||
});
|
||||
if (res.status === 401) { postError = 'You must be logged in to comment.'; return; }
|
||||
if (!res.ok) {
|
||||
|
||||
3
ui/src/lib/paraglide/.prettierignore
Normal file
3
ui/src/lib/paraglide/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
# ignore everything because the directory is auto-generated by inlang paraglide-js
|
||||
# for more info visit https://inlang.com/m/gerre34r/paraglide-js
|
||||
*
|
||||
147
ui/src/lib/paraglide/README.md
Normal file
147
ui/src/lib/paraglide/README.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Paraglide JS Compiled Output
|
||||
|
||||
> Auto-generated i18n message functions. Import `messages.js` to use translated strings.
|
||||
|
||||
Compiled from: `/Users/kalekber/code/libnovel-v2/ui/project.inlang`
|
||||
|
||||
|
||||
## What is this folder?
|
||||
|
||||
This folder contains compiled [Paraglide JS](https://github.com/opral/paraglide-js) output. Paraglide JS compiles your translation messages into tree-shakeable JavaScript functions.
|
||||
|
||||
## At a glance
|
||||
|
||||
Purpose:
|
||||
- This folder stores compiled i18n message functions.
|
||||
- Source translations live outside this folder in your inlang project.
|
||||
|
||||
Safe to import:
|
||||
- `messages.js` — all message functions
|
||||
- `runtime.js` — locale utilities
|
||||
- `server.js` — server-side middleware
|
||||
|
||||
Do not edit:
|
||||
- All files in this folder are auto-generated.
|
||||
- Changes will be overwritten on next compilation.
|
||||
|
||||
```
|
||||
paraglide/
|
||||
├── messages.js # Message exports (import this)
|
||||
├── messages/ # Individual message functions
|
||||
├── runtime.js # Locale detection & configuration
|
||||
├── registry.js # Formatting utilities (plural, number, datetime)
|
||||
├── server.js # Server-side middleware
|
||||
└── .gitignore # Marks folder as generated
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import * as m from "./paraglide/messages.js";
|
||||
|
||||
// Messages are functions that return localized strings
|
||||
m.hello_world(); // "Hello, World!" (in current locale)
|
||||
m.greeting({ name: "Sam" }); // "Hello, Sam!"
|
||||
|
||||
// Override locale per-call
|
||||
m.hello_world({}, { locale: "de" }); // "Hallo, Welt!"
|
||||
m.greeting({ name: "Sam" }, { locale: "de" }); // "Hallo, Sam!"
|
||||
```
|
||||
|
||||
## Runtime API
|
||||
|
||||
```js
|
||||
import { getLocale, getTextDirection, setLocale, locales, baseLocale } from "./paraglide/runtime.js";
|
||||
|
||||
getLocale(); // Current locale, e.g., "en"
|
||||
getTextDirection(); // "ltr" | "rtl" for current locale
|
||||
setLocale("de"); // Set locale
|
||||
locales; // Available locales, e.g., ["en", "de", "fr"]
|
||||
baseLocale; // Default locale, e.g., "en"
|
||||
```
|
||||
|
||||
## Strategy
|
||||
|
||||
The strategy determines how the current locale is detected and persisted:
|
||||
|
||||
- **Cookie**: Stores locale preference in a cookie.
|
||||
- **URL**: Derives locale from URL patterns (e.g., `/en/about`, `en.example.com`).
|
||||
- **GlobalVariable**: Uses a global variable (client-side only).
|
||||
- **BaseLocale**: Always returns the base locale.
|
||||
|
||||
Strategies can be combined. The order defines precedence:
|
||||
|
||||
```js
|
||||
await compile({
|
||||
project: "./project.inlang",
|
||||
outdir: "./src/paraglide",
|
||||
strategy: ["url", "cookie", "baseLocale"],
|
||||
});
|
||||
```
|
||||
|
||||
See the [strategy documentation](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy) for details.
|
||||
|
||||
## Markup (Rich Text)
|
||||
|
||||
Messages can contain markup tags for bold, links, and other inline elements. Translators control where tags appear; developers control how they render.
|
||||
|
||||
### Message syntax
|
||||
|
||||
```json
|
||||
{
|
||||
"cta": "{#link to=|/docs|}Read the docs{/link}",
|
||||
"bold_text": "This is {#bold}important{/bold}"
|
||||
}
|
||||
```
|
||||
|
||||
- `{#tagName}` opens a tag, `{/tagName}` closes it.
|
||||
- Options: `to=|/docs|` (accessed via `options.to`).
|
||||
- Attributes: `@track` (boolean, accessed via `attributes.track`).
|
||||
|
||||
This is the default inlang message syntax. Paraglide's message format is plugin-based — you can use [ICU MessageFormat 1](https://inlang.com/m/p7c8m1d2/plugin-inlang-icu-messageformat-1), [i18next](https://inlang.com/m/3i8bor92/plugin-inlang-i18next), or other [plugins](https://inlang.com/c/plugins) instead.
|
||||
|
||||
### Rendering markup
|
||||
|
||||
Calling `m.cta()` returns **plain text** (markup stripped). To render markup, use the framework adapter or the low-level `parts()` API:
|
||||
|
||||
```js
|
||||
const parts = m.cta.parts({});
|
||||
// [
|
||||
// { type: "markup-start", name: "link", options: { to: "/docs" }, attributes: {} },
|
||||
// { type: "text", value: "Read the docs" },
|
||||
// { type: "markup-end", name: "link" }
|
||||
// ]
|
||||
```
|
||||
|
||||
Framework adapters provide a `<ParaglideMessage>` component that accepts markup renderers:
|
||||
|
||||
- `@inlang/paraglide-js-react`
|
||||
- `@inlang/paraglide-js-vue`
|
||||
- `@inlang/paraglide-js-svelte`
|
||||
- `@inlang/paraglide-js-solid`
|
||||
|
||||
```jsx
|
||||
import { ParaglideMessage } from "@inlang/paraglide-js-react"; // or -vue, -svelte, -solid
|
||||
|
||||
<ParaglideMessage
|
||||
message={m.cta}
|
||||
inputs={{}}
|
||||
markup={{
|
||||
link: ({ children, options }) => <a href={options.to}>{children}</a>,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
See the [markup documentation](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/markup) for details.
|
||||
|
||||
## Key concepts
|
||||
|
||||
- **Tree-shakeable**: Each message is a function, enabling [up to 70% smaller i18n bundle sizes](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/benchmark) than traditional i18n libraries.
|
||||
- **Typesafe**: Full TypeScript/JSDoc support with autocomplete.
|
||||
- **Variants**: Messages can have variants for pluralization, gender, etc.
|
||||
- **Fallbacks**: Missing translations fall back to the base locale.
|
||||
|
||||
## Links
|
||||
|
||||
- [Paraglide JS Documentation](https://inlang.com/m/gerre34r/library-inlang-paraglideJs)
|
||||
- [Source Repository](https://github.com/opral/paraglide-js)
|
||||
2
ui/src/lib/paraglide/messages.js
Normal file
2
ui/src/lib/paraglide/messages.js
Normal file
@@ -0,0 +1,2 @@
|
||||
/* eslint-disable */
|
||||
export * from './messages/_index.js'
|
||||
386
ui/src/lib/paraglide/messages/_index.js
Normal file
386
ui/src/lib/paraglide/messages/_index.js
Normal file
@@ -0,0 +1,386 @@
|
||||
/* eslint-disable */
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
export * from './nav_library.js'
|
||||
export * from './nav_catalogue.js'
|
||||
export * from './nav_feedback.js'
|
||||
export * from './nav_admin.js'
|
||||
export * from './nav_profile.js'
|
||||
export * from './nav_sign_in.js'
|
||||
export * from './nav_sign_out.js'
|
||||
export * from './nav_toggle_menu.js'
|
||||
export * from './nav_admin_panel.js'
|
||||
export * from './footer_library.js'
|
||||
export * from './footer_catalogue.js'
|
||||
export * from './footer_feedback.js'
|
||||
export * from './footer_disclaimer.js'
|
||||
export * from './footer_privacy.js'
|
||||
export * from './footer_dmca.js'
|
||||
export * from './footer_copyright.js'
|
||||
export * from './footer_dev.js'
|
||||
export * from './home_title.js'
|
||||
export * from './home_stat_books.js'
|
||||
export * from './home_stat_chapters.js'
|
||||
export * from './home_stat_in_progress.js'
|
||||
export * from './home_continue_reading.js'
|
||||
export * from './home_view_all.js'
|
||||
export * from './home_recently_updated.js'
|
||||
export * from './home_from_following.js'
|
||||
export * from './home_empty_title.js'
|
||||
export * from './home_empty_body.js'
|
||||
export * from './home_discover_novels.js'
|
||||
export * from './home_via_reader.js'
|
||||
export * from './home_chapter_badge.js'
|
||||
export * from './player_generating.js'
|
||||
export * from './player_loading.js'
|
||||
export * from './player_chapters.js'
|
||||
export * from './player_chapter_n.js'
|
||||
export * from './player_toggle_chapter_list.js'
|
||||
export * from './player_chapter_list_label.js'
|
||||
export * from './player_close_chapter_list.js'
|
||||
export * from './player_rewind_15.js'
|
||||
export * from './player_skip_30.js'
|
||||
export * from './player_back_15.js'
|
||||
export * from './player_forward_30.js'
|
||||
export * from './player_play.js'
|
||||
export * from './player_pause.js'
|
||||
export * from './player_speed_label.js'
|
||||
export * from './player_seek_label.js'
|
||||
export * from './player_change_speed.js'
|
||||
export * from './player_auto_next_on.js'
|
||||
export * from './player_auto_next_off.js'
|
||||
export * from './player_auto_next_ready.js'
|
||||
export * from './player_auto_next_preparing.js'
|
||||
export * from './player_auto_next_aria.js'
|
||||
export * from './player_go_to_chapter.js'
|
||||
export * from './player_close.js'
|
||||
export * from './login_page_title.js'
|
||||
export * from './login_heading.js'
|
||||
export * from './login_subheading.js'
|
||||
export * from './login_continue_google.js'
|
||||
export * from './login_continue_github.js'
|
||||
export * from './login_terms_notice.js'
|
||||
export * from './login_error_oauth_state.js'
|
||||
export * from './login_error_oauth_failed.js'
|
||||
export * from './login_error_oauth_no_email.js'
|
||||
export * from './books_page_title.js'
|
||||
export * from './books_heading.js'
|
||||
export * from './books_empty_title.js'
|
||||
export * from './books_empty_body.js'
|
||||
export * from './books_browse_catalogue.js'
|
||||
export * from './books_chapter_count.js'
|
||||
export * from './books_last_read.js'
|
||||
export * from './books_reading_progress.js'
|
||||
export * from './books_remove.js'
|
||||
export * from './catalogue_page_title.js'
|
||||
export * from './catalogue_heading.js'
|
||||
export * from './catalogue_search_placeholder.js'
|
||||
export * from './catalogue_filter_genre.js'
|
||||
export * from './catalogue_filter_status.js'
|
||||
export * from './catalogue_filter_sort.js'
|
||||
export * from './catalogue_sort_popular.js'
|
||||
export * from './catalogue_sort_new.js'
|
||||
export * from './catalogue_sort_top_rated.js'
|
||||
export * from './catalogue_sort_rank.js'
|
||||
export * from './catalogue_status_all.js'
|
||||
export * from './catalogue_status_ongoing.js'
|
||||
export * from './catalogue_status_completed.js'
|
||||
export * from './catalogue_genre_all.js'
|
||||
export * from './catalogue_clear_filters.js'
|
||||
export * from './catalogue_reset.js'
|
||||
export * from './catalogue_no_results.js'
|
||||
export * from './catalogue_loading.js'
|
||||
export * from './catalogue_load_more.js'
|
||||
export * from './catalogue_results_count.js'
|
||||
export * from './book_detail_page_title.js'
|
||||
export * from './book_detail_signin_to_save.js'
|
||||
export * from './book_detail_add_to_library.js'
|
||||
export * from './book_detail_remove_from_library.js'
|
||||
export * from './book_detail_read_now.js'
|
||||
export * from './book_detail_continue_reading.js'
|
||||
export * from './book_detail_start_reading.js'
|
||||
export * from './book_detail_chapters.js'
|
||||
export * from './book_detail_status.js'
|
||||
export * from './book_detail_author.js'
|
||||
export * from './book_detail_genres.js'
|
||||
export * from './book_detail_description.js'
|
||||
export * from './book_detail_source.js'
|
||||
export * from './book_detail_rescrape.js'
|
||||
export * from './book_detail_scraping.js'
|
||||
export * from './book_detail_in_library.js'
|
||||
export * from './chapters_page_title.js'
|
||||
export * from './chapters_heading.js'
|
||||
export * from './chapters_back_to_book.js'
|
||||
export * from './chapters_reading_now.js'
|
||||
export * from './chapters_empty.js'
|
||||
export * from './reader_page_title.js'
|
||||
export * from './reader_play_narration.js'
|
||||
export * from './reader_generating_audio.js'
|
||||
export * from './reader_signin_for_audio.js'
|
||||
export * from './reader_signin_audio_desc.js'
|
||||
export * from './reader_audio_error.js'
|
||||
export * from './reader_prev_chapter.js'
|
||||
export * from './reader_next_chapter.js'
|
||||
export * from './reader_back_to_chapters.js'
|
||||
export * from './reader_chapter_n.js'
|
||||
export * from './reader_change_voice.js'
|
||||
export * from './reader_voice_panel_title.js'
|
||||
export * from './reader_voice_kokoro.js'
|
||||
export * from './reader_voice_pocket.js'
|
||||
export * from './reader_voice_play_sample.js'
|
||||
export * from './reader_voice_stop_sample.js'
|
||||
export * from './reader_voice_selected.js'
|
||||
export * from './reader_close_voice_panel.js'
|
||||
export * from './reader_auto_next.js'
|
||||
export * from './reader_speed.js'
|
||||
export * from './reader_preview_notice.js'
|
||||
export * from './profile_page_title.js'
|
||||
export * from './profile_heading.js'
|
||||
export * from './profile_avatar_label.js'
|
||||
export * from './profile_change_avatar.js'
|
||||
export * from './profile_username.js'
|
||||
export * from './profile_email.js'
|
||||
export * from './profile_change_password.js'
|
||||
export * from './profile_current_password.js'
|
||||
export * from './profile_new_password.js'
|
||||
export * from './profile_confirm_password.js'
|
||||
export * from './profile_save_password.js'
|
||||
export * from './profile_appearance_heading.js'
|
||||
export * from './profile_theme_label.js'
|
||||
export * from './profile_theme_amber.js'
|
||||
export * from './profile_theme_slate.js'
|
||||
export * from './profile_theme_rose.js'
|
||||
export * from './profile_theme_light.js'
|
||||
export * from './profile_theme_light_slate.js'
|
||||
export * from './profile_theme_light_rose.js'
|
||||
export * from './profile_reading_heading.js'
|
||||
export * from './profile_voice_label.js'
|
||||
export * from './profile_speed_label.js'
|
||||
export * from './profile_auto_next_label.js'
|
||||
export * from './profile_save_settings.js'
|
||||
export * from './profile_settings_saved.js'
|
||||
export * from './profile_settings_error.js'
|
||||
export * from './profile_password_saved.js'
|
||||
export * from './profile_password_error.js'
|
||||
export * from './profile_sessions_heading.js'
|
||||
export * from './profile_sign_out_all.js'
|
||||
export * from './profile_joined.js'
|
||||
export * from './user_page_title.js'
|
||||
export * from './user_library_heading.js'
|
||||
export * from './user_follow.js'
|
||||
export * from './user_unfollow.js'
|
||||
export * from './user_followers.js'
|
||||
export * from './user_following.js'
|
||||
export * from './user_library_empty.js'
|
||||
export * from './error_not_found_title.js'
|
||||
export * from './error_not_found_body.js'
|
||||
export * from './error_generic_title.js'
|
||||
export * from './error_go_home.js'
|
||||
export * from './error_status.js'
|
||||
export * from './admin_scrape_page_title.js'
|
||||
export * from './admin_scrape_heading.js'
|
||||
export * from './admin_scrape_catalogue.js'
|
||||
export * from './admin_scrape_book.js'
|
||||
export * from './admin_scrape_url_placeholder.js'
|
||||
export * from './admin_scrape_range.js'
|
||||
export * from './admin_scrape_from.js'
|
||||
export * from './admin_scrape_to.js'
|
||||
export * from './admin_scrape_submit.js'
|
||||
export * from './admin_scrape_cancel.js'
|
||||
export * from './admin_scrape_status_pending.js'
|
||||
export * from './admin_scrape_status_running.js'
|
||||
export * from './admin_scrape_status_done.js'
|
||||
export * from './admin_scrape_status_failed.js'
|
||||
export * from './admin_scrape_status_cancelled.js'
|
||||
export * from './admin_tasks_heading.js'
|
||||
export * from './admin_tasks_empty.js'
|
||||
export * from './admin_audio_page_title.js'
|
||||
export * from './admin_audio_heading.js'
|
||||
export * from './admin_audio_empty.js'
|
||||
export * from './admin_changelog_page_title.js'
|
||||
export * from './admin_changelog_heading.js'
|
||||
export * from './comments_heading.js'
|
||||
export * from './comments_empty.js'
|
||||
export * from './comments_placeholder.js'
|
||||
export * from './comments_submit.js'
|
||||
export * from './comments_login_prompt.js'
|
||||
export * from './comments_vote_up.js'
|
||||
export * from './comments_vote_down.js'
|
||||
export * from './comments_delete.js'
|
||||
export * from './comments_reply.js'
|
||||
export * from './comments_show_replies.js'
|
||||
export * from './comments_hide_replies.js'
|
||||
export * from './comments_edited.js'
|
||||
export * from './comments_deleted.js'
|
||||
export * from './disclaimer_page_title.js'
|
||||
export * from './privacy_page_title.js'
|
||||
export * from './dmca_page_title.js'
|
||||
export * from './terms_page_title.js'
|
||||
export * from './common_loading.js'
|
||||
export * from './common_error.js'
|
||||
export * from './common_save.js'
|
||||
export * from './common_cancel.js'
|
||||
export * from './common_close.js'
|
||||
export * from './common_search.js'
|
||||
export * from './common_back.js'
|
||||
export * from './common_next.js'
|
||||
export * from './common_previous.js'
|
||||
export * from './common_yes.js'
|
||||
export * from './common_no.js'
|
||||
export * from './common_on.js'
|
||||
export * from './common_off.js'
|
||||
export * from './locale_switcher_label.js'
|
||||
export * from './books_empty_library.js'
|
||||
export * from './books_empty_discover.js'
|
||||
export * from './books_empty_discover_link.js'
|
||||
export * from './books_empty_discover_suffix.js'
|
||||
export * from './books_count.js'
|
||||
export * from './catalogue_sort_updated.js'
|
||||
export * from './catalogue_search_button.js'
|
||||
export * from './catalogue_refresh.js'
|
||||
export * from './catalogue_refreshing.js'
|
||||
export * from './catalogue_refresh_mobile.js'
|
||||
export * from './catalogue_all_loaded.js'
|
||||
export * from './catalogue_scroll_top.js'
|
||||
export * from './catalogue_view_grid.js'
|
||||
export * from './catalogue_view_list.js'
|
||||
export * from './catalogue_browse_source.js'
|
||||
export * from './catalogue_search_results.js'
|
||||
export * from './catalogue_search_local_count.js'
|
||||
export * from './catalogue_rank_ranked.js'
|
||||
export * from './catalogue_rank_no_data.js'
|
||||
export * from './catalogue_rank_no_data_body.js'
|
||||
export * from './catalogue_rank_run_scrape_admin.js'
|
||||
export * from './catalogue_rank_run_scrape_user.js'
|
||||
export * from './catalogue_scrape_queued_flash.js'
|
||||
export * from './catalogue_scrape_busy_flash.js'
|
||||
export * from './catalogue_scrape_error_flash.js'
|
||||
export * from './catalogue_filters_label.js'
|
||||
export * from './catalogue_apply.js'
|
||||
export * from './catalogue_filter_rank_note.js'
|
||||
export * from './catalogue_no_results_search.js'
|
||||
export * from './catalogue_no_results_try.js'
|
||||
export * from './catalogue_no_results_filters.js'
|
||||
export * from './catalogue_scrape_queued_badge.js'
|
||||
export * from './catalogue_scrape_busy_badge.js'
|
||||
export * from './catalogue_scrape_busy_list.js'
|
||||
export * from './catalogue_scrape_forbidden_badge.js'
|
||||
export * from './catalogue_scrape_novel_button.js'
|
||||
export * from './catalogue_scraping_novel.js'
|
||||
export * from './book_detail_not_in_library.js'
|
||||
export * from './book_detail_continue_ch.js'
|
||||
export * from './book_detail_start_ch1.js'
|
||||
export * from './book_detail_preview_ch1.js'
|
||||
export * from './book_detail_reading_ch.js'
|
||||
export * from './book_detail_n_chapters.js'
|
||||
export * from './book_detail_rescraping.js'
|
||||
export * from './book_detail_from_chapter.js'
|
||||
export * from './book_detail_to_chapter.js'
|
||||
export * from './book_detail_range_queuing.js'
|
||||
export * from './book_detail_scrape_range.js'
|
||||
export * from './book_detail_admin.js'
|
||||
export * from './book_detail_scraping_progress.js'
|
||||
export * from './book_detail_scraping_home.js'
|
||||
export * from './book_detail_rescrape_book.js'
|
||||
export * from './book_detail_less.js'
|
||||
export * from './book_detail_more.js'
|
||||
export * from './chapters_search_placeholder.js'
|
||||
export * from './chapters_jump_to.js'
|
||||
export * from './chapters_no_match.js'
|
||||
export * from './chapters_none_available.js'
|
||||
export * from './chapters_reading_indicator.js'
|
||||
export * from './chapters_result_count.js'
|
||||
export * from './reader_fetching_chapter.js'
|
||||
export * from './reader_words.js'
|
||||
export * from './reader_preview_audio_notice.js'
|
||||
export * from './profile_click_to_change.js'
|
||||
export * from './profile_tts_voice.js'
|
||||
export * from './profile_auto_advance.js'
|
||||
export * from './profile_saving.js'
|
||||
export * from './profile_saved.js'
|
||||
export * from './profile_session_this.js'
|
||||
export * from './profile_session_signed_in.js'
|
||||
export * from './profile_session_last_seen.js'
|
||||
export * from './profile_session_sign_out.js'
|
||||
export * from './profile_session_end.js'
|
||||
export * from './profile_session_unrecognised.js'
|
||||
export * from './profile_no_sessions.js'
|
||||
export * from './profile_change_password_heading.js'
|
||||
export * from './profile_update_password.js'
|
||||
export * from './profile_updating.js'
|
||||
export * from './profile_password_changed_ok.js'
|
||||
export * from './profile_playback_speed.js'
|
||||
export * from './profile_subscription_heading.js'
|
||||
export * from './profile_plan_pro.js'
|
||||
export * from './profile_plan_free.js'
|
||||
export * from './profile_pro_active.js'
|
||||
export * from './profile_pro_perks.js'
|
||||
export * from './profile_manage_subscription.js'
|
||||
export * from './profile_upgrade_heading.js'
|
||||
export * from './profile_upgrade_desc.js'
|
||||
export * from './profile_upgrade_monthly.js'
|
||||
export * from './profile_upgrade_annual.js'
|
||||
export * from './profile_free_limits.js'
|
||||
export * from './user_currently_reading.js'
|
||||
export * from './user_library_count.js'
|
||||
export * from './user_joined.js'
|
||||
export * from './user_followers_label.js'
|
||||
export * from './user_following_label.js'
|
||||
export * from './user_no_books.js'
|
||||
export * from './admin_pages_label.js'
|
||||
export * from './admin_tools_label.js'
|
||||
export * from './admin_nav_scrape.js'
|
||||
export * from './admin_nav_audio.js'
|
||||
export * from './admin_nav_translation.js'
|
||||
export * from './admin_nav_changelog.js'
|
||||
export * from './admin_nav_feedback.js'
|
||||
export * from './admin_nav_errors.js'
|
||||
export * from './admin_nav_analytics.js'
|
||||
export * from './admin_nav_logs.js'
|
||||
export * from './admin_nav_uptime.js'
|
||||
export * from './admin_nav_push.js'
|
||||
export * from './admin_scrape_status_idle.js'
|
||||
export * from './admin_scrape_full_catalogue.js'
|
||||
export * from './admin_scrape_single_book.js'
|
||||
export * from './admin_scrape_quick_genres.js'
|
||||
export * from './admin_scrape_task_history.js'
|
||||
export * from './admin_scrape_filter_placeholder.js'
|
||||
export * from './admin_scrape_no_matching.js'
|
||||
export * from './admin_scrape_start.js'
|
||||
export * from './admin_scrape_queuing.js'
|
||||
export * from './admin_scrape_running.js'
|
||||
export * from './admin_audio_filter_jobs.js'
|
||||
export * from './admin_audio_filter_cache.js'
|
||||
export * from './admin_audio_no_matching_jobs.js'
|
||||
export * from './admin_audio_no_jobs.js'
|
||||
export * from './admin_audio_cache_empty.js'
|
||||
export * from './admin_audio_no_cache_results.js'
|
||||
export * from './admin_changelog_gitea.js'
|
||||
export * from './admin_changelog_no_releases.js'
|
||||
export * from './admin_changelog_load_error.js'
|
||||
export * from './comments_top.js'
|
||||
export * from './comments_new.js'
|
||||
export * from './comments_posting.js'
|
||||
export * from './comments_login_link.js'
|
||||
export * from './comments_login_suffix.js'
|
||||
export * from './comments_anonymous.js'
|
||||
export * from './reader_audio_narration.js'
|
||||
export * from './reader_playing.js'
|
||||
export * from './reader_paused.js'
|
||||
export * from './reader_ch_ready.js'
|
||||
export * from './reader_ch_preparing.js'
|
||||
export * from './reader_ch_generate_on_nav.js'
|
||||
export * from './reader_now_playing.js'
|
||||
export * from './reader_load_this_chapter.js'
|
||||
export * from './reader_generate_samples.js'
|
||||
export * from './reader_voice_applies_next.js'
|
||||
export * from './reader_choose_voice.js'
|
||||
export * from './reader_generating_narration.js'
|
||||
export * from './profile_font_family.js'
|
||||
export * from './profile_font_system.js'
|
||||
export * from './profile_font_serif.js'
|
||||
export * from './profile_font_mono.js'
|
||||
export * from './profile_text_size.js'
|
||||
export * from './profile_text_size_sm.js'
|
||||
export * from './profile_text_size_md.js'
|
||||
export * from './profile_text_size_lg.js'
|
||||
export * from './profile_text_size_xl.js'
|
||||
44
ui/src/lib/paraglide/messages/admin_audio_cache_empty.js
Normal file
44
ui/src/lib/paraglide/messages/admin_audio_cache_empty.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_Cache_EmptyInputs */
|
||||
|
||||
const en_admin_audio_cache_empty = /** @type {(inputs: Admin_Audio_Cache_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Audio cache is empty.`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_cache_empty = /** @type {(inputs: Admin_Audio_Cache_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Аудиокэш пуст.`)
|
||||
};
|
||||
|
||||
const id_admin_audio_cache_empty = /** @type {(inputs: Admin_Audio_Cache_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Cache audio kosong.`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_cache_empty = /** @type {(inputs: Admin_Audio_Cache_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Cache de áudio vazio.`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_cache_empty = /** @type {(inputs: Admin_Audio_Cache_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Cache audio vide.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Audio cache is empty." |
|
||||
*
|
||||
* @param {Admin_Audio_Cache_EmptyInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_cache_empty = /** @type {((inputs?: Admin_Audio_Cache_EmptyInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_Cache_EmptyInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_cache_empty(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_cache_empty(inputs)
|
||||
if (locale === "id") return id_admin_audio_cache_empty(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_cache_empty(inputs)
|
||||
return fr_admin_audio_cache_empty(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_audio_empty.js
Normal file
44
ui/src/lib/paraglide/messages/admin_audio_empty.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_EmptyInputs */
|
||||
|
||||
const en_admin_audio_empty = /** @type {(inputs: Admin_Audio_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No audio jobs.`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_empty = /** @type {(inputs: Admin_Audio_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Аудио задач нет.`)
|
||||
};
|
||||
|
||||
const id_admin_audio_empty = /** @type {(inputs: Admin_Audio_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tidak ada tugas audio.`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_empty = /** @type {(inputs: Admin_Audio_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Nenhuma tarefa de áudio.`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_empty = /** @type {(inputs: Admin_Audio_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Aucune tâche audio.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "No audio jobs." |
|
||||
*
|
||||
* @param {Admin_Audio_EmptyInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_empty = /** @type {((inputs?: Admin_Audio_EmptyInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_EmptyInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_empty(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_empty(inputs)
|
||||
if (locale === "id") return id_admin_audio_empty(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_empty(inputs)
|
||||
return fr_admin_audio_empty(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_audio_filter_cache.js
Normal file
44
ui/src/lib/paraglide/messages/admin_audio_filter_cache.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_Filter_CacheInputs */
|
||||
|
||||
const en_admin_audio_filter_cache = /** @type {(inputs: Admin_Audio_Filter_CacheInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by slug, chapter or voice…`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_filter_cache = /** @type {(inputs: Admin_Audio_Filter_CacheInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Фильтр по slug, главе или голосу…`)
|
||||
};
|
||||
|
||||
const id_admin_audio_filter_cache = /** @type {(inputs: Admin_Audio_Filter_CacheInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter berdasarkan slug, bab, atau suara…`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_filter_cache = /** @type {(inputs: Admin_Audio_Filter_CacheInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filtrar por slug, capítulo ou voz…`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_filter_cache = /** @type {(inputs: Admin_Audio_Filter_CacheInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filtrer par slug, chapitre ou voix…`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Filter by slug, chapter or voice…" |
|
||||
*
|
||||
* @param {Admin_Audio_Filter_CacheInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_filter_cache = /** @type {((inputs?: Admin_Audio_Filter_CacheInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_Filter_CacheInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_filter_cache(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_filter_cache(inputs)
|
||||
if (locale === "id") return id_admin_audio_filter_cache(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_filter_cache(inputs)
|
||||
return fr_admin_audio_filter_cache(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_audio_filter_jobs.js
Normal file
44
ui/src/lib/paraglide/messages/admin_audio_filter_jobs.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_Filter_JobsInputs */
|
||||
|
||||
const en_admin_audio_filter_jobs = /** @type {(inputs: Admin_Audio_Filter_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by slug, voice or status…`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_filter_jobs = /** @type {(inputs: Admin_Audio_Filter_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Фильтр по slug, голосу или статусу…`)
|
||||
};
|
||||
|
||||
const id_admin_audio_filter_jobs = /** @type {(inputs: Admin_Audio_Filter_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter berdasarkan slug, suara, atau status…`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_filter_jobs = /** @type {(inputs: Admin_Audio_Filter_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filtrar por slug, voz ou status…`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_filter_jobs = /** @type {(inputs: Admin_Audio_Filter_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filtrer par slug, voix ou statut…`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Filter by slug, voice or status…" |
|
||||
*
|
||||
* @param {Admin_Audio_Filter_JobsInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_filter_jobs = /** @type {((inputs?: Admin_Audio_Filter_JobsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_Filter_JobsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_filter_jobs(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_filter_jobs(inputs)
|
||||
if (locale === "id") return id_admin_audio_filter_jobs(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_filter_jobs(inputs)
|
||||
return fr_admin_audio_filter_jobs(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_audio_heading.js
Normal file
44
ui/src/lib/paraglide/messages/admin_audio_heading.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_HeadingInputs */
|
||||
|
||||
const en_admin_audio_heading = /** @type {(inputs: Admin_Audio_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Audio Jobs`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_heading = /** @type {(inputs: Admin_Audio_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Аудио задачи`)
|
||||
};
|
||||
|
||||
const id_admin_audio_heading = /** @type {(inputs: Admin_Audio_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tugas Audio`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_heading = /** @type {(inputs: Admin_Audio_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tarefas de Áudio`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_heading = /** @type {(inputs: Admin_Audio_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tâches audio`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Audio Jobs" |
|
||||
*
|
||||
* @param {Admin_Audio_HeadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_heading = /** @type {((inputs?: Admin_Audio_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_heading(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_heading(inputs)
|
||||
if (locale === "id") return id_admin_audio_heading(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_heading(inputs)
|
||||
return fr_admin_audio_heading(inputs)
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_No_Cache_ResultsInputs */
|
||||
|
||||
const en_admin_audio_no_cache_results = /** @type {(inputs: Admin_Audio_No_Cache_ResultsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No results.`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_no_cache_results = /** @type {(inputs: Admin_Audio_No_Cache_ResultsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Результатов нет.`)
|
||||
};
|
||||
|
||||
const id_admin_audio_no_cache_results = /** @type {(inputs: Admin_Audio_No_Cache_ResultsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tidak ada hasil.`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_no_cache_results = /** @type {(inputs: Admin_Audio_No_Cache_ResultsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Sem resultados.`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_no_cache_results = /** @type {(inputs: Admin_Audio_No_Cache_ResultsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Aucun résultat.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "No results." |
|
||||
*
|
||||
* @param {Admin_Audio_No_Cache_ResultsInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_no_cache_results = /** @type {((inputs?: Admin_Audio_No_Cache_ResultsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_No_Cache_ResultsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_no_cache_results(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_no_cache_results(inputs)
|
||||
if (locale === "id") return id_admin_audio_no_cache_results(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_no_cache_results(inputs)
|
||||
return fr_admin_audio_no_cache_results(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_audio_no_jobs.js
Normal file
44
ui/src/lib/paraglide/messages/admin_audio_no_jobs.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_No_JobsInputs */
|
||||
|
||||
const en_admin_audio_no_jobs = /** @type {(inputs: Admin_Audio_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No audio jobs yet.`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_no_jobs = /** @type {(inputs: Admin_Audio_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Аудиозаданий пока нет.`)
|
||||
};
|
||||
|
||||
const id_admin_audio_no_jobs = /** @type {(inputs: Admin_Audio_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Belum ada pekerjaan audio.`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_no_jobs = /** @type {(inputs: Admin_Audio_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Nenhum job de áudio ainda.`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_no_jobs = /** @type {(inputs: Admin_Audio_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Aucun job audio pour l'instant.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "No audio jobs yet." |
|
||||
*
|
||||
* @param {Admin_Audio_No_JobsInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_no_jobs = /** @type {((inputs?: Admin_Audio_No_JobsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_No_JobsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_no_jobs(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_no_jobs(inputs)
|
||||
if (locale === "id") return id_admin_audio_no_jobs(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_no_jobs(inputs)
|
||||
return fr_admin_audio_no_jobs(inputs)
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_No_Matching_JobsInputs */
|
||||
|
||||
const en_admin_audio_no_matching_jobs = /** @type {(inputs: Admin_Audio_No_Matching_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No matching jobs.`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_no_matching_jobs = /** @type {(inputs: Admin_Audio_No_Matching_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Заданий не найдено.`)
|
||||
};
|
||||
|
||||
const id_admin_audio_no_matching_jobs = /** @type {(inputs: Admin_Audio_No_Matching_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tidak ada pekerjaan yang cocok.`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_no_matching_jobs = /** @type {(inputs: Admin_Audio_No_Matching_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Nenhum job correspondente.`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_no_matching_jobs = /** @type {(inputs: Admin_Audio_No_Matching_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Aucun job correspondant.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "No matching jobs." |
|
||||
*
|
||||
* @param {Admin_Audio_No_Matching_JobsInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_no_matching_jobs = /** @type {((inputs?: Admin_Audio_No_Matching_JobsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_No_Matching_JobsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_no_matching_jobs(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_no_matching_jobs(inputs)
|
||||
if (locale === "id") return id_admin_audio_no_matching_jobs(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_no_matching_jobs(inputs)
|
||||
return fr_admin_audio_no_matching_jobs(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_audio_page_title.js
Normal file
44
ui/src/lib/paraglide/messages/admin_audio_page_title.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Audio_Page_TitleInputs */
|
||||
|
||||
const en_admin_audio_page_title = /** @type {(inputs: Admin_Audio_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Audio — Admin`)
|
||||
};
|
||||
|
||||
const ru_admin_audio_page_title = /** @type {(inputs: Admin_Audio_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Аудио — Админ`)
|
||||
};
|
||||
|
||||
const id_admin_audio_page_title = /** @type {(inputs: Admin_Audio_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Audio — Admin`)
|
||||
};
|
||||
|
||||
const pt_admin_audio_page_title = /** @type {(inputs: Admin_Audio_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Áudio — Admin`)
|
||||
};
|
||||
|
||||
const fr_admin_audio_page_title = /** @type {(inputs: Admin_Audio_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Audio — Admin`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Audio — Admin" |
|
||||
*
|
||||
* @param {Admin_Audio_Page_TitleInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_audio_page_title = /** @type {((inputs?: Admin_Audio_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Audio_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_audio_page_title(inputs)
|
||||
if (locale === "ru") return ru_admin_audio_page_title(inputs)
|
||||
if (locale === "id") return id_admin_audio_page_title(inputs)
|
||||
if (locale === "pt") return pt_admin_audio_page_title(inputs)
|
||||
return fr_admin_audio_page_title(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_changelog_gitea.js
Normal file
44
ui/src/lib/paraglide/messages/admin_changelog_gitea.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Changelog_GiteaInputs */
|
||||
|
||||
const en_admin_changelog_gitea = /** @type {(inputs: Admin_Changelog_GiteaInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Gitea releases`)
|
||||
};
|
||||
|
||||
const ru_admin_changelog_gitea = /** @type {(inputs: Admin_Changelog_GiteaInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Релизы Gitea`)
|
||||
};
|
||||
|
||||
const id_admin_changelog_gitea = /** @type {(inputs: Admin_Changelog_GiteaInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Rilis Gitea`)
|
||||
};
|
||||
|
||||
const pt_admin_changelog_gitea = /** @type {(inputs: Admin_Changelog_GiteaInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Releases do Gitea`)
|
||||
};
|
||||
|
||||
const fr_admin_changelog_gitea = /** @type {(inputs: Admin_Changelog_GiteaInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Releases Gitea`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Gitea releases" |
|
||||
*
|
||||
* @param {Admin_Changelog_GiteaInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_changelog_gitea = /** @type {((inputs?: Admin_Changelog_GiteaInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Changelog_GiteaInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_changelog_gitea(inputs)
|
||||
if (locale === "ru") return ru_admin_changelog_gitea(inputs)
|
||||
if (locale === "id") return id_admin_changelog_gitea(inputs)
|
||||
if (locale === "pt") return pt_admin_changelog_gitea(inputs)
|
||||
return fr_admin_changelog_gitea(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_changelog_heading.js
Normal file
44
ui/src/lib/paraglide/messages/admin_changelog_heading.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Changelog_HeadingInputs */
|
||||
|
||||
const en_admin_changelog_heading = /** @type {(inputs: Admin_Changelog_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog`)
|
||||
};
|
||||
|
||||
const ru_admin_changelog_heading = /** @type {(inputs: Admin_Changelog_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog`)
|
||||
};
|
||||
|
||||
const id_admin_changelog_heading = /** @type {(inputs: Admin_Changelog_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog`)
|
||||
};
|
||||
|
||||
const pt_admin_changelog_heading = /** @type {(inputs: Admin_Changelog_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog`)
|
||||
};
|
||||
|
||||
const fr_admin_changelog_heading = /** @type {(inputs: Admin_Changelog_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Changelog" |
|
||||
*
|
||||
* @param {Admin_Changelog_HeadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_changelog_heading = /** @type {((inputs?: Admin_Changelog_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Changelog_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_changelog_heading(inputs)
|
||||
if (locale === "ru") return ru_admin_changelog_heading(inputs)
|
||||
if (locale === "id") return id_admin_changelog_heading(inputs)
|
||||
if (locale === "pt") return pt_admin_changelog_heading(inputs)
|
||||
return fr_admin_changelog_heading(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_changelog_load_error.js
Normal file
44
ui/src/lib/paraglide/messages/admin_changelog_load_error.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{ error: NonNullable<unknown> }} Admin_Changelog_Load_ErrorInputs */
|
||||
|
||||
const en_admin_changelog_load_error = /** @type {(inputs: Admin_Changelog_Load_ErrorInputs) => LocalizedString} */ (i) => {
|
||||
return /** @type {LocalizedString} */ (`Could not load releases: ${i?.error}`)
|
||||
};
|
||||
|
||||
const ru_admin_changelog_load_error = /** @type {(inputs: Admin_Changelog_Load_ErrorInputs) => LocalizedString} */ (i) => {
|
||||
return /** @type {LocalizedString} */ (`Не удалось загрузить релизы: ${i?.error}`)
|
||||
};
|
||||
|
||||
const id_admin_changelog_load_error = /** @type {(inputs: Admin_Changelog_Load_ErrorInputs) => LocalizedString} */ (i) => {
|
||||
return /** @type {LocalizedString} */ (`Gagal memuat rilis: ${i?.error}`)
|
||||
};
|
||||
|
||||
const pt_admin_changelog_load_error = /** @type {(inputs: Admin_Changelog_Load_ErrorInputs) => LocalizedString} */ (i) => {
|
||||
return /** @type {LocalizedString} */ (`Não foi possível carregar os releases: ${i?.error}`)
|
||||
};
|
||||
|
||||
const fr_admin_changelog_load_error = /** @type {(inputs: Admin_Changelog_Load_ErrorInputs) => LocalizedString} */ (i) => {
|
||||
return /** @type {LocalizedString} */ (`Impossible de charger les releases : ${i?.error}`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Could not load releases: {error}" |
|
||||
*
|
||||
* @param {Admin_Changelog_Load_ErrorInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_changelog_load_error = /** @type {((inputs: Admin_Changelog_Load_ErrorInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Changelog_Load_ErrorInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_changelog_load_error(inputs)
|
||||
if (locale === "ru") return ru_admin_changelog_load_error(inputs)
|
||||
if (locale === "id") return id_admin_changelog_load_error(inputs)
|
||||
if (locale === "pt") return pt_admin_changelog_load_error(inputs)
|
||||
return fr_admin_changelog_load_error(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_changelog_no_releases.js
Normal file
44
ui/src/lib/paraglide/messages/admin_changelog_no_releases.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Changelog_No_ReleasesInputs */
|
||||
|
||||
const en_admin_changelog_no_releases = /** @type {(inputs: Admin_Changelog_No_ReleasesInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No releases found.`)
|
||||
};
|
||||
|
||||
const ru_admin_changelog_no_releases = /** @type {(inputs: Admin_Changelog_No_ReleasesInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Релизов не найдено.`)
|
||||
};
|
||||
|
||||
const id_admin_changelog_no_releases = /** @type {(inputs: Admin_Changelog_No_ReleasesInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tidak ada rilis.`)
|
||||
};
|
||||
|
||||
const pt_admin_changelog_no_releases = /** @type {(inputs: Admin_Changelog_No_ReleasesInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Nenhum release encontrado.`)
|
||||
};
|
||||
|
||||
const fr_admin_changelog_no_releases = /** @type {(inputs: Admin_Changelog_No_ReleasesInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Aucune release trouvée.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "No releases found." |
|
||||
*
|
||||
* @param {Admin_Changelog_No_ReleasesInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_changelog_no_releases = /** @type {((inputs?: Admin_Changelog_No_ReleasesInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Changelog_No_ReleasesInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_changelog_no_releases(inputs)
|
||||
if (locale === "ru") return ru_admin_changelog_no_releases(inputs)
|
||||
if (locale === "id") return id_admin_changelog_no_releases(inputs)
|
||||
if (locale === "pt") return pt_admin_changelog_no_releases(inputs)
|
||||
return fr_admin_changelog_no_releases(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_changelog_page_title.js
Normal file
44
ui/src/lib/paraglide/messages/admin_changelog_page_title.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Changelog_Page_TitleInputs */
|
||||
|
||||
const en_admin_changelog_page_title = /** @type {(inputs: Admin_Changelog_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog — Admin`)
|
||||
};
|
||||
|
||||
const ru_admin_changelog_page_title = /** @type {(inputs: Admin_Changelog_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog — Админ`)
|
||||
};
|
||||
|
||||
const id_admin_changelog_page_title = /** @type {(inputs: Admin_Changelog_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog — Admin`)
|
||||
};
|
||||
|
||||
const pt_admin_changelog_page_title = /** @type {(inputs: Admin_Changelog_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog — Admin`)
|
||||
};
|
||||
|
||||
const fr_admin_changelog_page_title = /** @type {(inputs: Admin_Changelog_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Changelog — Admin`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Changelog — Admin" |
|
||||
*
|
||||
* @param {Admin_Changelog_Page_TitleInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_changelog_page_title = /** @type {((inputs?: Admin_Changelog_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Changelog_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_changelog_page_title(inputs)
|
||||
if (locale === "ru") return ru_admin_changelog_page_title(inputs)
|
||||
if (locale === "id") return id_admin_changelog_page_title(inputs)
|
||||
if (locale === "pt") return pt_admin_changelog_page_title(inputs)
|
||||
return fr_admin_changelog_page_title(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_analytics.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_analytics.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_AnalyticsInputs */
|
||||
|
||||
const en_admin_nav_analytics = (_inputs = {}) => /** @type {LocalizedString} */ (`Analytics`);
|
||||
const ru_admin_nav_analytics = (_inputs = {}) => /** @type {LocalizedString} */ (`Аналитика`);
|
||||
const id_admin_nav_analytics = (_inputs = {}) => /** @type {LocalizedString} */ (`Analitik`);
|
||||
const pt_admin_nav_analytics = (_inputs = {}) => /** @type {LocalizedString} */ (`Análise`);
|
||||
const fr_admin_nav_analytics = (_inputs = {}) => /** @type {LocalizedString} */ (`Analytique`);
|
||||
|
||||
export const admin_nav_analytics = /** @type {((inputs?: Admin_Nav_AnalyticsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_AnalyticsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_analytics(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_analytics(inputs)
|
||||
if (locale === "id") return id_admin_nav_analytics(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_analytics(inputs)
|
||||
return fr_admin_nav_analytics(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_audio.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_audio.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_AudioInputs */
|
||||
|
||||
const en_admin_nav_audio = (_inputs = {}) => /** @type {LocalizedString} */ (`Audio`);
|
||||
const ru_admin_nav_audio = (_inputs = {}) => /** @type {LocalizedString} */ (`Аудио`);
|
||||
const id_admin_nav_audio = (_inputs = {}) => /** @type {LocalizedString} */ (`Audio`);
|
||||
const pt_admin_nav_audio = (_inputs = {}) => /** @type {LocalizedString} */ (`Áudio`);
|
||||
const fr_admin_nav_audio = (_inputs = {}) => /** @type {LocalizedString} */ (`Audio`);
|
||||
|
||||
export const admin_nav_audio = /** @type {((inputs?: Admin_Nav_AudioInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_AudioInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_audio(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_audio(inputs)
|
||||
if (locale === "id") return id_admin_nav_audio(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_audio(inputs)
|
||||
return fr_admin_nav_audio(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_changelog.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_changelog.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_ChangelogInputs */
|
||||
|
||||
const en_admin_nav_changelog = (_inputs = {}) => /** @type {LocalizedString} */ (`Changelog`);
|
||||
const ru_admin_nav_changelog = (_inputs = {}) => /** @type {LocalizedString} */ (`Изменения`);
|
||||
const id_admin_nav_changelog = (_inputs = {}) => /** @type {LocalizedString} */ (`Perubahan`);
|
||||
const pt_admin_nav_changelog = (_inputs = {}) => /** @type {LocalizedString} */ (`Alterações`);
|
||||
const fr_admin_nav_changelog = (_inputs = {}) => /** @type {LocalizedString} */ (`Modifications`);
|
||||
|
||||
export const admin_nav_changelog = /** @type {((inputs?: Admin_Nav_ChangelogInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_ChangelogInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_changelog(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_changelog(inputs)
|
||||
if (locale === "id") return id_admin_nav_changelog(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_changelog(inputs)
|
||||
return fr_admin_nav_changelog(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_errors.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_errors.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_ErrorsInputs */
|
||||
|
||||
const en_admin_nav_errors = (_inputs = {}) => /** @type {LocalizedString} */ (`Errors`);
|
||||
const ru_admin_nav_errors = (_inputs = {}) => /** @type {LocalizedString} */ (`Ошибки`);
|
||||
const id_admin_nav_errors = (_inputs = {}) => /** @type {LocalizedString} */ (`Kesalahan`);
|
||||
const pt_admin_nav_errors = (_inputs = {}) => /** @type {LocalizedString} */ (`Erros`);
|
||||
const fr_admin_nav_errors = (_inputs = {}) => /** @type {LocalizedString} */ (`Erreurs`);
|
||||
|
||||
export const admin_nav_errors = /** @type {((inputs?: Admin_Nav_ErrorsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_ErrorsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_errors(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_errors(inputs)
|
||||
if (locale === "id") return id_admin_nav_errors(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_errors(inputs)
|
||||
return fr_admin_nav_errors(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_feedback.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_feedback.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_FeedbackInputs */
|
||||
|
||||
const en_admin_nav_feedback = (_inputs = {}) => /** @type {LocalizedString} */ (`Feedback`);
|
||||
const ru_admin_nav_feedback = (_inputs = {}) => /** @type {LocalizedString} */ (`Отзывы`);
|
||||
const id_admin_nav_feedback = (_inputs = {}) => /** @type {LocalizedString} */ (`Masukan`);
|
||||
const pt_admin_nav_feedback = (_inputs = {}) => /** @type {LocalizedString} */ (`Feedback`);
|
||||
const fr_admin_nav_feedback = (_inputs = {}) => /** @type {LocalizedString} */ (`Retours`);
|
||||
|
||||
export const admin_nav_feedback = /** @type {((inputs?: Admin_Nav_FeedbackInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_FeedbackInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_feedback(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_feedback(inputs)
|
||||
if (locale === "id") return id_admin_nav_feedback(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_feedback(inputs)
|
||||
return fr_admin_nav_feedback(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_logs.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_logs.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_LogsInputs */
|
||||
|
||||
const en_admin_nav_logs = (_inputs = {}) => /** @type {LocalizedString} */ (`Logs`);
|
||||
const ru_admin_nav_logs = (_inputs = {}) => /** @type {LocalizedString} */ (`Логи`);
|
||||
const id_admin_nav_logs = (_inputs = {}) => /** @type {LocalizedString} */ (`Log`);
|
||||
const pt_admin_nav_logs = (_inputs = {}) => /** @type {LocalizedString} */ (`Logs`);
|
||||
const fr_admin_nav_logs = (_inputs = {}) => /** @type {LocalizedString} */ (`Journaux`);
|
||||
|
||||
export const admin_nav_logs = /** @type {((inputs?: Admin_Nav_LogsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_LogsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_logs(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_logs(inputs)
|
||||
if (locale === "id") return id_admin_nav_logs(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_logs(inputs)
|
||||
return fr_admin_nav_logs(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_push.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_push.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_PushInputs */
|
||||
|
||||
const en_admin_nav_push = (_inputs = {}) => /** @type {LocalizedString} */ (`Push`);
|
||||
const ru_admin_nav_push = (_inputs = {}) => /** @type {LocalizedString} */ (`Уведомления`);
|
||||
const id_admin_nav_push = (_inputs = {}) => /** @type {LocalizedString} */ (`Notifikasi`);
|
||||
const pt_admin_nav_push = (_inputs = {}) => /** @type {LocalizedString} */ (`Notificações`);
|
||||
const fr_admin_nav_push = (_inputs = {}) => /** @type {LocalizedString} */ (`Notifications`);
|
||||
|
||||
export const admin_nav_push = /** @type {((inputs?: Admin_Nav_PushInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_PushInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_push(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_push(inputs)
|
||||
if (locale === "id") return id_admin_nav_push(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_push(inputs)
|
||||
return fr_admin_nav_push(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_scrape.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_scrape.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_ScrapeInputs */
|
||||
|
||||
const en_admin_nav_scrape = (_inputs = {}) => /** @type {LocalizedString} */ (`Scrape`);
|
||||
const ru_admin_nav_scrape = (_inputs = {}) => /** @type {LocalizedString} */ (`Скрейпинг`);
|
||||
const id_admin_nav_scrape = (_inputs = {}) => /** @type {LocalizedString} */ (`Scrape`);
|
||||
const pt_admin_nav_scrape = (_inputs = {}) => /** @type {LocalizedString} */ (`Scrape`);
|
||||
const fr_admin_nav_scrape = (_inputs = {}) => /** @type {LocalizedString} */ (`Scrape`);
|
||||
|
||||
export const admin_nav_scrape = /** @type {((inputs?: Admin_Nav_ScrapeInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_ScrapeInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_scrape(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_scrape(inputs)
|
||||
if (locale === "id") return id_admin_nav_scrape(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_scrape(inputs)
|
||||
return fr_admin_nav_scrape(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_translation.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_translation.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_TranslationInputs */
|
||||
|
||||
const en_admin_nav_translation = (_inputs = {}) => /** @type {LocalizedString} */ (`Translation`);
|
||||
const ru_admin_nav_translation = (_inputs = {}) => /** @type {LocalizedString} */ (`Перевод`);
|
||||
const id_admin_nav_translation = (_inputs = {}) => /** @type {LocalizedString} */ (`Terjemahan`);
|
||||
const pt_admin_nav_translation = (_inputs = {}) => /** @type {LocalizedString} */ (`Tradução`);
|
||||
const fr_admin_nav_translation = (_inputs = {}) => /** @type {LocalizedString} */ (`Traduction`);
|
||||
|
||||
export const admin_nav_translation = /** @type {((inputs?: Admin_Nav_TranslationInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_TranslationInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_translation(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_translation(inputs)
|
||||
if (locale === "id") return id_admin_nav_translation(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_translation(inputs)
|
||||
return fr_admin_nav_translation(inputs)
|
||||
});
|
||||
21
ui/src/lib/paraglide/messages/admin_nav_uptime.js
Normal file
21
ui/src/lib/paraglide/messages/admin_nav_uptime.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Nav_UptimeInputs */
|
||||
|
||||
const en_admin_nav_uptime = (_inputs = {}) => /** @type {LocalizedString} */ (`Uptime`);
|
||||
const ru_admin_nav_uptime = (_inputs = {}) => /** @type {LocalizedString} */ (`Мониторинг`);
|
||||
const id_admin_nav_uptime = (_inputs = {}) => /** @type {LocalizedString} */ (`Uptime`);
|
||||
const pt_admin_nav_uptime = (_inputs = {}) => /** @type {LocalizedString} */ (`Uptime`);
|
||||
const fr_admin_nav_uptime = (_inputs = {}) => /** @type {LocalizedString} */ (`Disponibilité`);
|
||||
|
||||
export const admin_nav_uptime = /** @type {((inputs?: Admin_Nav_UptimeInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_UptimeInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_nav_uptime(inputs)
|
||||
if (locale === "ru") return ru_admin_nav_uptime(inputs)
|
||||
if (locale === "id") return id_admin_nav_uptime(inputs)
|
||||
if (locale === "pt") return pt_admin_nav_uptime(inputs)
|
||||
return fr_admin_nav_uptime(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_pages_label.js
Normal file
44
ui/src/lib/paraglide/messages/admin_pages_label.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Pages_LabelInputs */
|
||||
|
||||
const en_admin_pages_label = /** @type {(inputs: Admin_Pages_LabelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Pages`)
|
||||
};
|
||||
|
||||
const ru_admin_pages_label = /** @type {(inputs: Admin_Pages_LabelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Страницы`)
|
||||
};
|
||||
|
||||
const id_admin_pages_label = /** @type {(inputs: Admin_Pages_LabelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Halaman`)
|
||||
};
|
||||
|
||||
const pt_admin_pages_label = /** @type {(inputs: Admin_Pages_LabelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Páginas`)
|
||||
};
|
||||
|
||||
const fr_admin_pages_label = /** @type {(inputs: Admin_Pages_LabelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Pages`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Pages" |
|
||||
*
|
||||
* @param {Admin_Pages_LabelInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_pages_label = /** @type {((inputs?: Admin_Pages_LabelInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Pages_LabelInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_pages_label(inputs)
|
||||
if (locale === "ru") return ru_admin_pages_label(inputs)
|
||||
if (locale === "id") return id_admin_pages_label(inputs)
|
||||
if (locale === "pt") return pt_admin_pages_label(inputs)
|
||||
return fr_admin_pages_label(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_book.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_book.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_BookInputs */
|
||||
|
||||
const en_admin_scrape_book = /** @type {(inputs: Admin_Scrape_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape Book`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_book = /** @type {(inputs: Admin_Scrape_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Парсинг книги`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_book = /** @type {(inputs: Admin_Scrape_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape Buku`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_book = /** @type {(inputs: Admin_Scrape_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extrair Livro`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_book = /** @type {(inputs: Admin_Scrape_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extraire un livre`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Scrape Book" |
|
||||
*
|
||||
* @param {Admin_Scrape_BookInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_book = /** @type {((inputs?: Admin_Scrape_BookInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_BookInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_book(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_book(inputs)
|
||||
if (locale === "id") return id_admin_scrape_book(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_book(inputs)
|
||||
return fr_admin_scrape_book(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_cancel.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_cancel.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_CancelInputs */
|
||||
|
||||
const en_admin_scrape_cancel = /** @type {(inputs: Admin_Scrape_CancelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Cancel`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_cancel = /** @type {(inputs: Admin_Scrape_CancelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Отмена`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_cancel = /** @type {(inputs: Admin_Scrape_CancelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Batal`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_cancel = /** @type {(inputs: Admin_Scrape_CancelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Cancelar`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_cancel = /** @type {(inputs: Admin_Scrape_CancelInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Annuler`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Cancel" |
|
||||
*
|
||||
* @param {Admin_Scrape_CancelInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_cancel = /** @type {((inputs?: Admin_Scrape_CancelInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_CancelInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_cancel(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_cancel(inputs)
|
||||
if (locale === "id") return id_admin_scrape_cancel(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_cancel(inputs)
|
||||
return fr_admin_scrape_cancel(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_catalogue.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_catalogue.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_CatalogueInputs */
|
||||
|
||||
const en_admin_scrape_catalogue = /** @type {(inputs: Admin_Scrape_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape Catalogue`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_catalogue = /** @type {(inputs: Admin_Scrape_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Парсинг каталога`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_catalogue = /** @type {(inputs: Admin_Scrape_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape Katalog`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_catalogue = /** @type {(inputs: Admin_Scrape_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extrair Catálogo`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_catalogue = /** @type {(inputs: Admin_Scrape_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extraire le catalogue`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Scrape Catalogue" |
|
||||
*
|
||||
* @param {Admin_Scrape_CatalogueInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_catalogue = /** @type {((inputs?: Admin_Scrape_CatalogueInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_CatalogueInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_catalogue(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_catalogue(inputs)
|
||||
if (locale === "id") return id_admin_scrape_catalogue(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_catalogue(inputs)
|
||||
return fr_admin_scrape_catalogue(inputs)
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Filter_PlaceholderInputs */
|
||||
|
||||
const en_admin_scrape_filter_placeholder = /** @type {(inputs: Admin_Scrape_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by kind, status or URL…`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_filter_placeholder = /** @type {(inputs: Admin_Scrape_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Фильтр по типу, статусу или URL…`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_filter_placeholder = /** @type {(inputs: Admin_Scrape_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter berdasarkan jenis, status, atau URL…`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_filter_placeholder = /** @type {(inputs: Admin_Scrape_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filtrar por tipo, status ou URL…`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_filter_placeholder = /** @type {(inputs: Admin_Scrape_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filtrer par type, statut ou URL…`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Filter by kind, status or URL…" |
|
||||
*
|
||||
* @param {Admin_Scrape_Filter_PlaceholderInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_filter_placeholder = /** @type {((inputs?: Admin_Scrape_Filter_PlaceholderInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Filter_PlaceholderInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_filter_placeholder(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_filter_placeholder(inputs)
|
||||
if (locale === "id") return id_admin_scrape_filter_placeholder(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_filter_placeholder(inputs)
|
||||
return fr_admin_scrape_filter_placeholder(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_from.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_from.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_FromInputs */
|
||||
|
||||
const en_admin_scrape_from = /** @type {(inputs: Admin_Scrape_FromInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`From`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_from = /** @type {(inputs: Admin_Scrape_FromInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`От`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_from = /** @type {(inputs: Admin_Scrape_FromInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Dari`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_from = /** @type {(inputs: Admin_Scrape_FromInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`De`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_from = /** @type {(inputs: Admin_Scrape_FromInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`De`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "From" |
|
||||
*
|
||||
* @param {Admin_Scrape_FromInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_from = /** @type {((inputs?: Admin_Scrape_FromInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_FromInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_from(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_from(inputs)
|
||||
if (locale === "id") return id_admin_scrape_from(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_from(inputs)
|
||||
return fr_admin_scrape_from(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_full_catalogue.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_full_catalogue.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Full_CatalogueInputs */
|
||||
|
||||
const en_admin_scrape_full_catalogue = /** @type {(inputs: Admin_Scrape_Full_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Full catalogue`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_full_catalogue = /** @type {(inputs: Admin_Scrape_Full_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Полный каталог`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_full_catalogue = /** @type {(inputs: Admin_Scrape_Full_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Katalog penuh`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_full_catalogue = /** @type {(inputs: Admin_Scrape_Full_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Catálogo completo`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_full_catalogue = /** @type {(inputs: Admin_Scrape_Full_CatalogueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Catalogue complet`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Full catalogue" |
|
||||
*
|
||||
* @param {Admin_Scrape_Full_CatalogueInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_full_catalogue = /** @type {((inputs?: Admin_Scrape_Full_CatalogueInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Full_CatalogueInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_full_catalogue(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_full_catalogue(inputs)
|
||||
if (locale === "id") return id_admin_scrape_full_catalogue(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_full_catalogue(inputs)
|
||||
return fr_admin_scrape_full_catalogue(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_heading.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_heading.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_HeadingInputs */
|
||||
|
||||
const en_admin_scrape_heading = /** @type {(inputs: Admin_Scrape_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_heading = /** @type {(inputs: Admin_Scrape_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Парсинг`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_heading = /** @type {(inputs: Admin_Scrape_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_heading = /** @type {(inputs: Admin_Scrape_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extração`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_heading = /** @type {(inputs: Admin_Scrape_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extraction`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Scrape" |
|
||||
*
|
||||
* @param {Admin_Scrape_HeadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_heading = /** @type {((inputs?: Admin_Scrape_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_heading(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_heading(inputs)
|
||||
if (locale === "id") return id_admin_scrape_heading(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_heading(inputs)
|
||||
return fr_admin_scrape_heading(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_no_matching.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_no_matching.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_No_MatchingInputs */
|
||||
|
||||
const en_admin_scrape_no_matching = /** @type {(inputs: Admin_Scrape_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No matching tasks.`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_no_matching = /** @type {(inputs: Admin_Scrape_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Задач не найдено.`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_no_matching = /** @type {(inputs: Admin_Scrape_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tidak ada tugas yang cocok.`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_no_matching = /** @type {(inputs: Admin_Scrape_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Nenhuma tarefa correspondente.`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_no_matching = /** @type {(inputs: Admin_Scrape_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Aucune tâche correspondante.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "No matching tasks." |
|
||||
*
|
||||
* @param {Admin_Scrape_No_MatchingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_no_matching = /** @type {((inputs?: Admin_Scrape_No_MatchingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_No_MatchingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_no_matching(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_no_matching(inputs)
|
||||
if (locale === "id") return id_admin_scrape_no_matching(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_no_matching(inputs)
|
||||
return fr_admin_scrape_no_matching(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_page_title.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_page_title.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Page_TitleInputs */
|
||||
|
||||
const en_admin_scrape_page_title = /** @type {(inputs: Admin_Scrape_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape — Admin`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_page_title = /** @type {(inputs: Admin_Scrape_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Парсинг — Админ`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_page_title = /** @type {(inputs: Admin_Scrape_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape — Admin`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_page_title = /** @type {(inputs: Admin_Scrape_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extração — Admin`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_page_title = /** @type {(inputs: Admin_Scrape_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extraction — Admin`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Scrape — Admin" |
|
||||
*
|
||||
* @param {Admin_Scrape_Page_TitleInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_page_title = /** @type {((inputs?: Admin_Scrape_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_page_title(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_page_title(inputs)
|
||||
if (locale === "id") return id_admin_scrape_page_title(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_page_title(inputs)
|
||||
return fr_admin_scrape_page_title(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_queuing.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_queuing.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_QueuingInputs */
|
||||
|
||||
const en_admin_scrape_queuing = /** @type {(inputs: Admin_Scrape_QueuingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Queuing…`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_queuing = /** @type {(inputs: Admin_Scrape_QueuingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`В очереди…`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_queuing = /** @type {(inputs: Admin_Scrape_QueuingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Mengantri…`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_queuing = /** @type {(inputs: Admin_Scrape_QueuingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Na fila…`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_queuing = /** @type {(inputs: Admin_Scrape_QueuingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`En file d'attente…`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Queuing…" |
|
||||
*
|
||||
* @param {Admin_Scrape_QueuingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_queuing = /** @type {((inputs?: Admin_Scrape_QueuingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_QueuingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_queuing(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_queuing(inputs)
|
||||
if (locale === "id") return id_admin_scrape_queuing(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_queuing(inputs)
|
||||
return fr_admin_scrape_queuing(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_quick_genres.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_quick_genres.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Quick_GenresInputs */
|
||||
|
||||
const en_admin_scrape_quick_genres = /** @type {(inputs: Admin_Scrape_Quick_GenresInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Quick genres`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_quick_genres = /** @type {(inputs: Admin_Scrape_Quick_GenresInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Быстрые жанры`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_quick_genres = /** @type {(inputs: Admin_Scrape_Quick_GenresInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Genre cepat`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_quick_genres = /** @type {(inputs: Admin_Scrape_Quick_GenresInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Gêneros rápidos`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_quick_genres = /** @type {(inputs: Admin_Scrape_Quick_GenresInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Genres rapides`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Quick genres" |
|
||||
*
|
||||
* @param {Admin_Scrape_Quick_GenresInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_quick_genres = /** @type {((inputs?: Admin_Scrape_Quick_GenresInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Quick_GenresInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_quick_genres(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_quick_genres(inputs)
|
||||
if (locale === "id") return id_admin_scrape_quick_genres(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_quick_genres(inputs)
|
||||
return fr_admin_scrape_quick_genres(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_range.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_range.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_RangeInputs */
|
||||
|
||||
const en_admin_scrape_range = /** @type {(inputs: Admin_Scrape_RangeInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Chapter range`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_range = /** @type {(inputs: Admin_Scrape_RangeInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Диапазон глав`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_range = /** @type {(inputs: Admin_Scrape_RangeInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Rentang bab`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_range = /** @type {(inputs: Admin_Scrape_RangeInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Intervalo de capítulos`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_range = /** @type {(inputs: Admin_Scrape_RangeInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Plage de chapitres`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Chapter range" |
|
||||
*
|
||||
* @param {Admin_Scrape_RangeInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_range = /** @type {((inputs?: Admin_Scrape_RangeInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_RangeInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_range(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_range(inputs)
|
||||
if (locale === "id") return id_admin_scrape_range(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_range(inputs)
|
||||
return fr_admin_scrape_range(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_running.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_running.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_RunningInputs */
|
||||
|
||||
const en_admin_scrape_running = /** @type {(inputs: Admin_Scrape_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Running…`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_running = /** @type {(inputs: Admin_Scrape_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Выполняется…`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_running = /** @type {(inputs: Admin_Scrape_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Berjalan…`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_running = /** @type {(inputs: Admin_Scrape_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Executando…`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_running = /** @type {(inputs: Admin_Scrape_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`En cours…`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Running…" |
|
||||
*
|
||||
* @param {Admin_Scrape_RunningInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_running = /** @type {((inputs?: Admin_Scrape_RunningInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_RunningInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_running(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_running(inputs)
|
||||
if (locale === "id") return id_admin_scrape_running(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_running(inputs)
|
||||
return fr_admin_scrape_running(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_single_book.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_single_book.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Single_BookInputs */
|
||||
|
||||
const en_admin_scrape_single_book = /** @type {(inputs: Admin_Scrape_Single_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Single book`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_single_book = /** @type {(inputs: Admin_Scrape_Single_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Одна книга`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_single_book = /** @type {(inputs: Admin_Scrape_Single_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Satu buku`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_single_book = /** @type {(inputs: Admin_Scrape_Single_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Livro único`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_single_book = /** @type {(inputs: Admin_Scrape_Single_BookInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Livre unique`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Single book" |
|
||||
*
|
||||
* @param {Admin_Scrape_Single_BookInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_single_book = /** @type {((inputs?: Admin_Scrape_Single_BookInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Single_BookInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_single_book(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_single_book(inputs)
|
||||
if (locale === "id") return id_admin_scrape_single_book(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_single_book(inputs)
|
||||
return fr_admin_scrape_single_book(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_start.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_start.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_StartInputs */
|
||||
|
||||
const en_admin_scrape_start = /** @type {(inputs: Admin_Scrape_StartInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Start scrape`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_start = /** @type {(inputs: Admin_Scrape_StartInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Начать парсинг`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_start = /** @type {(inputs: Admin_Scrape_StartInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Mulai scrape`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_start = /** @type {(inputs: Admin_Scrape_StartInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Iniciar extração`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_start = /** @type {(inputs: Admin_Scrape_StartInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Démarrer l'extraction`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Start scrape" |
|
||||
*
|
||||
* @param {Admin_Scrape_StartInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_start = /** @type {((inputs?: Admin_Scrape_StartInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_StartInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_start(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_start(inputs)
|
||||
if (locale === "id") return id_admin_scrape_start(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_start(inputs)
|
||||
return fr_admin_scrape_start(inputs)
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Status_CancelledInputs */
|
||||
|
||||
const en_admin_scrape_status_cancelled = /** @type {(inputs: Admin_Scrape_Status_CancelledInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Cancelled`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_status_cancelled = /** @type {(inputs: Admin_Scrape_Status_CancelledInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Отменено`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_status_cancelled = /** @type {(inputs: Admin_Scrape_Status_CancelledInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Dibatalkan`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_status_cancelled = /** @type {(inputs: Admin_Scrape_Status_CancelledInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Cancelado`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_status_cancelled = /** @type {(inputs: Admin_Scrape_Status_CancelledInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Annulé`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Cancelled" |
|
||||
*
|
||||
* @param {Admin_Scrape_Status_CancelledInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_status_cancelled = /** @type {((inputs?: Admin_Scrape_Status_CancelledInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Status_CancelledInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_status_cancelled(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_status_cancelled(inputs)
|
||||
if (locale === "id") return id_admin_scrape_status_cancelled(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_status_cancelled(inputs)
|
||||
return fr_admin_scrape_status_cancelled(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_status_done.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_status_done.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Status_DoneInputs */
|
||||
|
||||
const en_admin_scrape_status_done = /** @type {(inputs: Admin_Scrape_Status_DoneInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Done`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_status_done = /** @type {(inputs: Admin_Scrape_Status_DoneInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Готово`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_status_done = /** @type {(inputs: Admin_Scrape_Status_DoneInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Selesai`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_status_done = /** @type {(inputs: Admin_Scrape_Status_DoneInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Concluído`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_status_done = /** @type {(inputs: Admin_Scrape_Status_DoneInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Terminé`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Done" |
|
||||
*
|
||||
* @param {Admin_Scrape_Status_DoneInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_status_done = /** @type {((inputs?: Admin_Scrape_Status_DoneInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Status_DoneInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_status_done(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_status_done(inputs)
|
||||
if (locale === "id") return id_admin_scrape_status_done(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_status_done(inputs)
|
||||
return fr_admin_scrape_status_done(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_status_failed.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_status_failed.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Status_FailedInputs */
|
||||
|
||||
const en_admin_scrape_status_failed = /** @type {(inputs: Admin_Scrape_Status_FailedInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Failed`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_status_failed = /** @type {(inputs: Admin_Scrape_Status_FailedInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Ошибка`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_status_failed = /** @type {(inputs: Admin_Scrape_Status_FailedInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Gagal`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_status_failed = /** @type {(inputs: Admin_Scrape_Status_FailedInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Falhou`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_status_failed = /** @type {(inputs: Admin_Scrape_Status_FailedInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Échoué`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Failed" |
|
||||
*
|
||||
* @param {Admin_Scrape_Status_FailedInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_status_failed = /** @type {((inputs?: Admin_Scrape_Status_FailedInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Status_FailedInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_status_failed(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_status_failed(inputs)
|
||||
if (locale === "id") return id_admin_scrape_status_failed(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_status_failed(inputs)
|
||||
return fr_admin_scrape_status_failed(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_status_idle.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_status_idle.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Status_IdleInputs */
|
||||
|
||||
const en_admin_scrape_status_idle = /** @type {(inputs: Admin_Scrape_Status_IdleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Idle`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_status_idle = /** @type {(inputs: Admin_Scrape_Status_IdleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Ожидание`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_status_idle = /** @type {(inputs: Admin_Scrape_Status_IdleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Menunggu`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_status_idle = /** @type {(inputs: Admin_Scrape_Status_IdleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Ocioso`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_status_idle = /** @type {(inputs: Admin_Scrape_Status_IdleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Inactif`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Idle" |
|
||||
*
|
||||
* @param {Admin_Scrape_Status_IdleInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_status_idle = /** @type {((inputs?: Admin_Scrape_Status_IdleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Status_IdleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_status_idle(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_status_idle(inputs)
|
||||
if (locale === "id") return id_admin_scrape_status_idle(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_status_idle(inputs)
|
||||
return fr_admin_scrape_status_idle(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_status_pending.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_status_pending.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Status_PendingInputs */
|
||||
|
||||
const en_admin_scrape_status_pending = /** @type {(inputs: Admin_Scrape_Status_PendingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Pending`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_status_pending = /** @type {(inputs: Admin_Scrape_Status_PendingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Ожидание`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_status_pending = /** @type {(inputs: Admin_Scrape_Status_PendingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Menunggu`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_status_pending = /** @type {(inputs: Admin_Scrape_Status_PendingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Pendente`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_status_pending = /** @type {(inputs: Admin_Scrape_Status_PendingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`En attente`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Pending" |
|
||||
*
|
||||
* @param {Admin_Scrape_Status_PendingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_status_pending = /** @type {((inputs?: Admin_Scrape_Status_PendingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Status_PendingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_status_pending(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_status_pending(inputs)
|
||||
if (locale === "id") return id_admin_scrape_status_pending(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_status_pending(inputs)
|
||||
return fr_admin_scrape_status_pending(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_status_running.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_status_running.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Status_RunningInputs */
|
||||
|
||||
const en_admin_scrape_status_running = /** @type {(inputs: Admin_Scrape_Status_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Running`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_status_running = /** @type {(inputs: Admin_Scrape_Status_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Выполняется`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_status_running = /** @type {(inputs: Admin_Scrape_Status_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Berjalan`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_status_running = /** @type {(inputs: Admin_Scrape_Status_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Em execução`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_status_running = /** @type {(inputs: Admin_Scrape_Status_RunningInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`En cours`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Running" |
|
||||
*
|
||||
* @param {Admin_Scrape_Status_RunningInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_status_running = /** @type {((inputs?: Admin_Scrape_Status_RunningInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Status_RunningInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_status_running(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_status_running(inputs)
|
||||
if (locale === "id") return id_admin_scrape_status_running(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_status_running(inputs)
|
||||
return fr_admin_scrape_status_running(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_submit.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_submit.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_SubmitInputs */
|
||||
|
||||
const en_admin_scrape_submit = /** @type {(inputs: Admin_Scrape_SubmitInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_submit = /** @type {(inputs: Admin_Scrape_SubmitInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Парсить`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_submit = /** @type {(inputs: Admin_Scrape_SubmitInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Scrape`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_submit = /** @type {(inputs: Admin_Scrape_SubmitInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extrair`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_submit = /** @type {(inputs: Admin_Scrape_SubmitInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Extraire`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Scrape" |
|
||||
*
|
||||
* @param {Admin_Scrape_SubmitInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_submit = /** @type {((inputs?: Admin_Scrape_SubmitInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_SubmitInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_submit(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_submit(inputs)
|
||||
if (locale === "id") return id_admin_scrape_submit(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_submit(inputs)
|
||||
return fr_admin_scrape_submit(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_task_history.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_task_history.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Task_HistoryInputs */
|
||||
|
||||
const en_admin_scrape_task_history = /** @type {(inputs: Admin_Scrape_Task_HistoryInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Task history`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_task_history = /** @type {(inputs: Admin_Scrape_Task_HistoryInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`История задач`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_task_history = /** @type {(inputs: Admin_Scrape_Task_HistoryInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Riwayat tugas`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_task_history = /** @type {(inputs: Admin_Scrape_Task_HistoryInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Histórico de tarefas`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_task_history = /** @type {(inputs: Admin_Scrape_Task_HistoryInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Historique des tâches`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Task history" |
|
||||
*
|
||||
* @param {Admin_Scrape_Task_HistoryInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_task_history = /** @type {((inputs?: Admin_Scrape_Task_HistoryInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Task_HistoryInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_task_history(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_task_history(inputs)
|
||||
if (locale === "id") return id_admin_scrape_task_history(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_task_history(inputs)
|
||||
return fr_admin_scrape_task_history(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_scrape_to.js
Normal file
44
ui/src/lib/paraglide/messages/admin_scrape_to.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_ToInputs */
|
||||
|
||||
const en_admin_scrape_to = /** @type {(inputs: Admin_Scrape_ToInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`To`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_to = /** @type {(inputs: Admin_Scrape_ToInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`До`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_to = /** @type {(inputs: Admin_Scrape_ToInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Sampai`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_to = /** @type {(inputs: Admin_Scrape_ToInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Até`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_to = /** @type {(inputs: Admin_Scrape_ToInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`À`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "To" |
|
||||
*
|
||||
* @param {Admin_Scrape_ToInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_to = /** @type {((inputs?: Admin_Scrape_ToInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_ToInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_to(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_to(inputs)
|
||||
if (locale === "id") return id_admin_scrape_to(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_to(inputs)
|
||||
return fr_admin_scrape_to(inputs)
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Scrape_Url_PlaceholderInputs */
|
||||
|
||||
const en_admin_scrape_url_placeholder = /** @type {(inputs: Admin_Scrape_Url_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`novelfire.net book URL`)
|
||||
};
|
||||
|
||||
const ru_admin_scrape_url_placeholder = /** @type {(inputs: Admin_Scrape_Url_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`URL книги на novelfire.net`)
|
||||
};
|
||||
|
||||
const id_admin_scrape_url_placeholder = /** @type {(inputs: Admin_Scrape_Url_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`URL buku di novelfire.net`)
|
||||
};
|
||||
|
||||
const pt_admin_scrape_url_placeholder = /** @type {(inputs: Admin_Scrape_Url_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`URL do livro em novelfire.net`)
|
||||
};
|
||||
|
||||
const fr_admin_scrape_url_placeholder = /** @type {(inputs: Admin_Scrape_Url_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`URL du livre sur novelfire.net`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "novelfire.net book URL" |
|
||||
*
|
||||
* @param {Admin_Scrape_Url_PlaceholderInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_scrape_url_placeholder = /** @type {((inputs?: Admin_Scrape_Url_PlaceholderInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Scrape_Url_PlaceholderInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_scrape_url_placeholder(inputs)
|
||||
if (locale === "ru") return ru_admin_scrape_url_placeholder(inputs)
|
||||
if (locale === "id") return id_admin_scrape_url_placeholder(inputs)
|
||||
if (locale === "pt") return pt_admin_scrape_url_placeholder(inputs)
|
||||
return fr_admin_scrape_url_placeholder(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_tasks_empty.js
Normal file
44
ui/src/lib/paraglide/messages/admin_tasks_empty.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Tasks_EmptyInputs */
|
||||
|
||||
const en_admin_tasks_empty = /** @type {(inputs: Admin_Tasks_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No tasks yet.`)
|
||||
};
|
||||
|
||||
const ru_admin_tasks_empty = /** @type {(inputs: Admin_Tasks_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Задач пока нет.`)
|
||||
};
|
||||
|
||||
const id_admin_tasks_empty = /** @type {(inputs: Admin_Tasks_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Belum ada tugas.`)
|
||||
};
|
||||
|
||||
const pt_admin_tasks_empty = /** @type {(inputs: Admin_Tasks_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Nenhuma tarefa ainda.`)
|
||||
};
|
||||
|
||||
const fr_admin_tasks_empty = /** @type {(inputs: Admin_Tasks_EmptyInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Aucune tâche pour l'instant.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "No tasks yet." |
|
||||
*
|
||||
* @param {Admin_Tasks_EmptyInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_tasks_empty = /** @type {((inputs?: Admin_Tasks_EmptyInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Tasks_EmptyInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_tasks_empty(inputs)
|
||||
if (locale === "ru") return ru_admin_tasks_empty(inputs)
|
||||
if (locale === "id") return id_admin_tasks_empty(inputs)
|
||||
if (locale === "pt") return pt_admin_tasks_empty(inputs)
|
||||
return fr_admin_tasks_empty(inputs)
|
||||
});
|
||||
44
ui/src/lib/paraglide/messages/admin_tasks_heading.js
Normal file
44
ui/src/lib/paraglide/messages/admin_tasks_heading.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Tasks_HeadingInputs */
|
||||
|
||||
const en_admin_tasks_heading = /** @type {(inputs: Admin_Tasks_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Recent tasks`)
|
||||
};
|
||||
|
||||
const ru_admin_tasks_heading = /** @type {(inputs: Admin_Tasks_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Последние задачи`)
|
||||
};
|
||||
|
||||
const id_admin_tasks_heading = /** @type {(inputs: Admin_Tasks_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tugas terbaru`)
|
||||
};
|
||||
|
||||
const pt_admin_tasks_heading = /** @type {(inputs: Admin_Tasks_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tarefas recentes`)
|
||||
};
|
||||
|
||||
const fr_admin_tasks_heading = /** @type {(inputs: Admin_Tasks_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Tâches récentes`)
|
||||
};
|
||||
|
||||
/**
|
||||
* | output |
|
||||
* | --- |
|
||||
* | "Recent tasks" |
|
||||
*
|
||||
* @param {Admin_Tasks_HeadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_tasks_heading = /** @type {((inputs?: Admin_Tasks_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Tasks_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_tasks_heading(inputs)
|
||||
if (locale === "ru") return ru_admin_tasks_heading(inputs)
|
||||
if (locale === "id") return id_admin_tasks_heading(inputs)
|
||||
if (locale === "pt") return pt_admin_tasks_heading(inputs)
|
||||
return fr_admin_tasks_heading(inputs)
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user