chore(product): replace template branding with 弹弹 Web copy and product SEO
This commit is contained in:
parent
668f90bcc0
commit
f48f0c8d51
|
|
@ -1,6 +1,6 @@
|
|||
# App Template — 设计方向
|
||||
# 弹弹 Web — 设计方向
|
||||
|
||||
脚手架视觉基线:**深空工业中控终端**。不绑定具体产品叙事;下游可在此 palette / type 上换肤,但 **`@app/design-tokens` 色板 token 路径约定不变**(改值可以,改结构需同步全局)。
|
||||
产品视觉基线:**shadcn/ui Dashboard**(媒体五页侧栏/顶栏导航、卡片网格、中性灰阶);模板深空工业 token 仍可用于非媒体页,但 **`@app/design-tokens` 色板 token 路径约定不变**(改值可以,改结构需同步全局)。
|
||||
|
||||
## Style anchor
|
||||
深色工业 HUD / 控制台:近黑底、信号黄强调、发丝线框、等宽铭牌。
|
||||
|
|
@ -36,8 +36,8 @@
|
|||
## Interaction
|
||||
- 主题:系统 / 亮 / 暗(`app-theme`)
|
||||
- 语言:cookie `app-lang` + Accept-Language
|
||||
- SSR 输出结构与内容;动效只在浏览器 effect 内绑定
|
||||
- 尊重 `prefers-reduced-motion`
|
||||
- **薄 SSR**:服务端输出布局 / head / 导航;页面主体(`components/pages/*Body`)`client:load` 在浏览器渲染
|
||||
- 动效只在浏览器 effect 内绑定;尊重 `prefers-reduced-motion`
|
||||
|
||||
## 范围
|
||||
`tokens.css` · `globals.css` · `AppLayout.astro` · `ThemeSwitch` · `UserMenu` · `home` / `dashboard` 视图 · `ui/*`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# App Template — Astro + tRPC + Drizzle Monorepo
|
||||
# 弹弹 Web — 本地片源 · 在线弹幕
|
||||
|
||||
可复用全栈脚手架:**不绑定产品业务**,只保留鉴权 + `user` 垂直示例,演示完整分层接线。
|
||||
多用户 Web 媒体中心:WebDAV/OpenList 挂载、媒体库扫描与 Bangumi 刮削、ArtPlayer 播放与弹幕(开放网络 + 本地 XML)。
|
||||
|
||||
基于 monorepo 分层(types → models → db → dao → trpc → web),本仓库按**产品仓**维护。
|
||||
|
||||
```
|
||||
.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* 首页主体:薄 SSR — Astro 壳负责 head/导航,本组件在浏览器渲染。
|
||||
*/
|
||||
import { t, type Locale } from "@app/i18n";
|
||||
|
||||
export interface HomeBodyProps {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export default function HomeBody({ locale }: HomeBodyProps) {
|
||||
const tr = (key: Parameters<typeof t>[1]) => t(locale, key);
|
||||
const libraryHref = locale === "zh-CN" ? "/zh-CN/library" : "/en/library";
|
||||
const loginHref = locale === "zh-CN" ? "/zh-CN/auth/login" : "/en/auth/login";
|
||||
|
||||
return (
|
||||
<section className="space-y-10 py-10">
|
||||
<div className="u-reveal">
|
||||
<p className="u-label u-label-edge">{tr("meta.tagline")}</p>
|
||||
<h1 className="mt-3 text-[40px] font-black tracking-tight leading-[1.05]">
|
||||
{tr("home.heroLine1")}
|
||||
<br />
|
||||
{tr("home.heroLine2")}
|
||||
</h1>
|
||||
<span className="u-edge-bar mt-5" />
|
||||
<p
|
||||
className="mt-5 max-w-xl text-[14px] leading-relaxed"
|
||||
style={{ color: "var(--ink-2);" }}
|
||||
>
|
||||
{tr("home.subtitle")}
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<a href={libraryHref} className="u-btn u-btn--edge">
|
||||
{tr("home.ctaDashboard")}
|
||||
</a>
|
||||
<a href={loginHref} className="u-btn u-btn--ghost">
|
||||
{tr("home.ctaLogin")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<section className="u-card p-6 u-reveal">
|
||||
<p className="u-label">{tr("home.stackTitle")}</p>
|
||||
<p
|
||||
className="mt-3 text-[14px] leading-relaxed"
|
||||
style={{ color: "var(--ink-2);" }}
|
||||
>
|
||||
{tr("home.stackBody")}
|
||||
</p>
|
||||
</section>
|
||||
<section className="u-card p-6 u-reveal">
|
||||
<p className="u-label">{tr("home.layerTitle")}</p>
|
||||
<p
|
||||
className="mt-3 text-[14px] leading-relaxed"
|
||||
style={{ color: "var(--ink-2);" }}
|
||||
>
|
||||
{tr("home.layerBody")}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -40,7 +40,6 @@ const {
|
|||
|
||||
const brandName = brand ?? t(locale, "meta.brand");
|
||||
const nav = navProp ?? [];
|
||||
const isProductBrand = Boolean(brand) && brand !== t(locale, "meta.brand");
|
||||
|
||||
const siteSeo = getSiteSeo(locale);
|
||||
const pageSeoInput: PageSeoInput = {
|
||||
|
|
@ -54,12 +53,9 @@ const jsonLdHtml = toJsonLdScript(buildDefaultJsonLd(siteSeo, pageSeoInput));
|
|||
const pageTitle = headTags.find((tTag) => tTag.key === "og:title")?.value ?? title;
|
||||
|
||||
const homeHref = locale === "zh-CN" ? "/zh-CN" : "/en";
|
||||
const brandHref = isProductBrand ? (locale === "zh-CN" ? "/zh-CN/library" : "/en/library") : homeHref;
|
||||
const brandInitial = isProductBrand
|
||||
? brandName.charAt(0)
|
||||
: locale === "zh-CN"
|
||||
? "模"
|
||||
: "A";
|
||||
const libraryHref = locale === "zh-CN" ? "/zh-CN/library" : "/en/library";
|
||||
const brandHref = hideSideRail ? libraryHref : homeHref;
|
||||
const brandInitial = brandName.charAt(0) || "弹";
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -222,10 +218,8 @@ const brandInitial = isProductBrand
|
|||
<footer class="site-footer">
|
||||
<div class="u-dotline" aria-hidden="true"></div>
|
||||
<div class="site-footer__inner">
|
||||
<p class="u-mono">{isProductBrand ? brandName : t(locale, "meta.brand")}</p>
|
||||
<p class="u-label">
|
||||
{isProductBrand ? t(locale, "meta.productTagline") : t(locale, "home.footer")}
|
||||
</p>
|
||||
<p class="u-mono">{brandName}</p>
|
||||
<p class="u-label">{t(locale, "home.footer")}</p>
|
||||
</div>
|
||||
</footer>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ export function getSiteSeo(locale: Locale): SiteSeoConfig {
|
|||
const name = t(locale, "meta.brand");
|
||||
const isZh = locale === "zh-CN";
|
||||
const description = isZh
|
||||
? "Astro + tRPC + Drizzle monorepo 应用脚手架,开箱即用的分层基线。"
|
||||
: "Astro + tRPC + Drizzle monorepo application scaffold with a ready vertical baseline.";
|
||||
? "挂载 WebDAV/OpenList 片源的 Web 媒体库:扫描、Bangumi 刮削、ArtPlayer 播放与在线弹幕。"
|
||||
: "Web media library for WebDAV/OpenList: scan, Bangumi scrape, ArtPlayer playback and online danmaku.";
|
||||
|
||||
return {
|
||||
siteUrl,
|
||||
|
|
@ -29,15 +29,15 @@ export function getSiteSeo(locale: Locale): SiteSeoConfig {
|
|||
organization: { name, url: siteUrl },
|
||||
llms: {
|
||||
summary: isZh
|
||||
? "这是可复用的应用脚手架模板:Astro SSR + React islands + tRPC + Drizzle/Turso,附带鉴权与用户垂直示例。"
|
||||
: "Reusable application scaffold: Astro SSR + React islands + tRPC + Drizzle/Turso, with auth and a user vertical example.",
|
||||
? "弹弹 Web:多用户 Web 媒体中心。WebDAV 挂载、媒体库扫描、Bangumi 刮削、ArtPlayer 播放与弹弹play 开放弹幕网络中继,本地 XML 兜底。"
|
||||
: "DanDan Web: multi-user web media hub. WebDAV mounts, library scan, Bangumi scraping, ArtPlayer playback, dandanplay open danmaku relay with local XML fallback.",
|
||||
details: [
|
||||
isZh
|
||||
? "分层:types → models → db → dao → trpc → web。业务实体按 user 切片扩展,禁止跨层。"
|
||||
: "Layers: types → models → db → dao → trpc → web. Extend domains via the user slice; never skip layers.",
|
||||
? "分层:types → models → db → dao → trpc → web。媒体域按垂直切片扩展。"
|
||||
: "Layers: types → models → db → dao → trpc → web. Extend the media domain as a vertical slice.",
|
||||
isZh
|
||||
? "不绑定具体产品业务;works 等领域逻辑应在业务仓库中添加。"
|
||||
: "No product domain baked in; add domains such as works in your product repo.",
|
||||
? "挂载凭据仅存服务端加密;浏览器不直连网盘密码。"
|
||||
: "Mount credentials are encrypted server-side; the browser never sees remote passwords.",
|
||||
],
|
||||
pages: PUBLIC_PATHS.map((basePath) => ({
|
||||
path: pathForLocale(locale, basePath),
|
||||
|
|
@ -45,9 +45,9 @@ export function getSiteSeo(locale: Locale): SiteSeoConfig {
|
|||
description,
|
||||
})),
|
||||
discouraged: isZh
|
||||
? ["/dashboard、/settings 等需登录页面", "/auth/* 等认证页", "/api/* 接口响应"]
|
||||
? ["/library、/watch 等需登录页面", "/auth/* 等认证页", "/api/* 接口响应"]
|
||||
: [
|
||||
"/dashboard, /settings and other app pages",
|
||||
"/library, /watch and other app pages",
|
||||
"/auth/* and auth pages",
|
||||
"/api/* responses",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -15,16 +15,16 @@ function buildFor(locale: Locale): string {
|
|||
}
|
||||
|
||||
return buildLlmsFullTxt(siteSeo, {
|
||||
title: `${t(locale, "meta.brand")} · scaffold index`,
|
||||
title: `${t(locale, "meta.brand")} · product guide`,
|
||||
sections: [
|
||||
{
|
||||
heading: t(locale, "home.stackTitle"),
|
||||
path: "/zh-CN",
|
||||
summary: t(locale, "home.stackBody"),
|
||||
points: [
|
||||
"Astro 7 SSR + React 19 islands",
|
||||
"tRPC v11 + Drizzle ORM + Turso/libSQL",
|
||||
"Tailwind CSS 4 + Biome + Yarn 4 workspaces + Turbo",
|
||||
"WebDAV / OpenList 挂载代理(凭据服务端加密)",
|
||||
"扫描入库 + Bangumi 刮削 + ArtPlayer 播放",
|
||||
"弹弹play 开放弹幕网络中继,本地 XML 兜底",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -32,19 +32,19 @@ function buildFor(locale: Locale): string {
|
|||
path: "/zh-CN",
|
||||
summary: t(locale, "home.layerBody"),
|
||||
points: [
|
||||
"types → models → db → dao → trpc → web",
|
||||
"user / auth 为垂直示例,可替换为业务实体",
|
||||
"多用户:每人独立媒体库与播放进度",
|
||||
"媒体页 shadcn Dashboard 导航:媒体库 / 浏览 / 挂载 / 弹幕设置",
|
||||
],
|
||||
},
|
||||
],
|
||||
faqs: [
|
||||
{
|
||||
question: "这个模板包含业务逻辑吗?",
|
||||
answer: "不包含产品业务。仅保留鉴权与 user 垂直示例,用于演示分层接线;具体业务应在下游仓库添加。",
|
||||
question: "如何接入 OpenList?",
|
||||
answer: "在挂载管理填写 OpenList 暴露的 WebDAV 地址(如 https://host/dav)与账号,测试连接后保存。",
|
||||
},
|
||||
{
|
||||
question: "如何新增业务实体?",
|
||||
answer: "按 user 切片扩展:@app/types 加 schema → @app/models 建表 → @app/dao 加查询 → @app/trpc 加 service/router → @app/web 挂页面。",
|
||||
question: "弹幕从哪里来?",
|
||||
answer: "优先走弹弹play 开放弹幕网络(需配置 AppId/AppSecret);失败或未配置时可在播放页导入本地 XML。",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
---
|
||||
import { alternatePath, type Locale, t } from "@app/i18n";
|
||||
import { Toaster } from "@app/ui";
|
||||
import UserList from "~/components/UserList";
|
||||
import { alternatePath, type Locale, localePath, t } from "@app/i18n";
|
||||
import AppLayout from "~/layouts/AppLayout.astro";
|
||||
|
||||
export interface Props {
|
||||
|
|
@ -10,6 +8,9 @@ export interface Props {
|
|||
|
||||
const { locale } = Astro.props;
|
||||
const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
||||
|
||||
// 产品:/dashboard 即媒体库(保留路由以免旧链接与 e2e 404)
|
||||
return Astro.redirect(localePath(locale, "/library"));
|
||||
---
|
||||
|
||||
<AppLayout
|
||||
|
|
@ -24,18 +25,5 @@ const other: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
|||
robots: ["noindex", "nofollow"],
|
||||
}}
|
||||
>
|
||||
<div class="space-y-10 py-8">
|
||||
<section class="u-reveal">
|
||||
<p class="u-label u-label-edge">{t(locale, "dashboard.label")}</p>
|
||||
<h2 class="mt-2 text-[32px] font-black tracking-tight">{t(locale, "dashboard.heading")}</h2>
|
||||
<span class="u-edge-bar mt-4"></span>
|
||||
<p class="mt-4 max-w-2xl text-[14px] leading-relaxed" style="color: var(--ink-2);">
|
||||
{t(locale, "dashboard.desc")}
|
||||
</p>
|
||||
<div class="u-card mt-8 p-6">
|
||||
<UserList client:visible />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<Toaster client:load />
|
||||
<p class="py-10 text-sm text-muted-foreground">{t(locale, "dashboard.desc")}</p>
|
||||
</AppLayout>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import { alternatePath, type Locale, t } from "@app/i18n";
|
||||
import HomeBody from "~/components/pages/HomeBody";
|
||||
import AppLayout from "~/layouts/AppLayout.astro";
|
||||
|
||||
export interface Props {
|
||||
|
|
@ -7,49 +8,16 @@ export interface Props {
|
|||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const tr = (key: Parameters<typeof t>[1]) => t(locale, key);
|
||||
const otherLocale: Locale = locale === "zh-CN" ? "en" : "zh-CN";
|
||||
const switchHref = alternatePath(Astro.url.pathname, otherLocale);
|
||||
const dashHref = locale === "zh-CN" ? "/zh-CN/dashboard" : "/en/dashboard";
|
||||
const loginHref = locale === "zh-CN" ? "/zh-CN/auth/login" : "/en/auth/login";
|
||||
---
|
||||
|
||||
<AppLayout
|
||||
title={tr("home.title")}
|
||||
title={t(locale, "home.title")}
|
||||
locale={locale}
|
||||
nav={[]}
|
||||
switchHref={switchHref}
|
||||
switchLabel={t(otherLocale, `locale.${otherLocale}` as never)}
|
||||
>
|
||||
<section class="space-y-10 py-10">
|
||||
<div class="u-reveal">
|
||||
<p class="u-label u-label-edge">{tr("meta.tagline")}</p>
|
||||
<h1 class="mt-3 text-[40px] font-black tracking-tight leading-[1.05]">
|
||||
{tr("home.heroLine1")}<br />{tr("home.heroLine2")}
|
||||
</h1>
|
||||
<span class="u-edge-bar mt-5"></span>
|
||||
<p class="mt-5 max-w-xl text-[14px] leading-relaxed" style="color: var(--ink-2);">
|
||||
{tr("home.subtitle")}
|
||||
</p>
|
||||
<div class="mt-8 flex flex-wrap gap-3">
|
||||
<a href={dashHref} class="u-btn u-btn--edge">{tr("home.ctaDashboard")}</a>
|
||||
<a href={loginHref} class="u-btn u-btn--ghost">{tr("home.ctaLogin")}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<section class="u-card p-6 u-reveal">
|
||||
<p class="u-label">{tr("home.stackTitle")}</p>
|
||||
<p class="mt-3 text-[14px] leading-relaxed" style="color: var(--ink-2);">
|
||||
{tr("home.stackBody")}
|
||||
</p>
|
||||
</section>
|
||||
<section class="u-card p-6 u-reveal">
|
||||
<p class="u-label">{tr("home.layerTitle")}</p>
|
||||
<p class="mt-3 text-[14px] leading-relaxed" style="color: var(--ink-2);">
|
||||
{tr("home.layerBody")}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<HomeBody client:load locale={locale} />
|
||||
</AppLayout>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("中文首页渲染 hero 与 CTA", async ({ page }) => {
|
||||
await page.goto("/zh-CN");
|
||||
const h1 = page.getByRole("heading", { level: 1 });
|
||||
await expect(h1).toContainText("本地片源");
|
||||
await expect(h1).toContainText("在线弹幕");
|
||||
await expect(page.getByRole("link", { name: "登录 / 注册" })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "打开媒体库" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("英文首页渲染英文文案", async ({ page }) => {
|
||||
await page.goto("/en");
|
||||
const h1 = page.getByRole("heading", { level: 1 });
|
||||
await expect(h1).toBeVisible();
|
||||
await expect(h1).not.toContainText("应用");
|
||||
await expect(page.getByRole("link", { name: "登录 / 注册" })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("首页无客户端控制台错误", async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", (err) => errors.push(err.message));
|
||||
await page.goto("/zh-CN");
|
||||
await page.waitForLoadState("networkidle");
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"scripts": {
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "biome check ."
|
||||
"lint": "../../node_modules/.bin/biome check ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.15.29",
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import type { MessageKey } from "./zh-CN.js";
|
|||
|
||||
/** 与 zh-CN 同构;类型强制 key 对齐。 */
|
||||
export const en: Record<MessageKey, string> = {
|
||||
"meta.brand": "App Template",
|
||||
"meta.brand": "DanDan Web",
|
||||
"meta.product": "DanDan Web",
|
||||
"meta.productTagline": "Local files · online danmaku",
|
||||
"meta.tagline": "Astro · tRPC · Drizzle scaffold",
|
||||
"meta.tagline": "Local video · online danmaku",
|
||||
|
||||
"nav.home": "Home",
|
||||
"nav.dashboard": "Dashboard",
|
||||
"nav.dashboard": "Console",
|
||||
"nav.primary": "Primary nav",
|
||||
"nav.brandHome": "Home",
|
||||
"nav.library": "Library",
|
||||
|
|
@ -17,26 +17,26 @@ export const en: Record<MessageKey, string> = {
|
|||
"nav.mounts": "Mounts",
|
||||
"nav.danmaku": "Danmaku settings",
|
||||
|
||||
"home.title": "App Template",
|
||||
"home.title": "DanDan Web",
|
||||
"home.subtitle":
|
||||
"Astro + tRPC + Drizzle monorepo scaffold. Sign in to open the dashboard user-list example.",
|
||||
"home.heroLine1": "App",
|
||||
"home.heroLine2": "Template",
|
||||
"home.ctaDashboard": "Open dashboard",
|
||||
"Mount WebDAV / OpenList libraries, scan into your media library, play with ArtPlayer and online danmaku.",
|
||||
"home.heroLine1": "Local files",
|
||||
"home.heroLine2": "Online danmaku",
|
||||
"home.ctaDashboard": "Open library",
|
||||
"home.ctaLogin": "Sign in / Register",
|
||||
"home.stackTitle": "Stack",
|
||||
"home.stackTitle": "Features",
|
||||
"home.stackBody":
|
||||
"Astro 7 SSR · React 19 islands · tRPC v11 · Drizzle + Turso · Tailwind 4 · Biome · Yarn 4 + Turbo.",
|
||||
"home.layerTitle": "Layering",
|
||||
"WebDAV mounts · Bangumi scraping · ArtPlayer + danmaku · multi-user progress · media dashboard.",
|
||||
"home.layerTitle": "Get started",
|
||||
"home.layerBody":
|
||||
"types → models → db → dao → trpc → web. Copy the user vertical slice for new entities; never skip layers.",
|
||||
"home.footer": "APP TEMPLATE · SSR",
|
||||
"Register → add a WebDAV mount → scan in Browse → play from the Library with danmaku.",
|
||||
"home.footer": "DANDAN WEB · SSR",
|
||||
|
||||
"dashboard.title": "Dashboard",
|
||||
"dashboard.heading": "Users",
|
||||
"dashboard.label": "Users",
|
||||
"dashboard.title": "Console",
|
||||
"dashboard.heading": "Media hub",
|
||||
"dashboard.label": "Console",
|
||||
"dashboard.desc":
|
||||
"Full vertical example: tRPC → service → dao → db (scaffold baseline — replace with your domain).",
|
||||
"Redirecting to the library… If nothing happens, open Library in the sidebar.",
|
||||
|
||||
"library.title": "Library",
|
||||
"library.scan": "Scan mounts",
|
||||
|
|
@ -117,7 +117,7 @@ export const en: Record<MessageKey, string> = {
|
|||
|
||||
"auth.login.title": "Sign in",
|
||||
"auth.login.heading": "Sign in",
|
||||
"auth.login.subtitle": "Continue with email and password",
|
||||
"auth.login.subtitle": "Continue with username and password",
|
||||
"auth.login.submit": "Sign in",
|
||||
"auth.login.noAccount": "No account yet?",
|
||||
"auth.login.toRegister": "Register",
|
||||
|
|
@ -144,8 +144,9 @@ export const en: Record<MessageKey, string> = {
|
|||
"auth.settings.changeHeading": "Change password",
|
||||
"auth.settings.changeSubmit": "Update password",
|
||||
"auth.settings.hint": "Other sessions will be signed out after a password change.",
|
||||
"auth.field.username": "Username",
|
||||
"auth.field.name": "Name",
|
||||
"auth.field.email": "Email",
|
||||
"auth.field.email": "Email (optional, for password reset)",
|
||||
"auth.field.password": "Password",
|
||||
"auth.field.currentPassword": "Current password",
|
||||
"auth.field.newPassword": "New password",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
/**
|
||||
* zh-CN 文案:key 权威源。en.ts 必须与本文件 key 同构。
|
||||
* 展示名与 description 的插值用 {name}。
|
||||
* 只保留脚手架基线文案;业务文案按同样模式追加。
|
||||
*/
|
||||
export const zhCN = {
|
||||
"meta.brand": "应用模板",
|
||||
"meta.brand": "弹弹 Web",
|
||||
"meta.product": "弹弹 Web",
|
||||
"meta.productTagline": "本地片源 · 在线弹幕",
|
||||
"meta.tagline": "Astro · tRPC · Drizzle 脚手架",
|
||||
"meta.tagline": "本地视频 · 在线弹幕",
|
||||
|
||||
"nav.home": "首页",
|
||||
"nav.dashboard": "仪表盘",
|
||||
"nav.dashboard": "控制台",
|
||||
"nav.primary": "主导航",
|
||||
"nav.brandHome": "首页",
|
||||
"nav.library": "媒体库",
|
||||
|
|
@ -19,25 +18,23 @@ export const zhCN = {
|
|||
"nav.mounts": "挂载管理",
|
||||
"nav.danmaku": "弹幕设置",
|
||||
|
||||
"home.title": "应用模板",
|
||||
"home.subtitle": "Astro + tRPC + Drizzle monorepo 脚手架。登录后可进入仪表盘查看用户列表示例。",
|
||||
"home.heroLine1": "应用",
|
||||
"home.heroLine2": "模板",
|
||||
"home.ctaDashboard": "打开仪表盘",
|
||||
"home.title": "弹弹 Web",
|
||||
"home.subtitle": "挂载 WebDAV / OpenList 片源,扫描入库,用 ArtPlayer 播放并加载在线弹幕。",
|
||||
"home.heroLine1": "本地片源",
|
||||
"home.heroLine2": "在线弹幕",
|
||||
"home.ctaDashboard": "打开媒体库",
|
||||
"home.ctaLogin": "登录 / 注册",
|
||||
"home.stackTitle": "技术栈",
|
||||
"home.stackTitle": "能力",
|
||||
"home.stackBody":
|
||||
"Astro 7 SSR · React 19 islands · tRPC v11 · Drizzle + Turso · Tailwind 4 · Biome · Yarn 4 + Turbo。",
|
||||
"home.layerTitle": "分层约定",
|
||||
"home.layerBody":
|
||||
"types → models → db → dao → trpc → web。业务实体按 user 垂直切片复制扩展,禁止跨层引用。",
|
||||
"home.footer": "APP TEMPLATE · SSR",
|
||||
"WebDAV 挂载 · Bangumi 刮削 · ArtPlayer + 弹幕 · 多用户进度 · shadcn 风媒体库。",
|
||||
"home.layerTitle": "快速开始",
|
||||
"home.layerBody": "注册登录 → 挂载管理添加 WebDAV → 文件浏览扫描 → 媒体库播放并看弹幕。",
|
||||
"home.footer": "DANDAN WEB · SSR",
|
||||
|
||||
"dashboard.title": "仪表盘",
|
||||
"dashboard.heading": "用户列表",
|
||||
"dashboard.label": "Users",
|
||||
"dashboard.desc":
|
||||
"tRPC → service → dao → db 的完整垂直示例(脚手架基线,可替换为你的业务实体)。",
|
||||
"dashboard.title": "控制台",
|
||||
"dashboard.heading": "媒体中心",
|
||||
"dashboard.label": "Console",
|
||||
"dashboard.desc": "正在前往媒体库……若未跳转,请手动打开侧栏「媒体库」。",
|
||||
|
||||
"library.title": "媒体库",
|
||||
"library.scan": "扫描挂载",
|
||||
|
|
@ -117,7 +114,7 @@ export const zhCN = {
|
|||
|
||||
"auth.login.title": "登录",
|
||||
"auth.login.heading": "登录",
|
||||
"auth.login.subtitle": "使用邮箱与密码继续",
|
||||
"auth.login.subtitle": "使用用户名与密码继续",
|
||||
"auth.login.submit": "登录",
|
||||
"auth.login.noAccount": "还没有账号?",
|
||||
"auth.login.toRegister": "注册",
|
||||
|
|
@ -144,8 +141,9 @@ export const zhCN = {
|
|||
"auth.settings.changeHeading": "修改密码",
|
||||
"auth.settings.changeSubmit": "更新密码",
|
||||
"auth.settings.hint": "更新密码后其它设备的会话会失效。",
|
||||
"auth.field.username": "用户名",
|
||||
"auth.field.name": "名称",
|
||||
"auth.field.email": "邮箱",
|
||||
"auth.field.email": "邮箱(可选,用于找回密码)",
|
||||
"auth.field.password": "密码",
|
||||
"auth.field.currentPassword": "当前密码",
|
||||
"auth.field.newPassword": "新密码",
|
||||
|
|
|
|||
Loading…
Reference in New Issue