From c2c31227a79e4db61128a7b8d1c9a5fa8f6886be Mon Sep 17 00:00:00 2001 From: hex2077 Date: Tue, 21 Oct 2025 14:59:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E8=AF=B4=E6=98=8E=E5=92=8C=E5=B8=B8=E8=A7=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E6=A8=A1=E5=9D=97=E5=B9=B6=E4=BC=98=E5=8C=96SEO?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增HowToUse和FAQ组件,提供多语言使用指南和常见问题解答 - 优化播客详情页SEO,使用overview_content生成动态标题和描述 - 简化页面元数据配置,更新网站域名和SEO文案 - 更新联系邮箱地址 - 完善中英日三语的多语言配置文件 --- web/public/locales/en/home.json | 44 +++++++ web/public/locales/en/layout.json | 4 +- web/public/locales/ja/home.json | 44 +++++++ web/public/locales/ja/layout.json | 4 +- web/public/locales/zh-CN/home.json | 48 ++++++++ web/src/app/[lang]/contact/page.tsx | 2 +- web/src/app/[lang]/layout.tsx | 4 +- web/src/app/[lang]/page.tsx | 8 ++ .../app/[lang]/podcast/[fileName]/layout.tsx | 62 ++++++++++ .../app/[lang]/podcast/[fileName]/page.tsx | 20 ---- web/src/components/FAQ.tsx | 108 ++++++++++++++++++ web/src/components/HowToUse.tsx | 93 +++++++++++++++ 12 files changed, 414 insertions(+), 27 deletions(-) create mode 100644 web/src/app/[lang]/podcast/[fileName]/layout.tsx create mode 100644 web/src/components/FAQ.tsx create mode 100644 web/src/components/HowToUse.tsx diff --git a/web/public/locales/en/home.json b/web/public/locales/en/home.json index a9ed265..9a6be48 100644 --- a/web/public/locales/en/home.json +++ b/web/public/locales/en/home.json @@ -18,6 +18,50 @@ "taskCreatedTitle": "Task Created", "taskCreatedMessage": "Podcast generation task has been started, Task ID", "recentlyGenerated": "Recently Generated", + "howToUse": "How to Use", + "howToUseSubtitle": "Get started with PodcastHub and create professional podcasts easily", + "faq": "FAQ", + "faqSubtitle": "Answers to common questions about using our service", + "howToUseSteps": { + "step1": { + "title": "1. Input Content", + "description": "Enter the text you want to convert into a podcast in the text box. It can be articles, notes, or any text. Use story generator feature to quickly generate automatic podcast content." + }, + "step2": { + "title": "2. Select Mode", + "description": "Choose between standard mode or AI story mode. Our podcast generator helps you create more engaging podcast scripts." + }, + "step3": { + "title": "3. Configure Voice", + "description": "Select the appropriate voice role to customize your podcast audio." + }, + "step4": { + "title": "4. Generate Podcast", + "description": "Click the generate button, and PodcastHub will automatically create professional podcast audio for you." + } + }, + "faqItems": { + "q1": { + "question": "What is PodcastHub?", + "answer": "PodcastHub is an intelligent podcast generation platform that quickly converts your text content into professional podcast audio. It supports multiple voices and AI generation modes." + }, + "q2": { + "question": "How to use the story generator feature?", + "answer": "When selecting AI story mode, the system automatically uses the immersive story feature to create vivid AI voice-over scripts." + }, + "q3": { + "question": "What languages are supported?", + "answer": "PodcastHub supports multiple languages including Chinese, English, Japanese, and more. You can choose the corresponding voice role based on your content." + }, + "q4": { + "question": "How long are generated podcasts saved?", + "answer": "To protect your privacy and save storage space, generated podcast audio is retained for 30 minutes. Please download and save it locally in time." + }, + "q5": { + "question": "How to get more credits?", + "answer": "You can purchase credit packages or sign up for free trial credits to get more generation attempts. New users receive free trial credits upon registration." + } + }, "dataRetentionWarning": "Data is only kept for 30 minutes, please download and save it as soon as possible", "saveSuccessTitle": "Save successfully", "saveErrorTitle": "Save failed", diff --git a/web/public/locales/en/layout.json b/web/public/locales/en/layout.json index 71e6a3e..edb4c3f 100644 --- a/web/public/locales/en/layout.json +++ b/web/public/locales/en/layout.json @@ -1,5 +1,5 @@ { - "title": "PodcastHub: Your AI Podcast creation platform - easily convert text into high-quality podcast audio, supporting multiple voices and styles, making creativity accessible.", - "description": "PodcastHub uses cutting-edge AI technology to provide unlimited possibilities for your creativity. Easily convert text and ideas into professional-quality podcast audio, with a variety of personalized voice and style options. Experience efficient creation now, spread your voice globally, attract more listeners, and simplify your podcast production process.", + "title": "PodcastHub: AI Podcast Creator - Text to Audio", + "description": "Transform text into professional podcast audio with AI. Multiple voices, styles & languages. Create engaging podcasts effortlessly. Start free today!", "keywords": "podcast,AI,voice synthesis,TTS,audio generation" } \ No newline at end of file diff --git a/web/public/locales/ja/home.json b/web/public/locales/ja/home.json index b2d63c1..452ddf9 100644 --- a/web/public/locales/ja/home.json +++ b/web/public/locales/ja/home.json @@ -18,6 +18,50 @@ "taskCreatedTitle": "タスク作成済み", "taskCreatedMessage": "ポッドキャスト生成タスクが開始されました。タスクID", "recentlyGenerated": "最近生成されたもの", + "howToUse": "使い方", + "howToUseSubtitle": "PodcastHubで簡単にプロフェッショナルなポッドキャストを作成", + "faq": "よくある質問", + "faqSubtitle": "サービス利用に関するよくある質問への回答", + "howToUseSteps": { + "step1": { + "title": "1. コンテンツを入力", + "description": "テキストボックスにポッドキャストに変換したいコンテンツを入力します。記事、メモ、または任意のテキストが使用できます。story generator機能を使用して素早く自動的にコンテンツを生成できます。" + }, + "step2": { + "title": "2. モードを選択", + "description": "標準モードまたはAIストーリーモードを選択します。podcast generatorを使用すると、より魅力的なポッドキャストスクリプトを作成できます。" + }, + "step3": { + "title": "3. 音声を設定", + "description": "適切な音声ロールを選択して、ポッドキャストの音声をカスタマイズします。" + }, + "step4": { + "title": "4. ポッドキャストを生成", + "description": "生成ボタンをクリックすると、PodcastHubが自動的にプロフェッショナルなポッドキャストオーディオを作成します。" + } + }, + "faqItems": { + "q1": { + "question": "PodcastHubとは何ですか?", + "answer": "PodcastHubは、テキストコンテンツを素早くプロフェッショナルなポッドキャストオーディオに変換するインテリジェントなポッドキャスト生成プラットフォームです。複数の音声とAI生成モードをサポートしています。" + }, + "q2": { + "question": "story generator機能の使い方は?", + "answer": "AIストーリーモードを選択すると、システムは自動的に没入型ストーリー機能を使用して、生き生きとしたAI音声スクリプトを作成します。" + }, + "q3": { + "question": "どの言語がサポートされていますか?", + "answer": "PodcastHubは中国語、英語、日本語など、複数の言語をサポートしています。コンテンツに応じて適切な音声ロールを選択できます。" + }, + "q4": { + "question": "生成されたポッドキャストはどのくらい保存されますか?", + "answer": "プライバシーを保護し、ストレージスペースを節約するため、生成されたポッドキャストオーディオは30分間保持されます。時間内にダウンロードしてローカルに保存してください。" + }, + "q5": { + "question": "より多くのクレジットを取得するには?", + "answer": "クレジットパッケージを購入することで、より多くの生成回数を取得できます。また、签到や無料体験クレジットを利用することもできます。新規ユーザーは登録時に無料体験クレジットを受け取ります。" + } + }, "dataRetentionWarning": "データは30分間のみ保持されます。できるだけ早くダウンロードして保存してください。", "saveSuccessTitle": "保存成功", "saveErrorTitle": "保存失敗", diff --git a/web/public/locales/ja/layout.json b/web/public/locales/ja/layout.json index 032cca2..505fb25 100644 --- a/web/public/locales/ja/layout.json +++ b/web/public/locales/ja/layout.json @@ -1,5 +1,5 @@ { - "title": "PodcastHub:あなたのAIポッドキャスト作成プラットフォーム - テキストを高品質なポッドキャストオーディオに簡単に変換、複数の声とスタイルをサポートし、創造性を手軽に実現。", - "description": "PodcastHubは最先端のAI技術を駆使し、あなたの創造性に無限の可能性を提供します。テキストやアイデアをプロ品質のポッドキャストオーディオに簡単に変換し、多様なパーソナライズされた声とスタイルのオプションを提供します。今すぐ効率的な作成を体験し、あなたの声を世界に広め、より多くのリスナーを引きつけ、ポッドキャスト制作プロセスを簡素化します。", + "title": "PodcastHub: AIポッドキャスト作成 - テキストを音声に変換", + "description": "AIでテキストをプロ品質のポッドキャスト音声に変換。多様な声とスタイルで魅力的なコンテンツを簡単作成。今すぐ無料で始めよう!", "keywords": "ポッドキャスト,AI,音声合成,TTS,オーディオ生成" } \ No newline at end of file diff --git a/web/public/locales/zh-CN/home.json b/web/public/locales/zh-CN/home.json index 854d1cf..1086a05 100644 --- a/web/public/locales/zh-CN/home.json +++ b/web/public/locales/zh-CN/home.json @@ -18,6 +18,54 @@ "taskCreatedTitle": "任务已创建", "taskCreatedMessage": "播客生成任务已启动,任务ID", "recentlyGenerated": "最近生成", + "howToUse": "使用说明", + "howToUseSubtitle": "快速上手 PodcastHub,轻松生成专业播客", + "faq": "常见问题", + "faqSubtitle": "解答您在使用过程中可能遇到的问题", + "howToUseSteps": { + "step1": { + "title": "1. 输入内容", + "description": "在文本框中输入您想要转换为播客的内容,可以是文章、笔记或任何文本。支持使用沉浸故事功能快速生成自动配音内容。" + }, + "step2": { + "title": "2. 选择模式", + "description": "选择AI播客模式或沉浸故事模式。使用我们的AI播客生成器可以帮助您创建更有吸引力的播客脚本。" + }, + "step3": { + "title": "3. 配置语音", + "description": "选择合适的语音角色,自定义您的播客音色。" + }, + "step4": { + "title": "4. 生成播客", + "description": "点击生成按钮,PodcastHub 将自动为您创建专业的播客音频。" + } + }, + "faqItems": { + "q1": { + "question": "什么是 PodcastHub?", + "answer": "PodcastHub 是一个智能播客生成平台,可以将您的文本内容快速转换为专业的播客音频。支持多种语音和AI生成模式。" + }, + "q2": { + "question": "如何使用沉浸故事功能?", + "answer": "选择AI故事模式时,系统会自动使用沉浸故事功能来创建生动的AI配音脚本。" + }, + "q3": { + "question": "支持哪些语言?", + "answer": "PodcastHub 支持中文、英文、日文等多种语言。您可以根据内容选择相应的语音角色。" + }, + "q4": { + "question": "生成的播客可以保存多久?", + "answer": "为了保护您的隐私和节省存储空间,生成的播客音频会保留30分钟。请及时下载保存到本地。" + }, + "q5": { + "question": "如何获取更多积分?", + "answer": "您可以通过签到或购买积分套餐来获取更多生成次数。新用户注册即可获得免费体验积分。" + }, + "q6": { + "question": "什么是AI播客生成器?", + "answer": "AI播客生成器是我们的智能播客创作工具,可以帮助您创建更专业、更有吸引力的播客内容。结合沉浸故事功能,在AI故事模式下自动启用,让您的播客更具感染力。" + } + }, "dataRetentionWarning": "数据只保留30分钟,请尽快下载保存", "saveSuccessTitle": "保存成功", "saveErrorTitle": "保存失败", diff --git a/web/src/app/[lang]/contact/page.tsx b/web/src/app/[lang]/contact/page.tsx index 7de3c0f..2638a0b 100644 --- a/web/src/app/[lang]/contact/page.tsx +++ b/web/src/app/[lang]/contact/page.tsx @@ -58,7 +58,7 @@ const ContactUsPage: React.FC<{ params: paramsType}> = async ({ params }) => {

{t('email_description')} justlikemaki@foxmail.com diff --git a/web/src/app/[lang]/layout.tsx b/web/src/app/[lang]/layout.tsx index fb61845..7e8177e 100644 --- a/web/src/app/[lang]/layout.tsx +++ b/web/src/app/[lang]/layout.tsx @@ -20,10 +20,10 @@ export async function generateMetadata({ params }: { params: { lang: string } }) const { t } = await getTranslation(lang, 'layout'); const truePath = await getTruePathFromHeaders(await headers(), lang, true); return { - metadataBase: new URL('https://www.podcasthub.com'), + metadataBase: new URL('https://podcast.hubtoday.app'), title: t('title'), description: t('description'), - keywords: t('keywords').split(','), + // keywords: t('keywords').split(','), authors: [{ name: 'PodcastHub Team' }], icons: { icon: '/favicon.webp', diff --git a/web/src/app/[lang]/page.tsx b/web/src/app/[lang]/page.tsx index b9169e3..173c87c 100644 --- a/web/src/app/[lang]/page.tsx +++ b/web/src/app/[lang]/page.tsx @@ -7,6 +7,8 @@ import PodcastCreator from '@/components/PodcastCreator'; import ContentSection from '@/components/ContentSection'; import AudioPlayer from '@/components/AudioPlayer'; import SettingsForm from '@/components/SettingsForm'; +import HowToUse from '@/components/HowToUse'; +import FAQ from '@/components/FAQ'; import PointsOverview from '@/components/PointsOverview'; // 导入 PointsOverview import LoginModal from '@/components/LoginModal'; // 导入 LoginModal import NotificationBanner from '@/components/NotificationBanner'; // 导入 NotificationBanner @@ -415,6 +417,12 @@ export default function HomePage({ params }: { params: Promise<{ lang: string }> /> )} + {/* 使用说明 */} + + + {/* 常见问题 */} + + {/* 定价部分 todo */} {/* */} diff --git a/web/src/app/[lang]/podcast/[fileName]/layout.tsx b/web/src/app/[lang]/podcast/[fileName]/layout.tsx new file mode 100644 index 0000000..005d2f8 --- /dev/null +++ b/web/src/app/[lang]/podcast/[fileName]/layout.tsx @@ -0,0 +1,62 @@ +import { Metadata } from 'next'; +import { getTranslation } from '../../../../i18n'; +import { headers } from 'next/headers'; +import { getTruePathFromHeaders } from '../../../../lib/utils'; +import { getAudioInfo } from '../../../../lib/podcastApi'; + +export type paramsType = Promise<{ lang: string, fileName: string }>; + +export async function generateMetadata({ params }: { params: paramsType }): Promise { + const { fileName, lang } = await params; + const { t } = await getTranslation(lang); + const decodedFileName = decodeURIComponent(fileName); + + // 获取网站主标题 + const siteName = 'PodcastHub'; + + // 获取音频信息以获取 overview_content + const result = await getAudioInfo(decodedFileName, lang); + + let pageTitle = `${t('podcastContent.podcastDetails')} - ${decodedFileName}`; + let description = `${t('podcastContent.listenToPodcast')} ${decodedFileName}。`; + + // 如果成功获取到 overview_content,使用它来生成更好的 title 和 description + if (result.success && result.data?.overview_content) { + const overviewContent = result.data.overview_content; + + // 从 overview_content 中提取前150个字符作为 description + description = overviewContent.length > 150 + ? overviewContent.substring(0, 150) + '...' + : overviewContent; + + // 尝试从 overview_content 的第一行或前50个字符生成 title + const firstLine = overviewContent.split('\n')[0]; + if (firstLine && firstLine.length > 0 && firstLine.length <= 50) { + pageTitle = firstLine; + } else if (overviewContent.length > 0) { + // 如果第一行太长,取前50个字符 + pageTitle = overviewContent.substring(0, 40) + (overviewContent.length > 40 ? '...' : ''); + } + } + + // 组合最终的 title: 网站名称 - 页面标题 + const title = `${siteName} - ${pageTitle}`; + + const truePath = await getTruePathFromHeaders(await headers(), lang); + + return { + title, + description, + alternates: { + canonical: `${truePath}/podcast/${decodedFileName}`, + }, + }; +} + +export default function PodcastLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} \ No newline at end of file diff --git a/web/src/app/[lang]/podcast/[fileName]/page.tsx b/web/src/app/[lang]/podcast/[fileName]/page.tsx index 0f22f65..e0585aa 100644 --- a/web/src/app/[lang]/podcast/[fileName]/page.tsx +++ b/web/src/app/[lang]/podcast/[fileName]/page.tsx @@ -1,27 +1,7 @@ -import { Metadata } from 'next'; import PodcastContent from '@/components/PodcastContent'; -import { getTranslation } from '../../../../i18n'; // 导入 getTranslation -import { headers } from 'next/headers'; -import { getTruePathFromHeaders } from '../../../../lib/utils'; export type paramsType = Promise<{ lang: string, fileName: string }>; -export async function generateMetadata({ params }: { params: paramsType }): Promise { - const { fileName, lang } = await params; - const { t } = await getTranslation(lang); - const decodedFileName = decodeURIComponent(fileName); - const title = `${t('podcastContent.podcastDetails')} - ${decodedFileName}`; - const description = `${t('podcastContent.listenToPodcast')} ${decodedFileName}。`; - const truePath = await getTruePathFromHeaders(await headers(), lang); - return { - title, - description, - alternates: { - canonical: `${truePath}/podcast/${decodedFileName}`, - }, - }; -} - const PodcastDetailPage: React.FC<{ params: paramsType}> = async ({ params }) => { const { fileName, lang } = await params; // 解构 lang return ( diff --git a/web/src/components/FAQ.tsx b/web/src/components/FAQ.tsx new file mode 100644 index 0000000..ebad7aa --- /dev/null +++ b/web/src/components/FAQ.tsx @@ -0,0 +1,108 @@ +'use client'; + +import React, { useState } from 'react'; +import { useTranslation } from '../i18n/client'; + +interface FAQProps { + lang: string; +} + +export default function FAQ({ lang }: FAQProps) { + const { t } = useTranslation(lang, 'home'); + const [openIndex, setOpenIndex] = useState(0); + + const faqItems = ['q1', 'q2', 'q3', 'q4', 'q5']; + + const toggleItem = (index: number) => { + setOpenIndex(openIndex === index ? null : index); + }; + + return ( +

+
+

+ {t('faq')} +

+

+ {t('faqSubtitle')} +

+
+ +
+ {faqItems.map((item, index) => ( +
+ +
+
+ {t(`faqItems.${item}.answer`)} +
+
+
+ ))} +
+ +
+
+
+ + + +
+
+

+ {lang === 'zh-CN' && '还有其他问题?'} + {lang === 'en' && 'Have more questions?'} + {lang === 'ja' && '他にご質問はありますか?'} +

+

+ {lang === 'zh-CN' && '如果您有任何其他问题或需要帮助,请随时联系我们的支持团队。'} + {lang === 'en' && 'If you have any other questions or need assistance, please feel free to contact our support team.'} + {lang === 'ja' && 'その他のご質問やサポートが必要な場合は、お気軽にサポートチームにお問い合わせください。'} +

+
+ {lang === 'zh-CN' && '联系我们'} + {lang === 'en' && 'Contact Us'} + {lang === 'ja' && 'お問い合わせ'} + + + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/web/src/components/HowToUse.tsx b/web/src/components/HowToUse.tsx new file mode 100644 index 0000000..fa23a6d --- /dev/null +++ b/web/src/components/HowToUse.tsx @@ -0,0 +1,93 @@ +'use client'; + +import React from 'react'; +import { useTranslation } from '../i18n/client'; + +interface HowToUseProps { + lang: string; +} + +export default function HowToUse({ lang }: HowToUseProps) { + const { t } = useTranslation(lang, 'home'); + + const steps = [ + { + key: 'step1', + icon: ( + + + + ), + }, + { + key: 'step2', + icon: ( + + + + ), + }, + { + key: 'step3', + icon: ( + + + + ), + }, + { + key: 'step4', + icon: ( + + + + + ), + }, + ]; + + return ( +
+
+

+ {t('howToUse')} +

+

+ {t('howToUseSubtitle')} +

+
+ +
+ {steps.map((step) => ( +
+
+ {step.icon} +
+

+ {t(`howToUseSteps.${step.key}.title`)} +

+

+ {t(`howToUseSteps.${step.key}.description`)} +

+
+ ))} +
+ +
+
+ + + + + {lang === 'zh-CN' && '使用 PodcastHub 的AI播客生成器和沉浸故事功能创建更专业的播客'} + {lang === 'en' && 'Use PodcastHub\'s podcast generator and story generator to create more professional podcasts'} + {lang === 'ja' && 'PodcastHubのpodcast generatorとstory generatorでより専門的なポッドキャストを作成'} + +
+
+
+ ); +} \ No newline at end of file