refactor(i18n): 将 useTranslation 重命名为 getTranslation 以更准确描述功能

fix(依赖): 添加缺失的依赖项到 useEffect 钩子中
style(env): 更新 .gitignore 和 .env 文件配置
docs(docker): 更新 docker-compose 和文档中的镜像命名
This commit is contained in:
hex2077
2025-08-25 20:45:28 +08:00
parent f64cd498cf
commit 99fad315d0
24 changed files with 113 additions and 98 deletions

View File

@@ -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推荐

View File

@@ -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:

24
web/.env Normal file
View File

@@ -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=

2
web/.gitignore vendored
View File

@@ -26,7 +26,7 @@ yarn-error.log*
# local env files
.env*.local
.env
.env.production
# vercel
.vercel

View File

@@ -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<Metadata> {
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<Metadata> {
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 (
<div className="bg-gray-50 min-h-screen py-12 sm:py-16">

View File

@@ -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<Metadata> {
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'),

View File

@@ -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<Metadata> {
export type paramsType = Promise<{ lang: string, fileName: string }>;
export async function generateMetadata({ params }: { params: paramsType }): Promise<Metadata> {
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 (
<div className="bg-white text-gray-800 font-sans">
<PodcastContent fileName={decodeURIComponent(fileName)} lang={lang} />
</div>
);
}
}
export default PodcastDetailPage;

View File

@@ -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<Metadata> {
export type paramsType = Promise<{ lang: string }>;
export async function generateMetadata({ params }: { params: paramsType }): Promise<Metadata> {
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 (
<PricingSection lang={lang} />
);

View File

@@ -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<Metadata> {
export async function generateMetadata({ params }: { params: paramsType }): Promise<Metadata> {
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<PrivacyPolicyPageProps> = 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 (
<div className="bg-gray-50 min-h-screen py-12 sm:py-16">
<div className="container mx-auto p-6 md:p-8 max-w-4xl bg-white shadow-lg rounded-lg">

View File

@@ -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<Metadata> {
export type paramsType = Promise<{ lang: string }>;
export async function generateMetadata({ params }: { params: paramsType }): Promise<Metadata> {
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 (
<div className="bg-gray-50 min-h-screen py-12 sm:py-16">
<div className="container mx-auto p-6 md:p-8 max-w-4xl bg-white shadow-lg rounded-lg">

View File

@@ -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;

View File

@@ -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');

View File

@@ -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');
// 验证文件名安全性

View File

@@ -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}`; // 缓存键中包含语言

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 });
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -111,7 +111,7 @@ const AudioPlayer: React.FC<AudioPlayerProps> = ({
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(() => {

View File

@@ -56,7 +56,7 @@ const AudioVisualizer: React.FC<AudioVisualizerProps> = ({
audioContext.close();
}
};
}, [audioElement]);
}, [audioElement, audioContext]); // 添加 audioContext
useEffect(() => {
if (!isPlaying || !analyserRef.current || !dataArrayRef.current || !canvasRef.current) {

View File

@@ -133,7 +133,7 @@ const ConfigSelector: React.FC<ConfigSelectorProps> = ({
useEffect(() => {
loadConfigFiles();
}, []);
}, [loadConfigFiles]); // 添加 loadConfigFiles
// 监听localStorage变化重新加载配置
useEffect(() => {
@@ -151,7 +151,7 @@ const ConfigSelector: React.FC<ConfigSelectorProps> = ({
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('settingsUpdated', handleStorageChange);
};
}, [selectedConfig]);
}, [selectedConfig, loadConfigFiles]); // 添加 loadConfigFiles
const handleConfigSelect = (configFile: string) => {
setSelectedConfig(configFile);

View File

@@ -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);

View File

@@ -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),