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

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