fix(ui): mobile media nav, product chrome, empty CTAs, density label, a11y
This commit is contained in:
parent
a7873891f2
commit
11a18a367c
|
|
@ -15,12 +15,46 @@ export function AppShell(props: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const base = `/${props.locale}`;
|
const base = `/${props.locale}`;
|
||||||
|
const product = props.labels["meta.product"] ?? props.labels["meta.brand"] ?? "弹弹 Web";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen bg-background text-foreground">
|
<div className="flex min-h-screen flex-col bg-background text-foreground md:flex-row">
|
||||||
<aside className="hidden w-56 shrink-0 border-r border-border bg-muted/40 md:flex md:flex-col md:p-4">
|
{/* 窄屏:顶栏品牌 + 横向导航(md 以下侧栏隐藏时的唯一入口) */}
|
||||||
<div className="mb-6 px-2 text-sm font-semibold tracking-wide">
|
<div className="sticky top-0 z-40 border-b border-border bg-background/95 backdrop-blur md:hidden">
|
||||||
{props.labels["meta.brand"] ?? "弹弹 Web"}
|
<div className="flex items-center gap-3 px-3 pt-3">
|
||||||
|
<a href={`${base}/library`} className="text-sm font-semibold tracking-wide">
|
||||||
|
{product}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<nav
|
||||||
|
className="flex gap-1 overflow-x-auto px-2 pb-2 pt-2"
|
||||||
|
aria-label="media-nav-mobile"
|
||||||
|
>
|
||||||
|
{NAV.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.id}
|
||||||
|
href={`${base}/${item.href}`}
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 rounded-full px-3 py-1.5 text-sm transition-colors",
|
||||||
|
props.active === item.id
|
||||||
|
? "bg-primary font-medium text-primary-foreground"
|
||||||
|
: "text-muted-foreground hover:bg-muted",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.labels[item.hrefKey] ?? item.href}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 宽屏:侧栏 */}
|
||||||
|
<aside className="hidden w-56 shrink-0 border-r border-border bg-muted/40 md:flex md:flex-col md:p-4">
|
||||||
|
<a
|
||||||
|
href={`${base}/library`}
|
||||||
|
className="mb-6 px-2 text-sm font-semibold tracking-wide"
|
||||||
|
>
|
||||||
|
{product}
|
||||||
|
</a>
|
||||||
<nav className="flex flex-col gap-1" aria-label="sidebar">
|
<nav className="flex flex-col gap-1" aria-label="sidebar">
|
||||||
{NAV.map((item) => (
|
{NAV.map((item) => (
|
||||||
<a
|
<a
|
||||||
|
|
@ -38,6 +72,7 @@ export function AppShell(props: {
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main className="flex min-w-0 flex-1 flex-col p-4 md:p-6">{props.children}</main>
|
<main className="flex min-w-0 flex-1 flex-col p-4 md:p-6">{props.children}</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -173,8 +173,16 @@ function BrowseInner(props: { locale: Locale }) {
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{!mountId && (
|
{!mountId && (
|
||||||
<li className="p-4 text-sm text-muted-foreground">
|
<li className="flex flex-col items-start gap-3 p-4 text-sm text-muted-foreground">
|
||||||
{t(locale, "browse.empty")}
|
<span>{t(locale, "browse.empty")}</span>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = `/${locale}/mounts`;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(locale, "browse.addMount")}
|
||||||
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Locale } from "@app/i18n";
|
import type { Locale } from "@app/i18n";
|
||||||
import { t } from "@app/i18n";
|
import { t } from "@app/i18n";
|
||||||
import { Button } from "@app/ui";
|
import { Button, toast } from "@app/ui";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { createTRPCReactClient, trpc } from "~/lib/trpc";
|
import { createTRPCReactClient, trpc } from "~/lib/trpc";
|
||||||
|
|
@ -48,7 +48,14 @@ function Inner(props: { locale: Locale }) {
|
||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
const next = { enabled, opacity, density };
|
const next = { enabled, opacity, density };
|
||||||
localStorage.setItem(DANMAKU_SETTINGS_KEY, JSON.stringify(next));
|
localStorage.setItem(DANMAKU_SETTINGS_KEY, JSON.stringify(next));
|
||||||
save.mutate(next);
|
save.mutate(next, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t(locale, "danmaku.saved"));
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error(t(locale, "danmaku.save"));
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -75,7 +82,9 @@ function Inner(props: { locale: Locale }) {
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="block text-sm">
|
<label className="block text-sm">
|
||||||
{t(locale, "danmaku.densityValue", { value: density })}
|
{t(locale, "danmaku.densityValue", {
|
||||||
|
value: (density * 100).toFixed(0),
|
||||||
|
})}
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min={0.1}
|
min={0.1}
|
||||||
|
|
@ -86,7 +95,9 @@ function Inner(props: { locale: Locale }) {
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<Button onClick={onSave}>{t(locale, "danmaku.save")}</Button>
|
<Button type="button" onClick={onSave}>
|
||||||
|
{t(locale, "danmaku.save")}
|
||||||
|
</Button>
|
||||||
{save.isSuccess && (
|
{save.isSuccess && (
|
||||||
<p className="text-sm text-green-600">{t(locale, "danmaku.saved")}</p>
|
<p className="text-sm text-green-600">{t(locale, "danmaku.saved")}</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,28 @@ function LibraryInner(props: { locale: Locale }) {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{progressList.length === 0 && !list.isPending && (
|
{progressList.length === 0 && !list.isPending && (
|
||||||
<p className="col-span-full text-sm text-muted-foreground">
|
<div className="col-span-full flex flex-col items-start gap-3 rounded-lg border border-border bg-card p-6">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
{t(locale, "library.empty")}
|
{t(locale, "library.empty")}
|
||||||
</p>
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = `/${locale}/browse`;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(locale, "library.ctaBrowse")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = `/${locale}/mounts`;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t(locale, "library.ctaMounts")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{hasMore && (
|
{hasMore && (
|
||||||
|
|
|
||||||
|
|
@ -46,29 +46,34 @@ function MountsInner(props: { locale: Locale }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
|
aria-label={t(locale, "mounts.namePlaceholder")}
|
||||||
placeholder={t(locale, "mounts.namePlaceholder")}
|
placeholder={t(locale, "mounts.namePlaceholder")}
|
||||||
value={form.name}
|
value={form.name}
|
||||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
aria-label={t(locale, "mounts.baseUrlPlaceholder")}
|
||||||
placeholder={t(locale, "mounts.baseUrlPlaceholder")}
|
placeholder={t(locale, "mounts.baseUrlPlaceholder")}
|
||||||
value={form.baseUrl}
|
value={form.baseUrl}
|
||||||
onChange={(e) => setForm({ ...form, baseUrl: e.target.value })}
|
onChange={(e) => setForm({ ...form, baseUrl: e.target.value })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
aria-label={t(locale, "mounts.usernamePlaceholder")}
|
||||||
placeholder={t(locale, "mounts.usernamePlaceholder")}
|
placeholder={t(locale, "mounts.usernamePlaceholder")}
|
||||||
value={form.username}
|
value={form.username}
|
||||||
onChange={(e) => setForm({ ...form, username: e.target.value })}
|
onChange={(e) => setForm({ ...form, username: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
|
aria-label={t(locale, "mounts.passwordPlaceholder")}
|
||||||
placeholder={t(locale, "mounts.passwordPlaceholder")}
|
placeholder={t(locale, "mounts.passwordPlaceholder")}
|
||||||
value={form.password}
|
value={form.password}
|
||||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
aria-label={t(locale, "mounts.rootPathPlaceholder")}
|
||||||
placeholder={t(locale, "mounts.rootPathPlaceholder")}
|
placeholder={t(locale, "mounts.rootPathPlaceholder")}
|
||||||
value={form.rootPath}
|
value={form.rootPath}
|
||||||
onChange={(e) => setForm({ ...form, rootPath: e.target.value })}
|
onChange={(e) => setForm({ ...form, rootPath: e.target.value })}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ export interface Props {
|
||||||
seo?: PageSeoInput;
|
seo?: PageSeoInput;
|
||||||
/** 媒体页只保留 AppShell 侧栏时隐藏模板 side-rail(含左让位) */
|
/** 媒体页只保留 AppShell 侧栏时隐藏模板 side-rail(含左让位) */
|
||||||
hideSideRail?: boolean;
|
hideSideRail?: boolean;
|
||||||
|
/** 媒体页隐藏模板页脚 */
|
||||||
|
hideFooter?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -33,10 +35,12 @@ const {
|
||||||
switchLabel,
|
switchLabel,
|
||||||
seo,
|
seo,
|
||||||
hideSideRail = false,
|
hideSideRail = false,
|
||||||
|
hideFooter = false,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
const brandName = brand ?? t(locale, "meta.brand");
|
const brandName = brand ?? t(locale, "meta.brand");
|
||||||
const nav = navProp ?? [];
|
const nav = navProp ?? [];
|
||||||
|
const isProductBrand = Boolean(brand) && brand !== t(locale, "meta.brand");
|
||||||
|
|
||||||
const siteSeo = getSiteSeo(locale);
|
const siteSeo = getSiteSeo(locale);
|
||||||
const pageSeoInput: PageSeoInput = {
|
const pageSeoInput: PageSeoInput = {
|
||||||
|
|
@ -50,8 +54,12 @@ const jsonLdHtml = toJsonLdScript(buildDefaultJsonLd(siteSeo, pageSeoInput));
|
||||||
const pageTitle = headTags.find((tTag) => tTag.key === "og:title")?.value ?? title;
|
const pageTitle = headTags.find((tTag) => tTag.key === "og:title")?.value ?? title;
|
||||||
|
|
||||||
const homeHref = locale === "zh-CN" ? "/zh-CN" : "/en";
|
const homeHref = locale === "zh-CN" ? "/zh-CN" : "/en";
|
||||||
const brandHref = homeHref;
|
const brandHref = isProductBrand ? (locale === "zh-CN" ? "/zh-CN/library" : "/en/library") : homeHref;
|
||||||
const brandInitial = locale === "zh-CN" ? "模" : "A";
|
const brandInitial = isProductBrand
|
||||||
|
? brandName.charAt(0)
|
||||||
|
: locale === "zh-CN"
|
||||||
|
? "模"
|
||||||
|
: "A";
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
|
@ -210,13 +218,17 @@ const brandInitial = locale === "zh-CN" ? "模" : "A";
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{!hideFooter && (
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="u-dotline" aria-hidden="true"></div>
|
<div class="u-dotline" aria-hidden="true"></div>
|
||||||
<div class="site-footer__inner">
|
<div class="site-footer__inner">
|
||||||
<p class="u-mono">{t(locale, "meta.brand")}</p>
|
<p class="u-mono">{isProductBrand ? brandName : t(locale, "meta.brand")}</p>
|
||||||
<p class="u-label">{t(locale, "home.footer")}</p>
|
<p class="u-label">
|
||||||
|
{isProductBrand ? t(locale, "meta.productTagline") : t(locale, "home.footer")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const { locale } = Astro.props;
|
||||||
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
"meta.brand": t(locale, "meta.brand"),
|
"meta.brand": t(locale, "meta.brand"),
|
||||||
|
"meta.product": t(locale, "meta.product"),
|
||||||
"nav.library": t(locale, "nav.library"),
|
"nav.library": t(locale, "nav.library"),
|
||||||
"nav.browse": t(locale, "nav.browse"),
|
"nav.browse": t(locale, "nav.browse"),
|
||||||
"nav.mounts": t(locale, "nav.mounts"),
|
"nav.mounts": t(locale, "nav.mounts"),
|
||||||
|
|
@ -25,6 +26,8 @@ const labels: Record<string, string> = {
|
||||||
locale={locale}
|
locale={locale}
|
||||||
nav={[]}
|
nav={[]}
|
||||||
hideSideRail
|
hideSideRail
|
||||||
|
hideFooter
|
||||||
|
brand={t(locale, "meta.product")}
|
||||||
switchHref={alternatePath(Astro.url.pathname, other)}
|
switchHref={alternatePath(Astro.url.pathname, other)}
|
||||||
switchLabel={t(other, `locale.${other}` as never)}
|
switchLabel={t(other, `locale.${other}` as never)}
|
||||||
seo={{
|
seo={{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const { locale } = Astro.props;
|
||||||
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
"meta.brand": t(locale, "meta.brand"),
|
"meta.brand": t(locale, "meta.brand"),
|
||||||
|
"meta.product": t(locale, "meta.product"),
|
||||||
"nav.library": t(locale, "nav.library"),
|
"nav.library": t(locale, "nav.library"),
|
||||||
"nav.browse": t(locale, "nav.browse"),
|
"nav.browse": t(locale, "nav.browse"),
|
||||||
"nav.mounts": t(locale, "nav.mounts"),
|
"nav.mounts": t(locale, "nav.mounts"),
|
||||||
|
|
@ -25,6 +26,8 @@ const labels: Record<string, string> = {
|
||||||
locale={locale}
|
locale={locale}
|
||||||
nav={[]}
|
nav={[]}
|
||||||
hideSideRail
|
hideSideRail
|
||||||
|
hideFooter
|
||||||
|
brand={t(locale, "meta.product")}
|
||||||
switchHref={alternatePath(Astro.url.pathname, other)}
|
switchHref={alternatePath(Astro.url.pathname, other)}
|
||||||
switchLabel={t(other, `locale.${other}` as never)}
|
switchLabel={t(other, `locale.${other}` as never)}
|
||||||
seo={{
|
seo={{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const { locale } = Astro.props;
|
||||||
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
"meta.brand": t(locale, "meta.brand"),
|
"meta.brand": t(locale, "meta.brand"),
|
||||||
|
"meta.product": t(locale, "meta.product"),
|
||||||
"nav.library": t(locale, "nav.library"),
|
"nav.library": t(locale, "nav.library"),
|
||||||
"nav.browse": t(locale, "nav.browse"),
|
"nav.browse": t(locale, "nav.browse"),
|
||||||
"nav.mounts": t(locale, "nav.mounts"),
|
"nav.mounts": t(locale, "nav.mounts"),
|
||||||
|
|
@ -25,6 +26,8 @@ const labels: Record<string, string> = {
|
||||||
locale={locale}
|
locale={locale}
|
||||||
nav={[]}
|
nav={[]}
|
||||||
hideSideRail
|
hideSideRail
|
||||||
|
hideFooter
|
||||||
|
brand={t(locale, "meta.product")}
|
||||||
switchHref={alternatePath(Astro.url.pathname, other)}
|
switchHref={alternatePath(Astro.url.pathname, other)}
|
||||||
switchLabel={t(other, `locale.${other}` as never)}
|
switchLabel={t(other, `locale.${other}` as never)}
|
||||||
seo={{
|
seo={{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const { locale } = Astro.props;
|
||||||
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
"meta.brand": t(locale, "meta.brand"),
|
"meta.brand": t(locale, "meta.brand"),
|
||||||
|
"meta.product": t(locale, "meta.product"),
|
||||||
"nav.library": t(locale, "nav.library"),
|
"nav.library": t(locale, "nav.library"),
|
||||||
"nav.browse": t(locale, "nav.browse"),
|
"nav.browse": t(locale, "nav.browse"),
|
||||||
"nav.mounts": t(locale, "nav.mounts"),
|
"nav.mounts": t(locale, "nav.mounts"),
|
||||||
|
|
@ -25,6 +26,8 @@ const labels: Record<string, string> = {
|
||||||
locale={locale}
|
locale={locale}
|
||||||
nav={[]}
|
nav={[]}
|
||||||
hideSideRail
|
hideSideRail
|
||||||
|
hideFooter
|
||||||
|
brand={t(locale, "meta.product")}
|
||||||
switchHref={alternatePath(Astro.url.pathname, other)}
|
switchHref={alternatePath(Astro.url.pathname, other)}
|
||||||
switchLabel={t(other, `locale.${other}` as never)}
|
switchLabel={t(other, `locale.${other}` as never)}
|
||||||
seo={{
|
seo={{
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const { locale, mediaItemId } = Astro.props;
|
||||||
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
"meta.brand": t(locale, "meta.brand"),
|
"meta.brand": t(locale, "meta.brand"),
|
||||||
|
"meta.product": t(locale, "meta.product"),
|
||||||
"nav.library": t(locale, "nav.library"),
|
"nav.library": t(locale, "nav.library"),
|
||||||
"nav.browse": t(locale, "nav.browse"),
|
"nav.browse": t(locale, "nav.browse"),
|
||||||
"nav.mounts": t(locale, "nav.mounts"),
|
"nav.mounts": t(locale, "nav.mounts"),
|
||||||
|
|
@ -26,6 +27,8 @@ const labels: Record<string, string> = {
|
||||||
locale={locale}
|
locale={locale}
|
||||||
nav={[]}
|
nav={[]}
|
||||||
hideSideRail
|
hideSideRail
|
||||||
|
hideFooter
|
||||||
|
brand={t(locale, "meta.product")}
|
||||||
switchHref={alternatePath(Astro.url.pathname, other)}
|
switchHref={alternatePath(Astro.url.pathname, other)}
|
||||||
switchLabel={t(other, `locale.${other}` as never)}
|
switchLabel={t(other, `locale.${other}` as never)}
|
||||||
seo={{
|
seo={{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import type { MessageKey } from "./zh-CN.js";
|
||||||
/** 与 zh-CN 同构;类型强制 key 对齐。 */
|
/** 与 zh-CN 同构;类型强制 key 对齐。 */
|
||||||
export const en: Record<MessageKey, string> = {
|
export const en: Record<MessageKey, string> = {
|
||||||
"meta.brand": "App Template",
|
"meta.brand": "App Template",
|
||||||
|
"meta.product": "DanDan Web",
|
||||||
|
"meta.productTagline": "Local files · online danmaku",
|
||||||
"meta.tagline": "Astro · tRPC · Drizzle scaffold",
|
"meta.tagline": "Astro · tRPC · Drizzle scaffold",
|
||||||
|
|
||||||
"nav.home": "Home",
|
"nav.home": "Home",
|
||||||
|
|
@ -48,6 +50,8 @@ export const en: Record<MessageKey, string> = {
|
||||||
"library.match": "Match",
|
"library.match": "Match",
|
||||||
"library.empty": "No media yet. Scan a mount in the file browser first.",
|
"library.empty": "No media yet. Scan a mount in the file browser first.",
|
||||||
"library.loadMore": "Load more",
|
"library.loadMore": "Load more",
|
||||||
|
"library.ctaBrowse": "Open file browser",
|
||||||
|
"library.ctaMounts": "Add mount",
|
||||||
"library.matchTitle": "Manual Bangumi match",
|
"library.matchTitle": "Manual Bangumi match",
|
||||||
"library.searchWork": "Search by title",
|
"library.searchWork": "Search by title",
|
||||||
"library.bind": "Bind",
|
"library.bind": "Bind",
|
||||||
|
|
@ -61,6 +65,7 @@ export const en: Record<MessageKey, string> = {
|
||||||
"browse.root": "Root",
|
"browse.root": "Root",
|
||||||
"browse.up": "Up",
|
"browse.up": "Up",
|
||||||
"browse.empty": "Empty directory or no mount selected",
|
"browse.empty": "Empty directory or no mount selected",
|
||||||
|
"browse.addMount": "Add mount",
|
||||||
"browse.loading": "Loading…",
|
"browse.loading": "Loading…",
|
||||||
"browse.loadError": "Failed to read directory",
|
"browse.loadError": "Failed to read directory",
|
||||||
"browse.playError": "Failed to add to library",
|
"browse.playError": "Failed to add to library",
|
||||||
|
|
@ -104,7 +109,7 @@ export const en: Record<MessageKey, string> = {
|
||||||
"danmaku.density": "Density",
|
"danmaku.density": "Density",
|
||||||
"danmaku.enabled": "Enable danmaku",
|
"danmaku.enabled": "Enable danmaku",
|
||||||
"danmaku.opacityValue": "Opacity {value}%",
|
"danmaku.opacityValue": "Opacity {value}%",
|
||||||
"danmaku.densityValue": "Density {value}x",
|
"danmaku.densityValue": "Density {value}%",
|
||||||
"danmaku.save": "Save",
|
"danmaku.save": "Save",
|
||||||
"danmaku.saved": "Saved (local session)",
|
"danmaku.saved": "Saved (local session)",
|
||||||
"danmaku.localImportHint":
|
"danmaku.localImportHint":
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
*/
|
*/
|
||||||
export const zhCN = {
|
export const zhCN = {
|
||||||
"meta.brand": "应用模板",
|
"meta.brand": "应用模板",
|
||||||
|
"meta.product": "弹弹 Web",
|
||||||
|
"meta.productTagline": "本地片源 · 在线弹幕",
|
||||||
"meta.tagline": "Astro · tRPC · Drizzle 脚手架",
|
"meta.tagline": "Astro · tRPC · Drizzle 脚手架",
|
||||||
|
|
||||||
"nav.home": "首页",
|
"nav.home": "首页",
|
||||||
|
|
@ -49,6 +51,8 @@ export const zhCN = {
|
||||||
"library.match": "匹配",
|
"library.match": "匹配",
|
||||||
"library.empty": "暂无媒体,请先在文件浏览中扫描挂载。",
|
"library.empty": "暂无媒体,请先在文件浏览中扫描挂载。",
|
||||||
"library.loadMore": "加载更多",
|
"library.loadMore": "加载更多",
|
||||||
|
"library.ctaBrowse": "打开文件浏览",
|
||||||
|
"library.ctaMounts": "添加挂载",
|
||||||
"library.matchTitle": "手动匹配 Bangumi",
|
"library.matchTitle": "手动匹配 Bangumi",
|
||||||
"library.searchWork": "搜索作品名",
|
"library.searchWork": "搜索作品名",
|
||||||
"library.bind": "绑定",
|
"library.bind": "绑定",
|
||||||
|
|
@ -62,6 +66,7 @@ export const zhCN = {
|
||||||
"browse.root": "根目录",
|
"browse.root": "根目录",
|
||||||
"browse.up": "上一级",
|
"browse.up": "上一级",
|
||||||
"browse.empty": "空目录或未选择挂载",
|
"browse.empty": "空目录或未选择挂载",
|
||||||
|
"browse.addMount": "添加挂载",
|
||||||
"browse.loading": "加载中…",
|
"browse.loading": "加载中…",
|
||||||
"browse.loadError": "目录读取失败",
|
"browse.loadError": "目录读取失败",
|
||||||
"browse.playError": "入库失败,请重试",
|
"browse.playError": "入库失败,请重试",
|
||||||
|
|
@ -105,7 +110,7 @@ export const zhCN = {
|
||||||
"danmaku.density": "密度",
|
"danmaku.density": "密度",
|
||||||
"danmaku.enabled": "启用弹幕",
|
"danmaku.enabled": "启用弹幕",
|
||||||
"danmaku.opacityValue": "不透明度 {value}%",
|
"danmaku.opacityValue": "不透明度 {value}%",
|
||||||
"danmaku.densityValue": "密度 {value}x",
|
"danmaku.densityValue": "密度 {value}%",
|
||||||
"danmaku.save": "保存",
|
"danmaku.save": "保存",
|
||||||
"danmaku.saved": "已保存(本地会话)",
|
"danmaku.saved": "已保存(本地会话)",
|
||||||
"danmaku.localImportHint": "本地 XML 导入在播放页操作;此处保存默认开关与显示参数。",
|
"danmaku.localImportHint": "本地 XML 导入在播放页操作;此处保存默认开关与显示参数。",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue