From 99fad315d016542a5dcaa83ae473a117d9361bd1 Mon Sep 17 00:00:00 2001 From: hex2077 Date: Mon, 25 Aug 2025 20:45:28 +0800 Subject: [PATCH] =?UTF-8?q?refactor(i18n):=20=E5=B0=86=20useTranslation=20?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BA=20getTranslation=20?= =?UTF-8?q?=E4=BB=A5=E6=9B=B4=E5=87=86=E7=A1=AE=E6=8F=8F=E8=BF=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(依赖): 添加缺失的依赖项到 useEffect 钩子中 style(env): 更新 .gitignore 和 .env 文件配置 docs(docker): 更新 docker-compose 和文档中的镜像命名 --- DOCKER_USAGE.md | 20 ++++++++-------- docker-compose.yml | 16 +++++-------- web/.env | 24 +++++++++++++++++++ web/.gitignore | 2 +- web/src/app/[lang]/contact/page.tsx | 22 ++++++++++------- web/src/app/[lang]/layout.tsx | 4 ++-- .../app/[lang]/podcast/[fileName]/page.tsx | 21 +++++++--------- web/src/app/[lang]/pricing/page.tsx | 12 ++++++---- web/src/app/[lang]/privacy/page.tsx | 22 +++++------------ web/src/app/[lang]/terms/page.tsx | 12 ++++++---- web/src/app/api/audio-example/route.ts | 4 ++-- web/src/app/api/audio-info/route.ts | 4 ++-- web/src/app/api/audio/route.ts | 4 ++-- web/src/app/api/config/route.ts | 6 ++--- web/src/app/api/generate-podcast/route.ts | 4 ++-- web/src/app/api/podcast-status/route.ts | 4 ++-- web/src/app/api/points/route.ts | 8 +++---- web/src/app/api/points/transactions/route.ts | 4 ++-- web/src/app/api/tts-providers/route.ts | 4 ++-- web/src/components/AudioPlayer.tsx | 2 +- web/src/components/AudioVisualizer.tsx | 2 +- web/src/components/ConfigSelector.tsx | 4 ++-- web/src/components/PodcastContent.tsx | 4 ++-- web/src/i18n/index.ts | 2 +- 24 files changed, 113 insertions(+), 98 deletions(-) create mode 100644 web/.env diff --git a/DOCKER_USAGE.md b/DOCKER_USAGE.md index ad80392..a74d512 100644 --- a/DOCKER_USAGE.md +++ b/DOCKER_USAGE.md @@ -8,27 +8,27 @@ ## 方法一:分别构建和运行 Docker 镜像 -在 `Simple-Podcast-Script` 项目的根目录下执行以下命令来构建 Docker 镜像。 +在项目的根目录下执行以下命令来构建 Docker 镜像。 ### 构建 Docker 镜像 #### 构建 Web 应用镜像 ```bash -docker build -t simple-podcast-web -f Dockerfile-Web . +docker build -t podcast-web -f Dockerfile-Web . ``` -* `-t simple-podcast-web`:为镜像指定一个名称和标签。 +* `-t podcast-web`:为镜像指定一个名称和标签。 * `-f Dockerfile-Web`:指定 Web 应用的 Dockerfile 路径。 * `.`:指定构建上下文的路径,这里是项目的根目录。 #### 构建 Server 应用镜像 ```bash -docker build -t simple-podcast-server -f Dockerfile-Server . +docker build -t podcast-server -f Dockerfile-Server . ``` -* `-t simple-podcast-server`:为镜像指定一个名称和标签。 +* `-t podcast-server`:为镜像指定一个名称和标签。 * `-f Dockerfile-Server`:指定 Server 应用的 Dockerfile 路径。 * `.`:指定构建上下文的路径,这里是项目的根目录。 @@ -39,7 +39,7 @@ docker build -t simple-podcast-server -f Dockerfile-Server . #### 运行 Web 应用容器 ```bash -docker run -d -p 3200:3000 -v /opt/audio:/app/server/output --restart always --name podcast-web simple-podcast-web +docker run -d -p 3200:3000 -v /opt/audio:/app/server/output --restart always --name podcast-web podcast-web ``` #### 命令说明: @@ -50,18 +50,18 @@ docker run -d -p 3200:3000 -v /opt/audio:/app/server/output --restart always --n * `-v /opt/sqlite.db:/app/web/sqlite.db`:将宿主机的 `/opt/sqlite.db` 文件挂载到容器内的 `/app/web/sqlite.db` 文件,用于数据库的持久化存储。 * `--restart always`:设置容器的重启策略,确保容器在意外停止或系统重启后能自动重启。 * `--name podcast-web`:为运行中的容器指定一个名称,方便后续管理。 -* `simple-podcast-web`:指定要运行的 Docker 镜像名称。 +* `podcast-web`:指定要运行的 Docker 镜像名称。 #### 运行 Server 应用容器 ```bash -docker run -d -p 3100:8000 -v /opt/audio:/app/server/output -v /opt/sqlite.db:/app/web/sqlite.db --restart always --name podcast-server simple-podcast-server +docker run -d -p 3100:8000 -v /opt/audio:/app/server/output -v /opt/sqlite.db:/app/web/sqlite.db --restart always --name podcast-server podcast-server ``` 或者,如果您的应用程序需要配置环境变量(例如 `PODCAST_API_SECRET_KEY`),您可以使用 `-e` 参数进行设置: ```bash -docker run -d -p 3100:8000 -v /opt/audio:/app/server/output --restart always --name podcast-server -e PODCAST_API_SECRET_KEY="your-production-api-secret-key" simple-podcast-server +docker run -d -p 3100:8000 -v /opt/audio:/app/server/output --restart always --name podcast-server -e PODCAST_API_SECRET_KEY="your-production-api-secret-key" podcast-server ``` #### 命令说明: @@ -72,7 +72,7 @@ docker run -d -p 3100:8000 -v /opt/audio:/app/server/output --restart always --n * `--restart always`:设置容器的重启策略,确保容器在意外停止或系统重启后能自动重启。 * `--name podcast-server`:为运行中的容器指定一个名称,方便后续管理。 * `-e PODCAST_API_SECRET_KEY="your-production-api-secret-key"`:设置环境变量,将 `"your-production-api-secret-key"` 替换为您的实际密钥。 -* `simple-podcast-server`:指定要运行的 Docker 镜像名称。 +* `podcast-server`:指定要运行的 Docker 镜像名称。 ## 方法二:使用 Docker Compose(推荐) diff --git a/docker-compose.yml b/docker-compose.yml index eaa8cbd..b0fb2f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,34 +1,30 @@ -version: '3.8' +version: '1.0.0' services: web: build: context: . dockerfile: Dockerfile-Web - image: simple-podcast-web + image: podcast-web ports: - "3200:3000" volumes: - - audio-data/output:/app/server/output - - audio-data/sqlite.db:/app/web/sqlite.db + - /opt/audio/output:/app/server/output + - /opt/audio/sqlite.db:/app/web/sqlite.db restart: always depends_on: - server - environment: - - NEXT_PUBLIC_API_URL=http://server:8000 server: build: context: . dockerfile: Dockerfile-Server - image: simple-podcast-server + image: podcast-server ports: - "3100:8000" volumes: - - audio-data/output:/app/server/output + - /opt/audio/output:/app/server/output restart: always - environment: - - PODCAST_API_SECRET_KEY=your-production-api-secret-key volumes: audio-data: \ No newline at end of file diff --git a/web/.env b/web/.env new file mode 100644 index 0000000..ceec7e5 --- /dev/null +++ b/web/.env @@ -0,0 +1,24 @@ +# 应用配置 +NEXT_PUBLIC_APP_NAME=PodcastHub +NEXT_PUBLIC_APP_VERSION=1.0.0 +NEXT_PUBLIC_BASE_URL=http://localhost:3000 +# 开发模式配置 +NODE_ENV=development + +NEXT_PUBLIC_PODCAST_API_BASE_URL=http://localhost:8000 +NEXT_PUBLIC_PODCAST_CALLBACK_URL=http://localhost:3000/api/points +NEXT_PUBLIC_ENABLE_TTS_CONFIG_PAGE=true +POINTS_PER_PODCAST=20 +POINTS_PER_PODCAST_INIT=100 +POINTS_PER_SIGN_IN=40 +ALLOWED_USER_IDS="123456890" + +TTS_PROVIDERS_NAME="tts_providers.json" +DB_FILE_NAME="file:sqlite.db" +BETTER_AUTH_SECRET=qwertyuiop +BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app. + +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +GITHUB_ID= +GITHUB_SECRET= \ No newline at end of file diff --git a/web/.gitignore b/web/.gitignore index 9d55bd4..0465c0b 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -26,7 +26,7 @@ yarn-error.log* # local env files .env*.local -.env +.env.production # vercel .vercel diff --git a/web/src/app/[lang]/contact/page.tsx b/web/src/app/[lang]/contact/page.tsx index 28b98a2..7de3c0f 100644 --- a/web/src/app/[lang]/contact/page.tsx +++ b/web/src/app/[lang]/contact/page.tsx @@ -1,13 +1,17 @@ -import React, { use } from 'react'; +import React from 'react'; // 不再需要 use import { Metadata } from 'next'; import { AiOutlineTikTok, AiFillQqCircle, AiOutlineGithub, AiOutlineTwitter, AiFillMail } from 'react-icons/ai'; import { headers } from 'next/headers'; import { getTruePathFromHeaders } from '../../../lib/utils'; +import { getTranslation } from '../../../i18n'; -export async function generateMetadata({ params }: { params: { lang: string } }): Promise { - const { lang } = await params; - const { t } = await (await import('../../../i18n')).useTranslation(lang, 'contact'); - const truePath = await getTruePathFromHeaders(await headers(), lang); +export type paramsType = Promise<{ lang: string }>; + +// 您的 generateMetadata 函数是完全正确的,无需改动 +export async function generateMetadata({ params }: { params: paramsType }): Promise { + const { lang } = await params; // 直接解构 + const { t } = await getTranslation(lang, 'contact'); + const truePath = await getTruePathFromHeaders(await headers(), lang); // 直接调用 headers() return { title: t('contact_us_title'), description: t('contact_us_description'), @@ -17,16 +21,16 @@ export async function generateMetadata({ params }: { params: { lang: string } }) }; } -import { useTranslation } from '../../../i18n'; // 导入服务端的 useTranslation - /** * 联系我们页面组件。 * 优化后的版本,移除了联系表单,专注于清晰地展示联系方式。 * 采用单栏居中布局,设计简洁、现代。 */ -const ContactUsPage = async ({ params }: { params: { lang: string } }) => { +// 1. 将 props 类型更正为普通对象 +const ContactUsPage: React.FC<{ params: paramsType}> = async ({ params }) => { + // 2. 直接从 params 对象中解构 lang,移除 use() const { lang } = await params; - const { t } = await useTranslation(lang, 'contact'); + const { t } = await getTranslation(lang, 'contact'); return (
diff --git a/web/src/app/[lang]/layout.tsx b/web/src/app/[lang]/layout.tsx index 6157a4b..8170cb4 100644 --- a/web/src/app/[lang]/layout.tsx +++ b/web/src/app/[lang]/layout.tsx @@ -4,7 +4,7 @@ import './globals.css'; import FooterLinks from '../../components/FooterLinks'; import { dir } from 'i18next'; import { languages } from '../../i18n/settings'; -import { useTranslation } from '../../i18n'; +import { getTranslation } from '../../i18n'; import { headers } from 'next/headers'; import { getTruePathFromHeaders } from '../../lib/utils'; @@ -17,7 +17,7 @@ const inter = Inter({ export async function generateMetadata({ params }: { params: { lang: string } }): Promise { const { lang } = await params; - const { t } = await useTranslation(lang, 'layout'); + const { t } = await getTranslation(lang, 'layout'); const truePath = await getTruePathFromHeaders(await headers(), lang); return { metadataBase: new URL('https://www.podcasthub.com'), diff --git a/web/src/app/[lang]/podcast/[fileName]/page.tsx b/web/src/app/[lang]/podcast/[fileName]/page.tsx index 19ec6f2..0f22f65 100644 --- a/web/src/app/[lang]/podcast/[fileName]/page.tsx +++ b/web/src/app/[lang]/podcast/[fileName]/page.tsx @@ -1,12 +1,14 @@ import { Metadata } from 'next'; import PodcastContent from '@/components/PodcastContent'; -import { useTranslation } from '../../../../i18n'; // 导入 useTranslation +import { getTranslation } from '../../../../i18n'; // 导入 getTranslation import { headers } from 'next/headers'; import { getTruePathFromHeaders } from '../../../../lib/utils'; -export async function generateMetadata({ params }: PodcastDetailPageProps): Promise { +export type paramsType = Promise<{ lang: string, fileName: string }>; + +export async function generateMetadata({ params }: { params: paramsType }): Promise { const { fileName, lang } = await params; - const { t } = await useTranslation(lang); + const { t } = await getTranslation(lang); const decodedFileName = decodeURIComponent(fileName); const title = `${t('podcastContent.podcastDetails')} - ${decodedFileName}`; const description = `${t('podcastContent.listenToPodcast')} ${decodedFileName}。`; @@ -20,18 +22,13 @@ export async function generateMetadata({ params }: PodcastDetailPageProps): Prom }; } -interface PodcastDetailPageProps { - params: { - fileName: string; - lang: string; // 添加 lang 属性 - }; -} - -export default async function PodcastDetailPage({ params }: PodcastDetailPageProps) { +const PodcastDetailPage: React.FC<{ params: paramsType}> = async ({ params }) => { const { fileName, lang } = await params; // 解构 lang return (
); -} \ No newline at end of file +} + +export default PodcastDetailPage; \ No newline at end of file diff --git a/web/src/app/[lang]/pricing/page.tsx b/web/src/app/[lang]/pricing/page.tsx index 783fc5f..490d0e3 100644 --- a/web/src/app/[lang]/pricing/page.tsx +++ b/web/src/app/[lang]/pricing/page.tsx @@ -1,13 +1,15 @@ import { Metadata } from 'next'; import React, { use } from 'react'; import PricingSection from '@/components/PricingSection'; // 导入 PricingSection 组件 -import { useTranslation } from '../../../i18n'; +import { getTranslation } from '../../../i18n'; import { headers } from 'next/headers'; import { getTruePathFromHeaders } from '../../../lib/utils'; -export async function generateMetadata({ params }: { params: { lang: string } }): Promise { +export type paramsType = Promise<{ lang: string }>; + +export async function generateMetadata({ params }: { params: paramsType }): Promise { const { lang } = await params; - const { t } = await (await import('../../../i18n')).useTranslation(lang, 'components'); + const { t } = await (await import('../../../i18n')).getTranslation(lang, 'components'); const truePath = await getTruePathFromHeaders(await headers(), lang); return { title: t('pricing_page_title'), @@ -18,10 +20,10 @@ export async function generateMetadata({ params }: { params: { lang: string } }) }; } -const PricingPage = async ({ params }: { params: { lang: string } }) => { +const PricingPage: React.FC<{ params: paramsType}> = async ({ params }) => { const { lang } = await params; // 尽管 PricingSection 是客户端组件,为了使 PricingPage 成为服务器组件并加载服务端 i18n,我们在这里模拟加载 - await useTranslation(lang, 'components'); + await getTranslation(lang, 'components'); return ( ); diff --git a/web/src/app/[lang]/privacy/page.tsx b/web/src/app/[lang]/privacy/page.tsx index 1fee4af..fbfc440 100644 --- a/web/src/app/[lang]/privacy/page.tsx +++ b/web/src/app/[lang]/privacy/page.tsx @@ -1,15 +1,16 @@ import React from 'react'; import { Metadata } from 'next'; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { headers } from 'next/headers'; import { getTruePathFromHeaders } from '../../../lib/utils'; +export type paramsType = Promise<{ lang: string }>; /** * 设置页面元数据。 */ -export async function generateMetadata({ params }: { params: { lang: string } }): Promise { +export async function generateMetadata({ params }: { params: paramsType }): Promise { const { lang } = await params; - const { t } = await useTranslation(lang, 'privacy'); + const { t } = await getTranslation(lang, 'privacy'); const truePath = await getTruePathFromHeaders(await headers(), lang); return { title: t('privacy_policy.title'), @@ -20,20 +21,9 @@ export async function generateMetadata({ params }: { params: { lang: string } }) }; } -/** - * 隐私政策页面组件。 - * 提供了详细的隐私政策说明,涵盖信息收集、使用、共享、安全及用户权利。 - * 布局采用 Tailwind CSS 进行优化,`prose` 类用于美化排版,`break-words` 确保内容不会溢出容器。 - */ -type PrivacyPolicyPageProps = { - params: { - lang: string; - }; -}; - -const PrivacyPolicyPage: React.FC = async ({ params }) => { +const PrivacyPolicyPage: React.FC<{ params: paramsType}> = async ({ params }) => { const { lang } = await params; - const { t } = await useTranslation(lang, 'privacy'); + const { t } = await getTranslation(lang, 'privacy'); return (
diff --git a/web/src/app/[lang]/terms/page.tsx b/web/src/app/[lang]/terms/page.tsx index f5ca07f..2809b82 100644 --- a/web/src/app/[lang]/terms/page.tsx +++ b/web/src/app/[lang]/terms/page.tsx @@ -1,13 +1,15 @@ import React from 'react'; import { Metadata } from 'next'; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { languages } from '@/i18n/settings'; import { headers } from 'next/headers'; import { getTruePathFromHeaders } from '../../../lib/utils'; -export async function generateMetadata({ params }: { params: { lang: string } }): Promise { +export type paramsType = Promise<{ lang: string }>; + +export async function generateMetadata({ params }: { params: paramsType }): Promise { const { lang } = await params; - const { t } = await useTranslation(lang, 'terms'); + const { t } = await getTranslation(lang, 'terms'); const truePath = await getTruePathFromHeaders(await headers(), lang); return { title: t('terms_of_service.title'), @@ -18,9 +20,9 @@ export async function generateMetadata({ params }: { params: { lang: string } }) }; } -const TermsOfServicePage: React.FC<{ params: { lang: string } }> = async ({ params }) => { +const TermsOfServicePage: React.FC<{ params: paramsType }> = async ({ params }) => { const { lang } = await params; - const { t } = await useTranslation(lang, 'terms'); + const { t } = await getTranslation(lang, 'terms'); return (
diff --git a/web/src/app/api/audio-example/route.ts b/web/src/app/api/audio-example/route.ts index 13de251..ca52373 100644 --- a/web/src/app/api/audio-example/route.ts +++ b/web/src/app/api/audio-example/route.ts @@ -1,11 +1,11 @@ import { NextRequest, NextResponse } from 'next/server'; import { getLanguageFromRequest } from '@/lib/utils'; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { fetchAndCacheProvidersLocal } from '@/lib/config-local'; export async function GET(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); + const { t } = await getTranslation(lang, 'errors'); // 获取查询参数 const searchParams = request.nextUrl.searchParams; diff --git a/web/src/app/api/audio-info/route.ts b/web/src/app/api/audio-info/route.ts index 26515a5..529cbd3 100644 --- a/web/src/app/api/audio-info/route.ts +++ b/web/src/app/api/audio-info/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { getAudioInfo, getUserInfo } from '@/lib/podcastApi'; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { getLanguageFromRequest } from '@/lib/utils'; @@ -10,7 +10,7 @@ import { getLanguageFromRequest } from '@/lib/utils'; */ export async function GET(req: NextRequest) { const lang = getLanguageFromRequest(req); - const { t } = await useTranslation(lang, 'errors'); + const { t } = await getTranslation(lang, 'errors'); // 从请求 URL 中获取查询参数 const { searchParams } = new URL(req.url); const fileName = searchParams.get('file_name'); diff --git a/web/src/app/api/audio/route.ts b/web/src/app/api/audio/route.ts index 39a934e..5d88870 100644 --- a/web/src/app/api/audio/route.ts +++ b/web/src/app/api/audio/route.ts @@ -1,12 +1,12 @@ import { NextRequest, NextResponse } from 'next/server'; import { getLanguageFromRequest } from '@/lib/utils'; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import path from 'path'; import fs from 'fs'; export async function GET(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); + const { t } = await getTranslation(lang, 'errors'); const filename = request.nextUrl.searchParams.get('filename'); // 验证文件名安全性 diff --git a/web/src/app/api/config/route.ts b/web/src/app/api/config/route.ts index d4e04b7..66613b6 100644 --- a/web/src/app/api/config/route.ts +++ b/web/src/app/api/config/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import path from 'path'; import fs from 'fs/promises'; import type { TTSConfig } from '@/types'; -import { useTranslation } from '@/i18n'; // 导入 useTranslation +import { getTranslation } from '@/i18n'; // 导入 getTranslation import { getLanguageFromRequest } from '@/lib/utils'; // 缓存对象,存储响应数据和时间戳 @@ -38,7 +38,7 @@ const TTS_PROVIDER_ORDER = [ // 获取配置文件列表 export async function GET(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); // 加载 'errors' 命名空间的翻译 + const { t } = await getTranslation(lang, 'errors'); // 加载 'errors' 命名空间的翻译 const cacheKey = `config_files_list_${lang}`; // 缓存键中包含语言 const cachedData = getCache(cacheKey); @@ -97,7 +97,7 @@ export async function GET(request: NextRequest) { // 获取特定配置文件内容 export async function POST(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); // 加载 'errors' 命名空间的翻译 + const { t } = await getTranslation(lang, 'errors'); // 加载 'errors' 命名空间的翻译 const { configFile } = await request.json(); const cacheKey = `config_file_${configFile}_${lang}`; // 缓存键中包含语言 diff --git a/web/src/app/api/generate-podcast/route.ts b/web/src/app/api/generate-podcast/route.ts index 3f3488f..ca99011 100644 --- a/web/src/app/api/generate-podcast/route.ts +++ b/web/src/app/api/generate-podcast/route.ts @@ -4,7 +4,7 @@ import type { PodcastGenerationRequest } from '@/types'; // 导入 SettingsFormD import { getSessionData } from '@/lib/server-actions'; import { getUserPoints } from '@/lib/points'; // 导入 getUserPoints import { fetchAndCacheProvidersLocal } from '@/lib/config-local'; // 导入 getTTSProviders -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { getLanguageFromRequest } from '@/lib/utils'; @@ -12,7 +12,7 @@ const enableTTSConfigPage = process.env.NEXT_PUBLIC_ENABLE_TTS_CONFIG_PAGE === ' export async function POST(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); + const { t } = await getTranslation(lang, 'errors'); const session = await getSessionData(); const userId = session.user?.id; diff --git a/web/src/app/api/podcast-status/route.ts b/web/src/app/api/podcast-status/route.ts index 7d3e267..13079f4 100644 --- a/web/src/app/api/podcast-status/route.ts +++ b/web/src/app/api/podcast-status/route.ts @@ -1,14 +1,14 @@ import { NextRequest, NextResponse } from 'next/server'; import { getPodcastStatus } from '@/lib/podcastApi'; import { getSessionData } from '@/lib/server-actions'; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { getLanguageFromRequest } from '@/lib/utils'; export const revalidate = 0; // 等同于 `cache: 'no-store'` export async function GET(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); + const { t } = await getTranslation(lang, 'errors'); const session = await getSessionData(); const userId = session.user?.id; diff --git a/web/src/app/api/points/route.ts b/web/src/app/api/points/route.ts index a3ff8fa..69f43fa 100644 --- a/web/src/app/api/points/route.ts +++ b/web/src/app/api/points/route.ts @@ -1,13 +1,13 @@ import { getUserPoints, deductUserPoints, addPointsToUser, hasUserSignedToday } from "@/lib/points"; // 导入 deductUserPoints, addPointsToUser, hasUserSignedToday import { NextResponse, NextRequest } from "next/server"; // 导入 NextRequest import { getSessionData } from "@/lib/server-actions"; // 导入 getSessionData -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { getLanguageFromRequest } from '@/lib/utils'; // 导入 getLanguageFromRequest export async function GET(request: NextRequest) { // GET 函数接收 request const session = await getSessionData(); // 使用 getSessionData 获取 session const lang = getLanguageFromRequest(request); // 获取语言 - const { t } = await useTranslation(lang, 'errors'); // 初始化翻译 + const { t } = await getTranslation(lang, 'errors'); // 初始化翻译 if (!session || !session.user || !session.user.id) { return NextResponse.json({ success: false, error: t("unauthorized") }, { status: 401 }); } @@ -32,7 +32,7 @@ export async function GET(request: NextRequest) { // GET 函数接收 request export async function PUT(request: NextRequest) { const { task_id, auth_id, timestamp, status } = await request.json(); const lang = getLanguageFromRequest(request); // 获取语言 - const { t } = await useTranslation(lang, 'errors'); // 初始化翻译 + const { t } = await getTranslation(lang, 'errors'); // 初始化翻译 try { if(status !== 'completed') { return NextResponse.json({ success: false, error: t("invalid_status") }, { status: 400 }); @@ -89,7 +89,7 @@ export async function POST(request: NextRequest) { const session = await getSessionData(); const lang = getLanguageFromRequest(request); // 获取语言 console.log(lang) - const { t } = await useTranslation(lang, 'errors'); // 初始化翻译 + const { t } = await getTranslation(lang, 'errors'); // 初始化翻译 if (!session || !session.user || !session.user.id) { return NextResponse.json({ success: false, error: t("unauthorized") }, { status: 401 }); } diff --git a/web/src/app/api/points/transactions/route.ts b/web/src/app/api/points/transactions/route.ts index ce633d4..1b5b9e3 100644 --- a/web/src/app/api/points/transactions/route.ts +++ b/web/src/app/api/points/transactions/route.ts @@ -1,12 +1,12 @@ import { getUserPointsTransactions } from "@/lib/points"; import { NextResponse, NextRequest } from "next/server"; import { getSessionData } from "@/lib/server-actions"; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { getLanguageFromRequest } from '@/lib/utils'; export async function GET(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); + const { t } = await getTranslation(lang, 'errors'); const session = await getSessionData(); if (!session || !session.user || !session.user.id) { diff --git a/web/src/app/api/tts-providers/route.ts b/web/src/app/api/tts-providers/route.ts index 1e119cf..9e6ebab 100644 --- a/web/src/app/api/tts-providers/route.ts +++ b/web/src/app/api/tts-providers/route.ts @@ -1,13 +1,13 @@ import { NextRequest, NextResponse } from 'next/server'; import { fetchAndCacheProvidersLocal } from '@/lib/config-local'; -import { useTranslation } from '@/i18n'; +import { getTranslation } from '@/i18n'; import { getLanguageFromRequest } from '@/lib/utils'; // 获取 tts_providers.json 文件内容 export async function GET(request: NextRequest) { const lang = getLanguageFromRequest(request); - const { t } = await useTranslation(lang, 'errors'); + const { t } = await getTranslation(lang, 'errors'); try { const config = await fetchAndCacheProvidersLocal(lang); diff --git a/web/src/components/AudioPlayer.tsx b/web/src/components/AudioPlayer.tsx index ad2750a..0a0b0f2 100644 --- a/web/src/components/AudioPlayer.tsx +++ b/web/src/components/AudioPlayer.tsx @@ -111,7 +111,7 @@ const AudioPlayer: React.FC = ({ audio.removeEventListener('loadstart', handleLoadStart); audio.removeEventListener('canplay', handleCanPlay); }; - }, [isPlaying, podcast.audioUrl, canPlay]); // 将 canPlay 加入依赖,确保状态变化时触发播放 + }, [isPlaying, podcast.audioUrl, canPlay, onEnded, currentPlaybackRate]); // 添加 onEnded 和 currentPlaybackRate // 当播客URL变化时,更新audio元素的src useEffect(() => { diff --git a/web/src/components/AudioVisualizer.tsx b/web/src/components/AudioVisualizer.tsx index ce1e02e..1f4174f 100644 --- a/web/src/components/AudioVisualizer.tsx +++ b/web/src/components/AudioVisualizer.tsx @@ -56,7 +56,7 @@ const AudioVisualizer: React.FC = ({ audioContext.close(); } }; - }, [audioElement]); + }, [audioElement, audioContext]); // 添加 audioContext useEffect(() => { if (!isPlaying || !analyserRef.current || !dataArrayRef.current || !canvasRef.current) { diff --git a/web/src/components/ConfigSelector.tsx b/web/src/components/ConfigSelector.tsx index ebe584b..85ce725 100644 --- a/web/src/components/ConfigSelector.tsx +++ b/web/src/components/ConfigSelector.tsx @@ -133,7 +133,7 @@ const ConfigSelector: React.FC = ({ useEffect(() => { loadConfigFiles(); - }, []); + }, [loadConfigFiles]); // 添加 loadConfigFiles // 监听localStorage变化,重新加载配置 useEffect(() => { @@ -151,7 +151,7 @@ const ConfigSelector: React.FC = ({ window.removeEventListener('storage', handleStorageChange); window.removeEventListener('settingsUpdated', handleStorageChange); }; - }, [selectedConfig]); + }, [selectedConfig, loadConfigFiles]); // 添加 loadConfigFiles const handleConfigSelect = (configFile: string) => { setSelectedConfig(configFile); diff --git a/web/src/components/PodcastContent.tsx b/web/src/components/PodcastContent.tsx index c7f40b8..7d168f8 100644 --- a/web/src/components/PodcastContent.tsx +++ b/web/src/components/PodcastContent.tsx @@ -3,7 +3,7 @@ import { getAudioInfo, getUserInfo } from '@/lib/podcastApi'; import AudioPlayerControls from './AudioPlayerControls'; import PodcastTabs from './PodcastTabs'; import ShareButton from './ShareButton'; // 导入 ShareButton 组件 -import { useTranslation } from '../i18n'; // 从正确路径导入 useTranslation +import { getTranslation } from '../i18n'; // 从正确路径导入 useTranslation import { headers } from 'next/headers'; // 导入 usePathname import { getTruePathFromHeaders } from '../lib/utils'; // 导入新函数 @@ -33,7 +33,7 @@ interface PodcastContentProps { export default async function PodcastContent({ fileName, lang }: PodcastContentProps) { - const { t } = await useTranslation(lang, 'components'); // 初始化 useTranslation + const { t } = await getTranslation(lang, 'components'); // 初始化 getTranslation const result = await getAudioInfo(fileName, lang); const truePath = await getTruePathFromHeaders(await headers(), lang); diff --git a/web/src/i18n/index.ts b/web/src/i18n/index.ts index 5d2fade..afa5fab 100644 --- a/web/src/i18n/index.ts +++ b/web/src/i18n/index.ts @@ -13,7 +13,7 @@ const initI18next = async (lng: string, ns: string | string[] | undefined): Prom return i18nInstance } -export async function useTranslation(lng: string, ns: string | string[] | undefined = 'common', options: { keyPrefix?: string } = {}) { +export async function getTranslation(lng: string, ns: string | string[] | undefined = 'common', options: { keyPrefix?: string } = {}) { const i18nextInstance = await initI18next(lng, ns) return { t: i18nextInstance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns, options.keyPrefix),