i18n: internationalize all admin pages

Add full i18n coverage for the admin section — locale keys, layout nav,
users, videos, and articles pages (list, new, edit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 16:49:30 +01:00
parent 95fd9f48fc
commit 7d373b3aa3
10 changed files with 330 additions and 187 deletions

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { toast } from "svelte-sonner";
import { _ } from "svelte-i18n";
import { updateArticle, uploadFile } from "$lib/services";
import { marked } from "marked";
import { Button } from "$lib/components/ui/button";
@@ -40,9 +41,9 @@
try {
const res = await uploadFile(fd);
imageId = res.id;
toast.success("Image uploaded");
toast.success($_("admin.common.image_uploaded"));
} catch {
toast.error("Image upload failed");
toast.error($_("admin.common.image_upload_failed"));
}
}
@@ -62,10 +63,10 @@
featured,
publishDate: publishDate || undefined,
});
toast.success("Article updated");
toast.success($_("admin.article_form.update_success"));
goto("/admin/articles");
} catch (e: any) {
toast.error(e?.message ?? "Failed to update article");
toast.error(e?.message ?? $_("admin.article_form.update_error"));
} finally {
saving = false;
}
@@ -75,43 +76,43 @@
<div class="p-3 sm:p-6">
<div class="flex items-center gap-4 mb-6">
<Button variant="ghost" href="/admin/articles" size="sm">
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>Back
<span class="icon-[ri--arrow-left-line] h-4 w-4 mr-1"></span>{$_("common.back")}
</Button>
<h1 class="text-2xl font-bold">Edit article</h1>
<h1 class="text-2xl font-bold">{$_("admin.article_form.edit_title")}</h1>
</div>
<div class="space-y-5 max-w-4xl">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="title">Title *</Label>
<Label for="title">{$_("admin.common.title_field")}</Label>
<Input id="title" bind:value={title} />
</div>
<div class="space-y-1.5">
<Label for="slug">Slug *</Label>
<Label for="slug">{$_("admin.common.slug_field")}</Label>
<Input id="slug" bind:value={slug} />
</div>
</div>
<div class="space-y-1.5">
<Label for="excerpt">Excerpt</Label>
<Label for="excerpt">{$_("admin.article_form.excerpt")}</Label>
<Textarea id="excerpt" bind:value={excerpt} rows={2} />
</div>
<!-- Markdown editor with live preview -->
<div class="space-y-1.5">
<div class="flex items-center justify-between">
<Label>Content (Markdown)</Label>
<Label>{$_("admin.article_form.content")}</Label>
<div class="flex rounded-lg border border-border/40 overflow-hidden text-xs sm:hidden">
<button
type="button"
class={`px-3 py-1 transition-colors ${editorTab === "write" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
onclick={() => (editorTab = "write")}
>Write</button>
>{$_("admin.common.write")}</button>
<button
type="button"
class={`px-3 py-1 transition-colors ${editorTab === "preview" ? "bg-primary/10 text-primary" : "text-muted-foreground"}`}
onclick={() => (editorTab = "preview")}
>Preview</button>
>{$_("admin.common.preview")}</button>
</div>
</div>
<div class="sm:grid sm:grid-cols-2 sm:gap-4 min-h-96">
@@ -125,14 +126,14 @@
{#if preview}
{@html preview}
{:else}
<p class="text-muted-foreground italic text-sm">Preview will appear here…</p>
<p class="text-muted-foreground italic text-sm">{$_("admin.article_form.preview_placeholder")}</p>
{/if}
</div>
</div>
</div>
<div class="space-y-1.5">
<Label>Cover image</Label>
<Label>{$_("admin.common.cover_image")}</Label>
{#if imageId}
<img
src={getAssetUrl(imageId, "thumbnail")}
@@ -145,7 +146,7 @@
<!-- Author -->
<div class="space-y-1.5">
<Label>Author</Label>
<Label>{$_("admin.article_form.author")}</Label>
<Select type="single" bind:value={authorId}>
<SelectTrigger class="w-full">
{#if selectedAuthor}
@@ -154,11 +155,11 @@
{/if}
{selectedAuthor.artist_name}
{:else}
<span class="text-muted-foreground">No author</span>
<span class="text-muted-foreground">{$_("admin.article_form.no_author")}</span>
{/if}
</SelectTrigger>
<SelectContent>
<SelectItem value="">No author</SelectItem>
<SelectItem value="">{$_("admin.article_form.no_author")}</SelectItem>
{#each data.authors as author (author.id)}
<SelectItem value={author.id}>
{#if author.avatar}
@@ -173,30 +174,30 @@
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="space-y-1.5">
<Label for="category">Category</Label>
<Label for="category">{$_("admin.article_form.category")}</Label>
<Input id="category" bind:value={category} />
</div>
<div class="space-y-1.5">
<Label for="publishDate">Publish date</Label>
<Label for="publishDate">{$_("admin.common.publish_date")}</Label>
<Input id="publishDate" type="datetime-local" bind:value={publishDate} />
</div>
</div>
<div class="space-y-1.5">
<Label>Tags</Label>
<Label>{$_("admin.common.tags")}</Label>
<TagsInput bind:value={tags} />
</div>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" bind:checked={featured} class="rounded" />
<span class="text-sm">Featured</span>
<span class="text-sm">{$_("admin.common.featured")}</span>
</label>
<div class="flex gap-3 pt-2">
<Button onclick={handleSubmit} disabled={saving}>
{saving ? "Saving" : "Save changes"}
{saving ? $_("admin.common.saving") : $_("admin.common.save_changes")}
</Button>
<Button variant="outline" href="/admin/articles">Cancel</Button>
<Button variant="outline" href="/admin/articles">{$_("common.cancel")}</Button>
</div>
</div>
</div>