Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7306877f1 | ||
|
|
0723049e0c | ||
|
|
b206994459 | ||
|
|
956594ae7b |
@@ -63,6 +63,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check Paraglide codegen is up to date
|
||||
run: npm run paraglide && git diff --exit-code src/lib/paraglide/
|
||||
|
||||
- name: Type check
|
||||
run: npm run check
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@
|
||||
voices?: Voice[];
|
||||
/** Called when the server returns 402 (free daily limit reached). */
|
||||
onProRequired?: () => void;
|
||||
/** Visual style of the player card. 'standard' = full controls; 'compact' = slim seekable player. */
|
||||
playerStyle?: 'standard' | 'compact';
|
||||
/** Visual style of the player card. 'standard' = inline card; 'float' = draggable overlay. */
|
||||
playerStyle?: 'standard' | 'float';
|
||||
/** Approximate word count for the chapter, used to show estimated listen time in the idle state. */
|
||||
wordCount?: number;
|
||||
}
|
||||
@@ -891,18 +891,32 @@
|
||||
audioStore.duration > 0 ? (audioStore.currentTime / audioStore.duration) * 100 : 0
|
||||
);
|
||||
|
||||
const SPEED_OPTIONS = [0.75, 1, 1.25, 1.5, 2] as const;
|
||||
function cycleSpeed() {
|
||||
const idx = SPEED_OPTIONS.indexOf(audioStore.speed as (typeof SPEED_OPTIONS)[number]);
|
||||
audioStore.speed = SPEED_OPTIONS[(idx + 1) % SPEED_OPTIONS.length];
|
||||
}
|
||||
|
||||
function seekFromCompactBar(e: MouseEvent) {
|
||||
function seekFromBar(e: MouseEvent) {
|
||||
if (audioStore.duration <= 0) return;
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
||||
audioStore.seekRequest = pct * audioStore.duration;
|
||||
}
|
||||
|
||||
// ── Float player drag state ──────────────────────────────────────────────
|
||||
/** Position of the floating overlay (bottom-right anchor by default). */
|
||||
let floatPos = $state({ x: 0, y: 0 });
|
||||
let floatDragging = $state(false);
|
||||
let floatDragStart = $state({ mx: 0, my: 0, ox: 0, oy: 0 });
|
||||
|
||||
function onFloatPointerDown(e: PointerEvent) {
|
||||
floatDragging = true;
|
||||
floatDragStart = { mx: e.clientX, my: e.clientY, ox: floatPos.x, oy: floatPos.y };
|
||||
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
||||
}
|
||||
function onFloatPointerMove(e: PointerEvent) {
|
||||
if (!floatDragging) return;
|
||||
floatPos = {
|
||||
x: floatDragStart.ox + (e.clientX - floatDragStart.mx),
|
||||
y: floatDragStart.oy + (e.clientY - floatDragStart.my)
|
||||
};
|
||||
}
|
||||
function onFloatPointerUp() { floatDragging = false; }
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeyDown} />
|
||||
@@ -953,121 +967,6 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if playerStyle === 'compact'}
|
||||
<!-- ── Compact player ──────────────────────────────────────────────────────── -->
|
||||
<div class="mt-4 p-3 rounded-lg bg-(--color-surface-2) border border-(--color-border)">
|
||||
{#if audioStore.isCurrentChapter(slug, chapter)}
|
||||
{#if audioStore.status === 'idle' || audioStore.status === 'error'}
|
||||
{#if audioStore.status === 'error'}
|
||||
<p class="text-(--color-danger) text-xs mb-2">{audioStore.errorMsg || 'Failed to load audio.'}</p>
|
||||
{/if}
|
||||
<Button variant="default" size="sm" onclick={handlePlay}>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
{m.reader_play_narration()}
|
||||
</Button>
|
||||
|
||||
{:else if audioStore.status === 'loading'}
|
||||
<div class="flex items-center gap-2 text-xs text-(--color-muted)">
|
||||
<svg class="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||
</svg>
|
||||
{m.player_loading()}
|
||||
</div>
|
||||
|
||||
{:else if audioStore.status === 'generating'}
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs text-(--color-muted)">
|
||||
<span>{m.reader_generating_narration()}</span>
|
||||
<span class="tabular-nums">{Math.round(audioStore.progress)}%</span>
|
||||
</div>
|
||||
<div class="w-full h-1 bg-(--color-surface-3) rounded-full overflow-hidden">
|
||||
<div class="h-full bg-(--color-brand) rounded-full transition-none" style="width: {audioStore.progress}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if audioStore.status === 'ready'}
|
||||
<div class="space-y-2">
|
||||
<!-- Seekable progress bar -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
role="none"
|
||||
class="w-full h-1.5 bg-(--color-surface-3) rounded-full overflow-hidden cursor-pointer group"
|
||||
onclick={seekFromCompactBar}
|
||||
>
|
||||
<div class="h-full bg-(--color-brand) rounded-full transition-none" style="width: {playPct}%"></div>
|
||||
</div>
|
||||
<!-- Controls row -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Skip back 15s -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { audioStore.seekRequest = Math.max(0, audioStore.currentTime - 15); }}
|
||||
class="text-(--color-muted) hover:text-(--color-text) transition-colors flex-shrink-0"
|
||||
title="-15s"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Play/pause -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { audioStore.toggleRequest++; }}
|
||||
class="w-8 h-8 rounded-full bg-(--color-brand) text-(--color-surface) flex items-center justify-center hover:bg-(--color-brand-dim) transition-colors flex-shrink-0"
|
||||
>
|
||||
{#if audioStore.isPlaying}
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>
|
||||
{:else}
|
||||
<svg class="w-3.5 h-3.5 ml-0.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
{/if}
|
||||
</button>
|
||||
<!-- Skip forward 30s -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { audioStore.seekRequest = Math.min(audioStore.duration || 0, audioStore.currentTime + 30); }}
|
||||
class="text-(--color-muted) hover:text-(--color-text) transition-colors flex-shrink-0"
|
||||
title="+30s"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M18 13c0 3.31-2.69 6-6 6s-6-2.69-6-6 2.69-6 6-6v4l5-5-5-5v4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8h-2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Time display -->
|
||||
<span class="flex-1 text-xs text-center tabular-nums text-(--color-muted)">
|
||||
{formatTime(audioStore.currentTime)} / {formatDuration(audioStore.duration)}
|
||||
</span>
|
||||
<!-- Speed cycle -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={cycleSpeed}
|
||||
class="text-xs font-medium text-(--color-muted) hover:text-(--color-text) flex-shrink-0 tabular-nums transition-colors"
|
||||
title="Playback speed"
|
||||
>
|
||||
{audioStore.speed}×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{:else if audioStore.active}
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<p class="text-xs text-(--color-muted)">
|
||||
{m.reader_now_playing({ title: audioStore.chapterTitle || `Ch.${audioStore.chapter}` })}
|
||||
</p>
|
||||
<Button variant="secondary" size="sm" class="flex-shrink-0" onclick={startPlayback}>
|
||||
{m.reader_load_this_chapter()}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<Button variant="default" size="sm" onclick={handlePlay}>
|
||||
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
{m.reader_play_narration()}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<!-- ── Standard player ─────────────────────────────────────────────────────── -->
|
||||
|
||||
{#if
|
||||
@@ -1197,6 +1096,7 @@
|
||||
|
||||
{:else}
|
||||
<!-- ── Non-idle states (loading / generating / ready / other-chapter-playing) ── -->
|
||||
{#if !(playerStyle === 'float' && audioStore.isCurrentChapter(slug, chapter) && audioStore.active)}
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-end gap-2 mb-3">
|
||||
<!-- Chapter picker button -->
|
||||
@@ -1402,7 +1302,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- /playerStyle -->
|
||||
|
||||
<!-- ── Chapter picker overlay ─────────────────────────────────────────────────
|
||||
Rendered as a top-level sibling (outside all player containers) so that
|
||||
@@ -1479,3 +1378,106 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- ── Float player overlay ──────────────────────────────────────────────────
|
||||
Rendered outside all containers so fixed positioning is never clipped.
|
||||
Visible when playerStyle='float' and audio is active for this chapter. -->
|
||||
{#if playerStyle === 'float' && audioStore.isCurrentChapter(slug, chapter) && audioStore.active}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed z-[55] select-none"
|
||||
style="
|
||||
bottom: calc(4.5rem + {-floatPos.y}px);
|
||||
right: calc(1rem + {-floatPos.x}px);
|
||||
touch-action: none;
|
||||
"
|
||||
>
|
||||
<div class="w-64 rounded-2xl bg-(--color-surface) border border-(--color-border) shadow-2xl overflow-hidden">
|
||||
<!-- Drag handle + title row -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="flex items-center gap-2 px-3 pt-2.5 pb-1 cursor-grab active:cursor-grabbing"
|
||||
onpointerdown={onFloatPointerDown}
|
||||
onpointermove={onFloatPointerMove}
|
||||
onpointerup={onFloatPointerUp}
|
||||
onpointercancel={onFloatPointerUp}
|
||||
>
|
||||
<!-- Drag grip dots -->
|
||||
<svg class="w-3.5 h-3.5 text-(--color-muted)/50 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
|
||||
<circle cx="9" cy="6" r="1.5"/><circle cx="15" cy="6" r="1.5"/>
|
||||
<circle cx="9" cy="12" r="1.5"/><circle cx="15" cy="12" r="1.5"/>
|
||||
<circle cx="9" cy="18" r="1.5"/><circle cx="15" cy="18" r="1.5"/>
|
||||
</svg>
|
||||
<span class="flex-1 text-xs font-medium text-(--color-muted) truncate">
|
||||
{audioStore.chapterTitle || `Chapter ${audioStore.chapter}`}
|
||||
</span>
|
||||
<!-- Status dot -->
|
||||
{#if audioStore.isPlaying}
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-(--color-brand) flex-shrink-0 animate-pulse"></span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Seek bar -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
role="none"
|
||||
class="mx-3 mb-2 h-1 bg-(--color-surface-3) rounded-full overflow-hidden cursor-pointer"
|
||||
onclick={seekFromBar}
|
||||
>
|
||||
<div class="h-full bg-(--color-brand) rounded-full transition-none" style="width: {playPct}%"></div>
|
||||
</div>
|
||||
|
||||
<!-- Controls row -->
|
||||
<div class="flex items-center gap-1 px-3 pb-2.5">
|
||||
<!-- Skip back 15s -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { audioStore.seekRequest = Math.max(0, audioStore.currentTime - 15); }}
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full text-(--color-muted) hover:text-(--color-text) hover:bg-(--color-surface-2) transition-colors flex-shrink-0"
|
||||
title="-15s"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M11.99 5V1l-5 5 5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6h-2c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Play/pause -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { audioStore.toggleRequest++; }}
|
||||
class="w-9 h-9 rounded-full bg-(--color-brand) text-(--color-surface) flex items-center justify-center hover:bg-(--color-brand-dim) active:scale-95 transition-all flex-shrink-0"
|
||||
>
|
||||
{#if audioStore.isPlaying}
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>
|
||||
{:else}
|
||||
<svg class="w-4 h-4 ml-0.5" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Skip forward 30s -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { audioStore.seekRequest = Math.min(audioStore.duration || 0, audioStore.currentTime + 30); }}
|
||||
class="w-8 h-8 flex items-center justify-center rounded-full text-(--color-muted) hover:text-(--color-text) hover:bg-(--color-surface-2) transition-colors flex-shrink-0"
|
||||
title="+30s"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M18 13c0 3.31-2.69 6-6 6s-6-2.69-6-6 2.69-6 6-6v4l5-5-5-5v4c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8h-2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Time -->
|
||||
<span class="flex-1 text-[11px] text-center tabular-nums text-(--color-muted)">
|
||||
{formatTime(audioStore.currentTime)}
|
||||
<span class="opacity-50">/</span>
|
||||
{formatDuration(audioStore.duration)}
|
||||
</span>
|
||||
|
||||
<!-- Speed -->
|
||||
<span class="text-[11px] font-medium tabular-nums text-(--color-muted) flex-shrink-0">
|
||||
{audioStore.speed}×
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -405,6 +405,18 @@ 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 './admin_translation_page_title.js'
|
||||
export * from './admin_translation_heading.js'
|
||||
export * from './admin_translation_tab_enqueue.js'
|
||||
export * from './admin_translation_tab_jobs.js'
|
||||
export * from './admin_translation_filter_placeholder.js'
|
||||
export * from './admin_translation_no_matching.js'
|
||||
export * from './admin_translation_no_jobs.js'
|
||||
export * from './admin_ai_jobs_page_title.js'
|
||||
export * from './admin_ai_jobs_heading.js'
|
||||
export * from './admin_ai_jobs_subheading.js'
|
||||
export * from './admin_text_gen_page_title.js'
|
||||
export * from './admin_text_gen_heading.js'
|
||||
export * from './comments_top.js'
|
||||
export * from './comments_new.js'
|
||||
export * from './comments_posting.js'
|
||||
|
||||
40
ui/src/lib/paraglide/messages/admin_ai_jobs_heading.js
Normal file
40
ui/src/lib/paraglide/messages/admin_ai_jobs_heading.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Ai_Jobs_HeadingInputs */
|
||||
|
||||
const en_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs`)
|
||||
};
|
||||
|
||||
const ru_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs`)
|
||||
};
|
||||
|
||||
const id_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs`)
|
||||
};
|
||||
|
||||
const pt_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs`)
|
||||
};
|
||||
|
||||
const fr_admin_ai_jobs_heading = /** @type {(inputs: Admin_Ai_Jobs_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Ai_Jobs_HeadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_ai_jobs_heading = /** @type {((inputs?: Admin_Ai_Jobs_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Ai_Jobs_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_ai_jobs_heading(inputs)
|
||||
if (locale === "ru") return ru_admin_ai_jobs_heading(inputs)
|
||||
if (locale === "id") return id_admin_ai_jobs_heading(inputs)
|
||||
if (locale === "pt") return pt_admin_ai_jobs_heading(inputs)
|
||||
return fr_admin_ai_jobs_heading(inputs)
|
||||
});
|
||||
40
ui/src/lib/paraglide/messages/admin_ai_jobs_page_title.js
Normal file
40
ui/src/lib/paraglide/messages/admin_ai_jobs_page_title.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Ai_Jobs_Page_TitleInputs */
|
||||
|
||||
const en_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
|
||||
};
|
||||
|
||||
const ru_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
|
||||
};
|
||||
|
||||
const id_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
|
||||
};
|
||||
|
||||
const pt_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
|
||||
};
|
||||
|
||||
const fr_admin_ai_jobs_page_title = /** @type {(inputs: Admin_Ai_Jobs_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`AI Jobs — Admin`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Ai_Jobs_Page_TitleInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_ai_jobs_page_title = /** @type {((inputs?: Admin_Ai_Jobs_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Ai_Jobs_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_ai_jobs_page_title(inputs)
|
||||
if (locale === "ru") return ru_admin_ai_jobs_page_title(inputs)
|
||||
if (locale === "id") return id_admin_ai_jobs_page_title(inputs)
|
||||
if (locale === "pt") return pt_admin_ai_jobs_page_title(inputs)
|
||||
return fr_admin_ai_jobs_page_title(inputs)
|
||||
});
|
||||
40
ui/src/lib/paraglide/messages/admin_ai_jobs_subheading.js
Normal file
40
ui/src/lib/paraglide/messages/admin_ai_jobs_subheading.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Ai_Jobs_SubheadingInputs */
|
||||
|
||||
const en_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
|
||||
};
|
||||
|
||||
const ru_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
|
||||
};
|
||||
|
||||
const id_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
|
||||
};
|
||||
|
||||
const pt_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
|
||||
};
|
||||
|
||||
const fr_admin_ai_jobs_subheading = /** @type {(inputs: Admin_Ai_Jobs_SubheadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Background AI generation tasks`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Ai_Jobs_SubheadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_ai_jobs_subheading = /** @type {((inputs?: Admin_Ai_Jobs_SubheadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Ai_Jobs_SubheadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_ai_jobs_subheading(inputs)
|
||||
if (locale === "ru") return ru_admin_ai_jobs_subheading(inputs)
|
||||
if (locale === "id") return id_admin_ai_jobs_subheading(inputs)
|
||||
if (locale === "pt") return pt_admin_ai_jobs_subheading(inputs)
|
||||
return fr_admin_ai_jobs_subheading(inputs)
|
||||
});
|
||||
40
ui/src/lib/paraglide/messages/admin_text_gen_heading.js
Normal file
40
ui/src/lib/paraglide/messages/admin_text_gen_heading.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Text_Gen_HeadingInputs */
|
||||
|
||||
const en_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Generation`)
|
||||
};
|
||||
|
||||
const ru_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Generation`)
|
||||
};
|
||||
|
||||
const id_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Generation`)
|
||||
};
|
||||
|
||||
const pt_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Generation`)
|
||||
};
|
||||
|
||||
const fr_admin_text_gen_heading = /** @type {(inputs: Admin_Text_Gen_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Generation`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Text_Gen_HeadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_text_gen_heading = /** @type {((inputs?: Admin_Text_Gen_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Text_Gen_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_text_gen_heading(inputs)
|
||||
if (locale === "ru") return ru_admin_text_gen_heading(inputs)
|
||||
if (locale === "id") return id_admin_text_gen_heading(inputs)
|
||||
if (locale === "pt") return pt_admin_text_gen_heading(inputs)
|
||||
return fr_admin_text_gen_heading(inputs)
|
||||
});
|
||||
40
ui/src/lib/paraglide/messages/admin_text_gen_page_title.js
Normal file
40
ui/src/lib/paraglide/messages/admin_text_gen_page_title.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Text_Gen_Page_TitleInputs */
|
||||
|
||||
const en_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
|
||||
};
|
||||
|
||||
const ru_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
|
||||
};
|
||||
|
||||
const id_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
|
||||
};
|
||||
|
||||
const pt_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
|
||||
};
|
||||
|
||||
const fr_admin_text_gen_page_title = /** @type {(inputs: Admin_Text_Gen_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Text Gen — Admin`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Text_Gen_Page_TitleInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_text_gen_page_title = /** @type {((inputs?: Admin_Text_Gen_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Text_Gen_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_text_gen_page_title(inputs)
|
||||
if (locale === "ru") return ru_admin_text_gen_page_title(inputs)
|
||||
if (locale === "id") return id_admin_text_gen_page_title(inputs)
|
||||
if (locale === "pt") return pt_admin_text_gen_page_title(inputs)
|
||||
return fr_admin_text_gen_page_title(inputs)
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Translation_Filter_PlaceholderInputs */
|
||||
|
||||
const en_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
|
||||
};
|
||||
|
||||
const ru_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
|
||||
};
|
||||
|
||||
const id_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
|
||||
};
|
||||
|
||||
const pt_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
|
||||
};
|
||||
|
||||
const fr_admin_translation_filter_placeholder = /** @type {(inputs: Admin_Translation_Filter_PlaceholderInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Filter by slug, lang, or status…`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Translation_Filter_PlaceholderInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_translation_filter_placeholder = /** @type {((inputs?: Admin_Translation_Filter_PlaceholderInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Filter_PlaceholderInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_translation_filter_placeholder(inputs)
|
||||
if (locale === "ru") return ru_admin_translation_filter_placeholder(inputs)
|
||||
if (locale === "id") return id_admin_translation_filter_placeholder(inputs)
|
||||
if (locale === "pt") return pt_admin_translation_filter_placeholder(inputs)
|
||||
return fr_admin_translation_filter_placeholder(inputs)
|
||||
});
|
||||
40
ui/src/lib/paraglide/messages/admin_translation_heading.js
Normal file
40
ui/src/lib/paraglide/messages/admin_translation_heading.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Translation_HeadingInputs */
|
||||
|
||||
const en_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Machine Translation`)
|
||||
};
|
||||
|
||||
const ru_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Machine Translation`)
|
||||
};
|
||||
|
||||
const id_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Machine Translation`)
|
||||
};
|
||||
|
||||
const pt_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Machine Translation`)
|
||||
};
|
||||
|
||||
const fr_admin_translation_heading = /** @type {(inputs: Admin_Translation_HeadingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Machine Translation`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Translation_HeadingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_translation_heading = /** @type {((inputs?: Admin_Translation_HeadingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_HeadingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_translation_heading(inputs)
|
||||
if (locale === "ru") return ru_admin_translation_heading(inputs)
|
||||
if (locale === "id") return id_admin_translation_heading(inputs)
|
||||
if (locale === "pt") return pt_admin_translation_heading(inputs)
|
||||
return fr_admin_translation_heading(inputs)
|
||||
});
|
||||
40
ui/src/lib/paraglide/messages/admin_translation_no_jobs.js
Normal file
40
ui/src/lib/paraglide/messages/admin_translation_no_jobs.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Translation_No_JobsInputs */
|
||||
|
||||
const en_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
|
||||
};
|
||||
|
||||
const ru_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
|
||||
};
|
||||
|
||||
const id_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
|
||||
};
|
||||
|
||||
const pt_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
|
||||
};
|
||||
|
||||
const fr_admin_translation_no_jobs = /** @type {(inputs: Admin_Translation_No_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No translation jobs yet.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Translation_No_JobsInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_translation_no_jobs = /** @type {((inputs?: Admin_Translation_No_JobsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_No_JobsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_translation_no_jobs(inputs)
|
||||
if (locale === "ru") return ru_admin_translation_no_jobs(inputs)
|
||||
if (locale === "id") return id_admin_translation_no_jobs(inputs)
|
||||
if (locale === "pt") return pt_admin_translation_no_jobs(inputs)
|
||||
return fr_admin_translation_no_jobs(inputs)
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Translation_No_MatchingInputs */
|
||||
|
||||
const en_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No matching jobs.`)
|
||||
};
|
||||
|
||||
const ru_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No matching jobs.`)
|
||||
};
|
||||
|
||||
const id_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No matching jobs.`)
|
||||
};
|
||||
|
||||
const pt_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No matching jobs.`)
|
||||
};
|
||||
|
||||
const fr_admin_translation_no_matching = /** @type {(inputs: Admin_Translation_No_MatchingInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`No matching jobs.`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Translation_No_MatchingInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_translation_no_matching = /** @type {((inputs?: Admin_Translation_No_MatchingInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_No_MatchingInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_translation_no_matching(inputs)
|
||||
if (locale === "ru") return ru_admin_translation_no_matching(inputs)
|
||||
if (locale === "id") return id_admin_translation_no_matching(inputs)
|
||||
if (locale === "pt") return pt_admin_translation_no_matching(inputs)
|
||||
return fr_admin_translation_no_matching(inputs)
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Translation_Page_TitleInputs */
|
||||
|
||||
const en_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Translation — Admin`)
|
||||
};
|
||||
|
||||
const ru_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Translation — Admin`)
|
||||
};
|
||||
|
||||
const id_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Translation — Admin`)
|
||||
};
|
||||
|
||||
const pt_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Translation — Admin`)
|
||||
};
|
||||
|
||||
const fr_admin_translation_page_title = /** @type {(inputs: Admin_Translation_Page_TitleInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Translation — Admin`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Translation_Page_TitleInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_translation_page_title = /** @type {((inputs?: Admin_Translation_Page_TitleInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Page_TitleInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_translation_page_title(inputs)
|
||||
if (locale === "ru") return ru_admin_translation_page_title(inputs)
|
||||
if (locale === "id") return id_admin_translation_page_title(inputs)
|
||||
if (locale === "pt") return pt_admin_translation_page_title(inputs)
|
||||
return fr_admin_translation_page_title(inputs)
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Translation_Tab_EnqueueInputs */
|
||||
|
||||
const en_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Enqueue`)
|
||||
};
|
||||
|
||||
const ru_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Enqueue`)
|
||||
};
|
||||
|
||||
const id_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Enqueue`)
|
||||
};
|
||||
|
||||
const pt_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Enqueue`)
|
||||
};
|
||||
|
||||
const fr_admin_translation_tab_enqueue = /** @type {(inputs: Admin_Translation_Tab_EnqueueInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Enqueue`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Translation_Tab_EnqueueInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_translation_tab_enqueue = /** @type {((inputs?: Admin_Translation_Tab_EnqueueInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Tab_EnqueueInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_translation_tab_enqueue(inputs)
|
||||
if (locale === "ru") return ru_admin_translation_tab_enqueue(inputs)
|
||||
if (locale === "id") return id_admin_translation_tab_enqueue(inputs)
|
||||
if (locale === "pt") return pt_admin_translation_tab_enqueue(inputs)
|
||||
return fr_admin_translation_tab_enqueue(inputs)
|
||||
});
|
||||
40
ui/src/lib/paraglide/messages/admin_translation_tab_jobs.js
Normal file
40
ui/src/lib/paraglide/messages/admin_translation_tab_jobs.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
||||
|
||||
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
||||
|
||||
/** @typedef {{}} Admin_Translation_Tab_JobsInputs */
|
||||
|
||||
const en_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Jobs`)
|
||||
};
|
||||
|
||||
const ru_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Jobs`)
|
||||
};
|
||||
|
||||
const id_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Jobs`)
|
||||
};
|
||||
|
||||
const pt_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Jobs`)
|
||||
};
|
||||
|
||||
const fr_admin_translation_tab_jobs = /** @type {(inputs: Admin_Translation_Tab_JobsInputs) => LocalizedString} */ () => {
|
||||
return /** @type {LocalizedString} */ (`Jobs`)
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Admin_Translation_Tab_JobsInputs} inputs
|
||||
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
|
||||
* @returns {LocalizedString}
|
||||
*/
|
||||
export const admin_translation_tab_jobs = /** @type {((inputs?: Admin_Translation_Tab_JobsInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Translation_Tab_JobsInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
|
||||
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
||||
if (locale === "en") return en_admin_translation_tab_jobs(inputs)
|
||||
if (locale === "ru") return ru_admin_translation_tab_jobs(inputs)
|
||||
if (locale === "id") return id_admin_translation_tab_jobs(inputs)
|
||||
if (locale === "pt") return pt_admin_translation_tab_jobs(inputs)
|
||||
return fr_admin_translation_tab_jobs(inputs)
|
||||
});
|
||||
@@ -90,17 +90,13 @@
|
||||
// Auto-advance carousel every CAROUSEL_INTERVAL ms when there are multiple books.
|
||||
// autoAdvanceSeed is bumped on manual swipe/dot to restart the interval.
|
||||
let autoAdvanceSeed = $state(0);
|
||||
// progressStart tracks when the current interval began (for the progress bar).
|
||||
let progressStart = $state(browser ? performance.now() : 0);
|
||||
|
||||
$effect(() => {
|
||||
if (heroBooks.length <= 1) return;
|
||||
const len = heroBooks.length;
|
||||
void autoAdvanceSeed; // restart when seed changes
|
||||
progressStart = browser ? performance.now() : 0;
|
||||
const id = setInterval(() => {
|
||||
heroIndex = (heroIndex + 1) % len;
|
||||
progressStart = browser ? performance.now() : 0;
|
||||
}, CAROUSEL_INTERVAL);
|
||||
return () => clearInterval(id);
|
||||
});
|
||||
@@ -109,8 +105,7 @@
|
||||
autoAdvanceSeed++;
|
||||
}
|
||||
|
||||
// ── Swipe handling ───────────────────────────────────────────────────────
|
||||
let swipeStartX = 0;
|
||||
// ── Swipe handling ─────────────────────────────────────────────────────── let swipeStartX = 0;
|
||||
function onSwipeStart(e: TouchEvent) {
|
||||
swipeStartX = e.touches[0].clientX;
|
||||
}
|
||||
@@ -127,22 +122,6 @@
|
||||
resetAutoAdvance();
|
||||
}
|
||||
|
||||
// ── Progress bar animation ───────────────────────────────────────────────
|
||||
// rAF loop drives a 0→1 progress value that resets on each advance.
|
||||
let rafProgress = $state(0);
|
||||
$effect(() => {
|
||||
if (!browser || heroBooks.length <= 1) return;
|
||||
void autoAdvanceSeed; // re-subscribe so effect re-runs on manual nav
|
||||
void heroIndex;
|
||||
let raf: number;
|
||||
function tick() {
|
||||
rafProgress = Math.min((performance.now() - progressStart) / CAROUSEL_INTERVAL, 1);
|
||||
raf = requestAnimationFrame(tick);
|
||||
}
|
||||
raf = requestAnimationFrame(tick);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
});
|
||||
|
||||
function playChapter(slug: string, chapter: number) {
|
||||
audioStore.autoStartChapter = chapter;
|
||||
goto(`/books/${slug}/chapters/${chapter}`);
|
||||
@@ -210,10 +189,6 @@
|
||||
</svg>
|
||||
Listen
|
||||
</button>
|
||||
{#if heroBook.book.total_chapters > 0 && heroBook.chapter < heroBook.book.total_chapters}
|
||||
{@const ahead = heroBook.book.total_chapters - heroBook.chapter}
|
||||
<span class="text-xs text-(--color-muted) hidden sm:inline">{ahead} chapters ahead</span>
|
||||
{/if}
|
||||
{#each parseGenres(heroBook.book.genres).slice(0, 2) as genre}
|
||||
<span class="text-xs px-2 py-1 rounded-full bg-(--color-surface-3) text-(--color-muted)">{genre}</span>
|
||||
{/each}
|
||||
@@ -221,7 +196,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dot indicators with animated progress line under active dot -->
|
||||
<!-- Dot indicators -->
|
||||
{#if heroBooks.length > 1}
|
||||
<div class="flex items-center justify-center gap-2 mt-2.5">
|
||||
{#each heroBooks as _, i}
|
||||
@@ -229,21 +204,10 @@
|
||||
type="button"
|
||||
onclick={() => heroDot(i)}
|
||||
aria-label="Go to book {i + 1}"
|
||||
class="relative flex flex-col items-center gap-0.5 group/dot"
|
||||
>
|
||||
<!-- dot -->
|
||||
<span class="block rounded-full transition-all duration-300 {i === heroIndex
|
||||
? 'w-4 h-1.5 bg-(--color-brand)'
|
||||
: 'w-1.5 h-1.5 bg-(--color-border) group-hover/dot:bg-(--color-muted)'}"></span>
|
||||
<!-- progress line — only visible under the active dot -->
|
||||
{#if i === heroIndex}
|
||||
<span class="absolute -bottom-1.5 left-0 h-0.5 w-full bg-(--color-border) rounded-full overflow-hidden">
|
||||
<span
|
||||
class="block h-full bg-(--color-brand) rounded-full"
|
||||
style="width: {rafProgress * 100}%"
|
||||
></span>
|
||||
</span>
|
||||
{/if}
|
||||
: 'w-1.5 h-1.5 bg-(--color-border) hover:bg-(--color-muted)'}"></span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -256,9 +220,6 @@
|
||||
{#if streak > 0}
|
||||
<div class="mb-6 flex items-center gap-3 flex-wrap text-sm">
|
||||
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-(--color-surface-2) border border-(--color-border)">
|
||||
<svg class="w-4 h-4 text-orange-400" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M13.5 0.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/>
|
||||
</svg>
|
||||
<span class="font-semibold text-(--color-text)">{streak}</span>
|
||||
<span class="text-(--color-muted)">day{streak !== 1 ? 's' : ''} reading</span>
|
||||
</span>
|
||||
@@ -293,12 +254,6 @@
|
||||
<span class="absolute bottom-1.5 right-1.5 text-xs bg-(--color-brand) text-(--color-surface) font-bold px-1.5 py-0.5 rounded">
|
||||
{m.home_chapter_badge({ n: String(chapter) })}
|
||||
</span>
|
||||
<!-- Chapters ahead badge -->
|
||||
{#if book.total_chapters > 0 && chapter < book.total_chapters}
|
||||
<span class="absolute top-1.5 left-1.5 text-xs bg-black/60 text-white font-medium px-1.5 py-0.5 rounded">
|
||||
{book.total_chapters - chapter} left
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
<!-- Listen button (hover overlay) -->
|
||||
@@ -338,7 +293,7 @@
|
||||
<svg class="w-8 h-8 text-(--color-muted)" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
||||
</div>
|
||||
{/if}
|
||||
<span class="absolute top-1.5 right-1.5 text-xs bg-green-600/90 text-white font-bold px-1.5 py-0.5 rounded">✓ Done</span>
|
||||
<span class="absolute top-1.5 right-1.5 text-xs bg-green-600/90 text-white font-bold px-1.5 py-0.5 rounded">Done</span>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<h3 class="text-xs font-semibold text-(--color-text) line-clamp-2 leading-snug">{book.title ?? ''}</h3>
|
||||
@@ -597,6 +552,6 @@
|
||||
<span><span class="font-semibold text-(--color-text)">{data.stats.totalChapters.toLocaleString()}</span> {m.home_stat_chapters()}</span>
|
||||
{#if streak > 0}
|
||||
<span class="w-px h-4 bg-(--color-border)"></span>
|
||||
<span><span class="font-semibold text-(--color-text)">{streak}</span> day streak 🔥</span>
|
||||
<span><span class="font-semibold text-(--color-text)">{streak}</span> day streak</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
type LineSpacing = 'compact' | 'normal' | 'relaxed';
|
||||
type ReadWidth = 'narrow' | 'normal' | 'wide';
|
||||
type ParaStyle = 'spaced' | 'indented';
|
||||
type PlayerStyle = 'standard' | 'compact';
|
||||
type PlayerStyle = 'standard' | 'float';
|
||||
/** Controls how many lines fit on a page by adjusting the container height offset. */
|
||||
type PageLines = 'less' | 'normal' | 'more';
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
pageLines: PageLines;
|
||||
}
|
||||
|
||||
const LAYOUT_KEY = 'reader_layout_v1';
|
||||
const LAYOUT_KEY = 'reader_layout_v2';
|
||||
const LINE_HEIGHTS: Record<LineSpacing, number> = { compact: 1.55, normal: 1.85, relaxed: 2.2 };
|
||||
const READ_WIDTHS: Record<ReadWidth, string> = { narrow: '58ch', normal: '72ch', wide: 'min(90ch, 100%)' };
|
||||
/**
|
||||
@@ -950,7 +950,7 @@
|
||||
<div class="flex items-center gap-3 px-3 py-2.5">
|
||||
<span class="text-xs text-(--color-muted) w-14 shrink-0">Style</span>
|
||||
<div class="flex gap-1.5 flex-1">
|
||||
{#each ([['standard', 'Standard'], ['compact', 'Compact']] as const) as [s, lbl]}
|
||||
{#each ([['standard', 'Standard'], ['float', 'Float']] as const) as [s, lbl]}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => setLayout('playerStyle', s)}
|
||||
|
||||
Reference in New Issue
Block a user