diff --git a/README.md b/README.md index 7744ff2..116fa6e 100644 --- a/README.md +++ b/README.md @@ -144,9 +144,9 @@ curl -X POST "http://localhost:8000/generate-podcast" \ ``` ```custom-begin 您希望提供给 AI 的额外指令或上下文,例如: -- “请确保讨论中包含对 [特定概念] 的深入分析。” -- “请在对话中加入一些幽默元素,特别是关于 [某个主题] 的笑话。” -- “所有角色的发言都必须是简短的,并且每句话不超过两行。” +- "请确保讨论中包含对 [特定概念] 的深入分析。" +- "请在对话中加入一些幽默元素,特别是关于 [某个主题] 的笑话。" +- "所有角色的发言都必须是简短的,并且每句话不超过两行。" ```custom-end ``` @@ -194,6 +194,33 @@ curl -X POST "http://localhost:8000/generate-podcast" \ 本项目支持通过 Docker 进行部署,详细信息请参考 [Docker 使用指南](DOCKER_USAGE.md)。 --- + +## 🌍 国际化 (i18n) 支持 + +本项目支持多语言界面,目前支持中文 (zh-CN) 和英文 (en)。 + +### 📁 语言文件结构 + +语言文件位于 `web/public/locales/` 目录下,按照语言代码分组: +- `web/public/locales/zh-CN/common.json` - 中文翻译 +- `web/public/locales/en/common.json` - 英文翻译 + +### 🛠️ 添加新语言 + +1. 在 `web/public/locales/` 目录下创建新的语言文件夹,例如 `fr/` +2. 复制 `common.json` 文件到新文件夹中 +3. 翻译文件中的所有键值对 +4. 在 `web/next-i18next.config.js` 文件中添加新的语言代码到 `locales` 数组 +5. 在 `web/src/i18n.ts` 文件中更新 `languages` 变量 + +### 🌐 语言切换 + +用户可以通过 URL 路径或浏览器语言设置自动切换语言: +- `http://localhost:3000/zh-CN/` - 中文界面 +- `http://localhost:3000/en/` - 英文界面 + +--- + ## ⚙️ 配置文件详解 ### `config/[tts-provider].json` (TTS 角色与语音配置) @@ -329,7 +356,7 @@ curl -X POST "http://localhost:8000/generate-podcast" \ ## 📝 免责声明 * **许可证**: 本项目采用 [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html) 授权。 -* **无担保**: 本软件按“现状”提供,不附带任何明示或暗示的担保。 +* **无担保**: 本软件按"现状"提供,不附带任何明示或暗示的担保。 * **责任限制**: 在任何情况下,作者或版权持有者均不对因使用本软件而产生的任何损害承担责任。 * **第三方服务**: 用户需自行承担使用第三方服务(如 OpenAI API、TTS 服务)的风险和责任。 * **使用目的**: 本项目仅供学习和研究目的使用,请遵守所有适用的法律法规。 diff --git a/web/next.config.js b/web/next.config.ts similarity index 92% rename from web/next.config.js rename to web/next.config.ts index f1ce147..f736174 100644 --- a/web/next.config.js +++ b/web/next.config.ts @@ -9,6 +9,7 @@ const nextConfig = { removeConsole: process.env.NODE_ENV === 'production', }, output: 'standalone', + devIndicators: false, }; module.exports = nextConfig; \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 5e0abbc..4f1b0b8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -25,13 +25,17 @@ "drizzle-orm": "^0.44.4", "framer-motion": "^11.3.8", "globby": "^14.1.0", + "i18next": "^25.4.1", + "i18next-resources-to-backend": "^1.2.1", "lucide-react": "^0.424.0", - "next": "^14.2.5", + "next": "15.2.4", + "next-language-detector": "^1.1.0", "next-sitemap": "^4.2.3", "postcss": "^8.4.40", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "19.0.0", + "react-dom": "19.0.0", "react-hot-toast": "^2.4.1", + "react-i18next": "^15.7.2", "react-icons": "^5.5.0", "remark": "^15.0.1", "remark-html": "^16.0.1", @@ -65,6 +69,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", + "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@better-auth/utils": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.6.tgz", @@ -108,7 +124,6 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1077,9 +1092,9 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz", - "integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.3", @@ -1087,12 +1102,12 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.5.tgz", - "integrity": "sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.3" + "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", @@ -1158,6 +1173,367 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1454,9 +1830,9 @@ "peer": true }, "node_modules/@next/env": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.31.tgz", - "integrity": "sha512-X8VxxYL6VuezrG82h0pUA1V+DuTSJp7Nv15bxq3ivrFqZLjx81rfeHMWOE9T0jm1n3DtHGv8gdn6B0T0kr0D3Q==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz", + "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1470,9 +1846,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.31.tgz", - "integrity": "sha512-dTHKfaFO/xMJ3kzhXYgf64VtV6MMwDs2viedDOdP+ezd0zWMOQZkxcwOfdcQeQCpouTr9b+xOqMCUXxgLizl8Q==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz", + "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==", "cpu": [ "arm64" ], @@ -1486,9 +1862,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.31.tgz", - "integrity": "sha512-iSavebQgeMukUAfjfW8Fi2Iz01t95yxRl2w2wCzjD91h5In9la99QIDKcKSYPfqLjCgwz3JpIWxLG6LM/sxL4g==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", + "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", "cpu": [ "x64" ], @@ -1502,9 +1878,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.31.tgz", - "integrity": "sha512-XJb3/LURg1u1SdQoopG6jDL2otxGKChH2UYnUTcby4izjM0il7ylBY5TIA7myhvHj9lG5pn9F2nR2s3i8X9awQ==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", + "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", "cpu": [ "arm64" ], @@ -1518,9 +1894,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.31.tgz", - "integrity": "sha512-IInDAcchNCu3BzocdqdCv1bKCmUVO/bKJHnBFTeq3svfaWpOPewaLJ2Lu3GL4yV76c/86ZvpBbG/JJ1lVIs5MA==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", + "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", "cpu": [ "arm64" ], @@ -1534,9 +1910,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.31.tgz", - "integrity": "sha512-YTChJL5/9e4NXPKW+OJzsQa42RiWUNbE+k+ReHvA+lwXk+bvzTsVQboNcezWOuCD+p/J+ntxKOB/81o0MenBhw==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", + "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", "cpu": [ "x64" ], @@ -1550,9 +1926,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.31.tgz", - "integrity": "sha512-A0JmD1y4q/9ufOGEAhoa60Sof++X10PEoiWOH0gZ2isufWZeV03NnyRlRmJpRQWGIbRkJUmBo9I3Qz5C10vx4w==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", + "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", "cpu": [ "x64" ], @@ -1566,9 +1942,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.31.tgz", - "integrity": "sha512-nowJ5GbMeDOMzbTm29YqrdrD6lTM8qn2wnZfGpYMY7SZODYYpaJHH1FJXE1l1zWICHR+WfIMytlTDBHu10jb8A==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", + "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", "cpu": [ "arm64" ], @@ -1581,26 +1957,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.31.tgz", - "integrity": "sha512-pk9Bu4K0015anTS1OS9d/SpS0UtRObC+xe93fwnm7Gvqbv/W1ZbzhK4nvc96RURIQOux3P/bBH316xz8wjGSsA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.31.tgz", - "integrity": "sha512-LwFZd4JFnMHGceItR9+jtlMm8lGLU/IPkgjBBgYmdYSfalbHCiDpjMYtgDQ2wtwiAOSJOCyFI4m8PikrsDyA6Q==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", + "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", "cpu": [ "x64" ], @@ -2561,13 +2921,12 @@ "license": "Apache-2.0" }, "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/@tailwindcss/typography": { @@ -4162,6 +4521,20 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4180,6 +4553,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -6071,12 +6455,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -6228,6 +6606,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -6238,6 +6625,82 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/i18next": { + "version": "25.4.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.1.tgz", + "integrity": "sha512-sYJX4Ap0SSho1JZppKvc9BeacbpNCdNvKuXMYPUGjDH0XTTdVXmm7UU3l4bvF8nqh70lZNBeLboRYwItBf9jOw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz", + "integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-browser-languagedetector/node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/i18next-resources-to-backend": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.1.tgz", + "integrity": "sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-resources-to-backend/node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/i18next/node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -6352,6 +6815,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -6853,6 +7323,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -7085,6 +7556,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -7819,41 +8291,42 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.2.31", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.31.tgz", - "integrity": "sha512-Wyw1m4t8PhqG+or5a1U/Deb888YApC4rAez9bGhHkTsfwAy4SWKVro0GhEx4sox1856IbLhvhce2hAA6o8vkog==", + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz", + "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==", "license": "MIT", "dependencies": { - "@next/env": "14.2.31", - "@swc/helpers": "0.5.5", + "@next/env": "15.2.4", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.6" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=18.17.0" + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.31", - "@next/swc-darwin-x64": "14.2.31", - "@next/swc-linux-arm64-gnu": "14.2.31", - "@next/swc-linux-arm64-musl": "14.2.31", - "@next/swc-linux-x64-gnu": "14.2.31", - "@next/swc-linux-x64-musl": "14.2.31", - "@next/swc-win32-arm64-msvc": "14.2.31", - "@next/swc-win32-ia32-msvc": "14.2.31", - "@next/swc-win32-x64-msvc": "14.2.31" + "@next/swc-darwin-arm64": "15.2.4", + "@next/swc-darwin-x64": "15.2.4", + "@next/swc-linux-arm64-gnu": "15.2.4", + "@next/swc-linux-arm64-musl": "15.2.4", + "@next/swc-linux-x64-gnu": "15.2.4", + "@next/swc-linux-x64-musl": "15.2.4", + "@next/swc-win32-arm64-msvc": "15.2.4", + "@next/swc-win32-x64-msvc": "15.2.4", + "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -7863,11 +8336,24 @@ "@playwright/test": { "optional": true }, + "babel-plugin-react-compiler": { + "optional": true + }, "sass": { "optional": true } } }, + "node_modules/next-language-detector": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-language-detector/-/next-language-detector-1.1.0.tgz", + "integrity": "sha512-TLXMSqweKrrIID0a+PxotItN8MBwIurso3oyz9Odq1VPv3/sdrSpfam2V2eoF462+ibjtSW/P0Xs7gFmeA4Ktw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.16.7", + "i18next-browser-languagedetector": "7.2.0" + } + }, "node_modules/next-sitemap": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.2.3.tgz", @@ -8834,28 +9320,24 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-hot-toast": { @@ -8875,6 +9357,41 @@ "react-dom": ">=16" } }, + "node_modules/react-i18next": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.2.tgz", + "integrity": "sha512-xJxq7ibnhUlMvd82lNC4te1GxGUMoM1A05KKyqoqsBXVZtEvZg/fz/fnVzdlY/hhQ3SpP/79qCocZOtICGhd3g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 25.4.1", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-i18next/node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -9018,6 +9535,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -9297,13 +9820,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" }, "node_modules/semver": { "version": "7.7.2", @@ -9372,6 +9892,46 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9526,6 +10086,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -9919,9 +10489,9 @@ } }, "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { "client-only": "0.0.1" @@ -9930,7 +10500,7 @@ "node": ">= 12.0.0" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "peerDependenciesMeta": { "@babel/core": { @@ -10705,6 +11275,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", diff --git a/web/package.json b/web/package.json index 5ee86f9..ca06f01 100644 --- a/web/package.json +++ b/web/package.json @@ -30,13 +30,17 @@ "drizzle-orm": "^0.44.4", "framer-motion": "^11.3.8", "globby": "^14.1.0", + "i18next": "^25.4.1", + "i18next-resources-to-backend": "^1.2.1", "lucide-react": "^0.424.0", - "next": "^14.2.5", + "next": "15.2.4", + "next-language-detector": "^1.1.0", "next-sitemap": "^4.2.3", "postcss": "^8.4.40", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "19.0.0", + "react-dom": "19.0.0", "react-hot-toast": "^2.4.1", + "react-i18next": "^15.7.2", "react-icons": "^5.5.0", "remark": "^15.0.1", "remark-html": "^16.0.1", diff --git a/web/public/locales/en/components.json b/web/public/locales/en/components.json new file mode 100644 index 0000000..93e5841 --- /dev/null +++ b/web/public/locales/en/components.json @@ -0,0 +1,256 @@ +{ + "audioPlayer": { + "play": "Play", + "pause": "Pause", + "backward10s": "Backward 10s", + "forward10s": "Forward 10s", + "currentPlaybackRate": "Current playback rate", + "mute": "Mute", + "unmute": "Unmute", + "share": "Share", + "download": "Download", + "cannotGetAudioFileName": "Cannot get audio file name for sharing.", + "shareFailed": "Share failed: Cannot get audio file name.", + "playLinkCopied": "Play link copied to clipboard!" + }, + "audioPlayerControls": { + "pause": "Pause", + "play": "Play" + }, + "billingToggle": { + "monthly": "Monthly", + "annually": "Annually", + "save20Percent": "Save 20%" + }, + "configSelector": { + "loading": "Loading...", + "selectTTSConfig": "Select TTS Config", + "noAvailableTTSConfig": "No available TTS config", + "pleaseConfigTTS": "Please configure TTS service in settings first" + }, + "contentSection": { + "viewAll": "View All", + "noContent": "No content yet", + "refresh": "Refresh", + "recommendForYou": "Recommend for you" + }, + "footerLinks": { + "termsOfUse": "Terms of Use", + "privacyPolicy": "Privacy Policy", + "contactUs": "Contact Us", + "copyright": "© 2025 Hex2077" + }, + "languageSwitcher": { + "chinese": "中文", + "english": "English" + }, + "loginModal": { + "loginToYourAccount": "Login to Your Account", + "signInWithGoogle": "Sign in with Google", + "signInWithGitHub": "Sign in with GitHub" + }, + "podcastCard": { + "podcastGenerationQueued": "Podcast generation queued...", + "podcastGenerating": "Podcast generating...", + "moreOperations": "More operations", + "mostPopular": "Most Popular" + }, + "podcastContent": { + "speaker": "Speaker", + "cannotLoadPodcastDetails": "Cannot load podcast details:", + "unknownError": "Unknown error", + "returnToHomepage": "Return to Homepage", + "downloadAudio": "Download Audio", + "script": "Script", + "outline": "Outline", + "noOutlineContent": "No outline content." + }, + "podcastCreator": { + "giveVoiceToCreativity": "Give voice to creativity", + "enterTextPlaceholder": "Enter text, Markdown format supported...", + "addCustomInstructions": "Add custom instructions (optional)... e.g. fixed opening and closing remarks, contextual text, key points of output content", + "ttsConfigSelection": "TTS Config Selection", + "speaker": "Speaker", + "languageSelection": "Language Selection", + "durationSelection": "Duration Selection", + "fileUpload": "Upload File", + "pasteContent": "Paste Content", + "copyContent": "Copy Content", + "credits": "Credits", + "checkIn": "Check In", + "create": "Create", + "biu": "Biu!", + "checkInSuccess": "Check-in successful", + "checkInFailed": "Check-in failed", + "networkError": "Network error or server no response", + "topicCannotBeEmpty": "Topic cannot be empty", + "pleaseEnterPodcastTopic": "Please enter podcast topic.", + "ttsConfigNotSelected": "TTS config not selected", + "pleaseSelectTTSConfig": "Please select a TTS config.", + "pleaseSelectSpeaker": "Please select a speaker", + "pleaseSelectAtLeastOneSpeaker": "Please select at least one podcast speaker.", + "podcastGenerationFailed": "Podcast generation failed:", + "maximum5Speakers": "You can select up to 5 speakers.", + "chinese": "Chinese", + "english": "English", + "japanese": "Japanese", + "under5Minutes": "Under 5 minutes", + "between5And10Minutes": "5-10 minutes", + "between10And15Minutes": "10-15 minutes" + }, + "podcastTabs": { + "script": "Script", + "outline": "Outline", + "noOutlineContent": "No outline content." + }, + "pointsOverview": { + "totalPoints": "Total Points", + "last20EntriesOnly": "Only the last 20 entries are displayed.", + "pointDetails": "Point Details", + "noPointDetails": "No point details yet." + }, + "pricingCard": { + "perMonth": "/month", + "getStarted": "Get Started", + "upgradeToPro": "Upgrade to Pro", + "upgradeToBusiness": "Upgrade to Business", + "mostPopular": "Most Popular" + }, + "pricingSection": { + "creator": "Creator", + "pro": "Pro", + "business": "Business", + "chooseYourPlan": "Choose your plan", + "forIndividualsOrTeams": "Whether you are an individual creator or a large team, we have a plan for you.", + "visitPricingPage": "Visit Pricing Page", + "monthlyCreatorFeatures": { + "points": "2,000 Points/month", + "aiVoiceSynthesis": "AI voice synthesis", + "twoSpeakers": "Up to 2 speakers", + "commercialLicense": "Commercial License", + "audioDownload": "Audio Download" + }, + "monthlyProFeatures": { + "points": "5,000 Points/month", + "aiVoiceSynthesis": "AI voice synthesis", + "multiSpeakers": "Multi-speaker support", + "commercialLicense": "Commercial License", + "audioDownload": "Audio Download", + "advancedVoices": "Advanced Voices", + "storytellingMode": "Storytelling Mode" + }, + "monthlyBusinessFeatures": { + "points": "12,000 Points/month", + "aiVoiceSynthesis": "AI voice synthesis", + "multiSpeakers": "Multi-speaker support", + "commercialLicense": "Commercial License", + "dedicatedAccountManager": "Dedicated Account Manager", + "audioDownload": "Audio Download", + "advancedVoices": "Advanced Voices", + "storytellingMode": "Storytelling Mode", + "apiAccess": "API Access" + }, + "comingSoon": "(Coming Soon)" + }, + "settingsForm": { + "settings": "Settings", + "apiSettingsDescription": "Configure API settings and TTS services for the podcast generator", + "generalSettings": "General Settings", + "inputYourOpenAIAPIKey": "Input your OpenAI API Key", + "model": "Model", + "selectOrEnterModelName": "Select or enter model name", + "customModelInput": "Enter custom model", + "optionalCustomBaseURL": "Optional: Custom API Base URL", + "ttsServiceSettings": "TTS Service Settings", + "webAPITTSServices": "Web API TTS Services", + "edgeTTS": "Edge TTS", + "edgeTTSDescription": "Free TTS service based on Microsoft Edge, providing high-quality speech synthesis.", + "doubaoTTS": "Doubao TTS", + "doubaoTTSDescription": "Speech synthesis service powered by ByteDance Volcano Engine, baseUrl=https://openspeech.bytedance.com/api/v3/tts/unidirectional", + "inputDoubaoAppID": "Input Doubao App ID", + "inputDoubaoAccessKey": "Input Doubao Access Key", + "minimaxTTS": "Minimax TTS", + "minimaxTTSDescription": "Speech synthesis service powered by Minimax, baseUrl=https://api.minimaxi.com/v1/t2a_v2", + "inputMinimaxGroupID": "Input Minimax Group ID", + "inputMinimaxAPIKey": "Input Minimax API Key", + "fishTTS": "Fish TTS", + "fishTTSDescription": "Speech synthesis service powered by FishAudio, baseUrl=https://api.fish.audio/v1/tts", + "inputFishTTSAPIKey": "Input Fish TTS API Key", + "geminiTTS": "Gemini TTS", + "geminiTTSDescription": "Speech synthesis service powered by Google Gemini, baseUrl=https://generativelanguage.googleapis.com/v1beta/models", + "inputGeminiAPIKey": "Input Gemini API Key", + "localAPITTSServices": "Local API TTS Services", + "indexTTS": "Index TTS", + "indexTTSDescription": "IndexTTS service for local deployment, providing custom speech synthesis capabilities.", + "reset": "Reset", + "saving": "Saving...", + "saveSettings": "Save Settings", + "settingsSavedSuccessfully": "Settings saved successfully!", + "errorSavingSettings": "Error saving settings, please try again", + "configurationNotes": "Configuration Notes", + "apiKeyRequired": "API Key is required to call OpenAI service to generate podcast script", + "ttsOptional": "TTS service configuration is optional, unconfigured services will not be displayed in voice selection", + "emptyFieldsNull": "Empty fields will be saved as null values", + "settingsApplyImmediately": "Settings will take effect immediately after saving, no application restart required", + "apiKey": "API Key", + "baseURL": "Base URL", + "appID": "App ID", + "accessKey": "Access Key", + "groupID": "Group ID" + }, + "shareButton": { + "copySuccess": "Copy successful", + "pageLinkCopied": "Page link copied to clipboard!", + "copyFailed": "Copy failed", + "cannotCopyPageLink": "Cannot copy page link to clipboard." + }, + "sidebar": { + "expandSidebar": "Expand Sidebar", + "collapseSidebar": "Collapse Sidebar", + "home": "Home", + "library": "Library", + "explore": "Explore", + "pricing": "Pricing", + "points": "Points", + "ttsSettings": "TTS Settings", + "github": "Github", + "twitter": "Twitter", + "tiktok": "TikTok", + "email": "Email", + "login": "Login", + "logout": "Logout", + "areYouSureToLogout": "Are you sure you want to log out?", + "cancel": "Cancel", + "confirmLogout": "Logout", + "sessionExpired": "Session expired, logging out...", + "user": "User", + "clickAvatarToLogout": "Click avatar to logout", + "lessThanSMSizeCannotExpand": "Cannot expand on screens smaller than sm size", + "showMore": "Show more" + }, + "toast": { + "title": "Notification", + "message": "This is a notification message." + }, + "voicesModal": { + "selectSpeaker": "Select Speaker", + "all": "All", + "male": "Male", + "female": "Female", + "chinese": "Chinese (zh)", + "english": "English (en)", + "japanese": "Japanese (ja)", + "close": "Close", + "searchVoices": "Search voices...", + "noMatchingVoices": "No matching voices found.", + "language": "Language", + "unknown": "Unknown", + "host": "Host", + "confirmSelection": "Confirm Selection", + "max5Speakers": "You can select up to 5 speakers.", + "searchVoicesPlaceholder": "Search voices...", + "maxVoicesAlert": "You can select up to 5 speakers.", + "delete": "Delete", + "presenter": "Presenter" + } +} \ No newline at end of file diff --git a/web/public/locales/en/contact.json b/web/public/locales/en/contact.json new file mode 100644 index 0000000..3cfacfc --- /dev/null +++ b/web/public/locales/en/contact.json @@ -0,0 +1,10 @@ +{ + "contact_us_title": "Contact Us - PodcastHub", + "contact_us_description": "Have questions or suggestions? Feel free to contact the PodcastHub team. We look forward to hearing from you.", + "contact_us_heading": "Contact Us", + "contact_us_subheading": "We'd love to hear from you. Whether it's a question, suggestion, or collaboration opportunity, feel free to contact us through the following methods.", + "email_title": "Email", + "email_description": "For general inquiries and support, please email:", + "social_media_title": "Social Media", + "social_media_description": "Follow us on social media for the latest updates:" +} \ No newline at end of file diff --git a/web/public/locales/en/errors.json b/web/public/locales/en/errors.json new file mode 100644 index 0000000..2832573 --- /dev/null +++ b/web/public/locales/en/errors.json @@ -0,0 +1,49 @@ +{ + "invalid_filename": "Invalid filename", + "unsupported_file_type": "Unsupported file type", + "file_not_found": "File not found", + "internal_server_error": "Internal server error" +, + "unauthorized": "Unauthorized", + "invalid_status": "Invalid status", + "invalid_request_parameters": "Invalid request parameters", + "request_too_old_or_future": "Request too old or in the future", + "points_deducted_successfully": "Points deducted successfully", + "insufficient_points": "Insufficient points", + "daily_sign_in": "Daily sign-in", + "already_signed_in_today": "Already signed in today", + "points_added_successfully": "Points added successfully" +, + "config_files_list_success": "Configuration files list retrieved successfully", + "config_files_list_error": "Failed to retrieve configuration files list", + "invalid_config_file_name": "Invalid configuration file name", + "config_file_read_success": "Configuration file read successfully", + "read_config_file_error": "Failed to read configuration file" +, + "missing_file_name_parameter": "Missing file_name query parameter", + "internal_server_error_backend_connection": "Internal server error or unable to connect to backend service" +, + "user_not_logged_in_or_session_expired": "User not logged in or session expired", + "request_body_cannot_be_empty": "Request body cannot be empty", + "tts_provider_cannot_be_empty": "TTS provider cannot be empty", + "please_select_at_least_one_speaker": "Please select at least one podcast speaker", + "invalid_speaker_config_format": "Invalid podcast speaker configuration format", + "insufficient_points_for_podcast": "Insufficient points, generating a podcast requires {{pointsNeeded}} points, you currently have {{currentPoints}} points.", + "invalid_output_language": "Invalid output language", + "invalid_podcast_duration": "Invalid podcast duration", + "missing_frontend_tts_config": "Missing frontend TTS configuration information", + "incomplete_backend_tts_config": "Incomplete backend TTS configuration, please check backend configuration file.", + "internal_server_error_default": "Internal server error" +, + "failed_to_get_task_status": "Failed to get task status" +, + "insufficient_points_raw": "积分不足" +, + "forbidden_user_id": "Forbidden: User ID not allowed to access this resource", + "invalid_request_parameters_add_points": "Invalid request parameters. `userId`, `pointsToAdd` (positive number), `reasonCode`, and `description` are required.", + "points_added_successfully_to_user": "Points successfully added to user {{userId}}" +, + "invalid_pagination_parameters": "Invalid pagination parameters" +, + "cannot_read_tts_provider_config": "Cannot read TTS provider configuration file" +} \ No newline at end of file diff --git a/web/public/locales/en/home.json b/web/public/locales/en/home.json new file mode 100644 index 0000000..a9ed265 --- /dev/null +++ b/web/public/locales/en/home.json @@ -0,0 +1,26 @@ +{ + "podcastGenerationFailed": "Podcast generation failed, please retry", + "podcastTagsPlaceholder": "Pending podcast tags", + "configErrorTitle": "Configuration Error", + "configErrorMessage": "API Key or Model not set, please go to settings page to fill in.", + "error": { + "401": "Podcast generation failed, please check if the API Key is correct, or login status.", + "402": "Podcast generation failed, please check if you have enough points.", + "403": "Podcast generation failed, please login and try again.", + "409": "Podcast generation failed, there is a task in progress", + "backend": "Podcast generation failed, please check backend service or configuration", + "generationFailed": "Generation Failed", + "noTaskId": "Generation task failed, no task ID returned", + "unknown": "Unknown Error", + "dataProcessing": "Data processing failed", + "cantProcessPodcastList": "Unable to process podcast list data" + }, + "taskCreatedTitle": "Task Created", + "taskCreatedMessage": "Podcast generation task has been started, Task ID", + "recentlyGenerated": "Recently Generated", + "dataRetentionWarning": "Data is only kept for 30 minutes, please download and save it as soon as possible", + "saveSuccessTitle": "Save successfully", + "saveErrorTitle": "Save failed", + "pageInDevelopment": "Page in development", + "featureComingSoon": "This feature is under development, please look forward to it." +} \ No newline at end of file diff --git a/web/public/locales/en/layout.json b/web/public/locales/en/layout.json new file mode 100644 index 0000000..71e6a3e --- /dev/null +++ b/web/public/locales/en/layout.json @@ -0,0 +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.", + "keywords": "podcast,AI,voice synthesis,TTS,audio generation" +} \ No newline at end of file diff --git a/web/public/locales/en/privacy.json b/web/public/locales/en/privacy.json new file mode 100644 index 0000000..586a871 --- /dev/null +++ b/web/public/locales/en/privacy.json @@ -0,0 +1,84 @@ +{ + "privacy_policy": { + "title": "Privacy Policy - PodcastHub", + "description": "Learn how PodcastHub protects your privacy. We are committed to transparently handling your data.", + "last_updated": "Last updated: August 21, 2025", + "intro_paragraph": "Thank you for choosing PodcastHub! We understand the importance of privacy to you. This Privacy Policy (hereinafter referred to as \"this Policy\") aims to explain how we collect, use, store, share, and protect your personal information. Please read and understand this Policy carefully before using our services.", + "section1": { + "title": "1. Information We Collect", + "intro": "To provide and optimize our services, we collect the following types of information:", + "point1": { + "heading": "Information you actively provide", + "content": "When you register an account, use services, or contact us, you may provide personal information such as your name, email address, password, and text content you upload for podcast generation." + }, + "point2": { + "heading": "Information we automatically collect", + "content": "When you use the service, our servers automatically record certain information, including your IP address, browser type and version, operating system, device information, access date and time, and your interaction data within the service (such as clickstream, feature usage frequency, etc.)." + }, + "point3": { + "heading": "Cookies and similar technologies", + "content": "We use Cookies to store your preferences, maintain login status, and analyze usage to improve user experience. You can manage or delete Cookies according to your preferences." + } + }, + "section2": { + "title": "2. How We Use Your Information", + "intro": "We will use your personal information for the following purposes:", + "point1": { + "heading": "Provide and maintain services", + "content": ": Process your text content to generate podcasts, manage your account, and ensure the normal operation of services." + }, + "point2": { + "heading": "Improve and develop services", + "content": ": Analyze usage data to understand user preferences, optimize existing features, and develop new products and services." + }, + "point3": { + "heading": "Communicate with you", + "content": ": Send you important service notifications, account security reminders, update instructions, or marketing information you may be interested in (you can choose to unsubscribe)." + }, + "point4": { + "heading": "Security assurance", + "content": ": Protect our services, users, and the public from fraud, abuse, and security threats." + }, + "point5": { + "heading": "Comply with legal obligations", + "content": ": Fulfill applicable laws and regulations." + } + }, + "section3": { + "title": "3. Information Sharing and Disclosure", + "intro": "We are committed to keeping your information confidential. Unless the following circumstances exist, we will not share your personal information with any third party:", + "point1": { + "heading": "Obtain your explicit consent", + "content": ": With your explicit consent, we will share your information with other parties." + }, + "point2": { + "heading": "Legal requirements", + "content": ": We may disclose your personal information externally in accordance with laws and regulations, legal procedures, or mandatory requirements from government authorities." + }, + "point3": { + "heading": "Service providers", + "content": ": We may share necessary information with trusted third-party service providers (such as cloud storage, data analysis services) to assist us in providing and improving services. These providers are obligated to comply with our data protection standards." + }, + "point4": { + "heading": "Protecting our rights", + "content": ": To enforce our terms of service, protect our rights, property, or safety, and protect our users or the public from harm, we may share information to the necessary extent." + } + }, + "section4": { + "title": "4. Data Security", + "content": "We adopt industry-standard security measures to protect your information and prevent unauthorized access, disclosure, use, modification, or destruction. These measures include data encryption, access control, and regular security reviews. However, no internet transmission or electronic storage method is 100% secure, so we cannot guarantee absolute security." + }, + "section5": { + "title": "5. Your Rights", + "content": "You have various rights regarding your personal information, including accessing, correcting, and deleting your account information. You can exercise these rights through account settings or by contacting us." + }, + "section6": { + "title": "6. Policy Changes", + "content": "We may revise this Privacy Policy from time to time. For any significant changes, we will notify you by posting a prominent notice on the website or by sending you an email at least 30 days before the new terms take effect. What constitutes a significant change will be determined by us at our sole discretion." + }, + "section7": { + "title": "7. Contact Us", + "content": "If you have any questions or concerns about this Privacy Policy or our privacy practices, please feel free to contact us through our contact page." + } + } +} \ No newline at end of file diff --git a/web/public/locales/en/terms.json b/web/public/locales/en/terms.json new file mode 100644 index 0000000..b85d114 --- /dev/null +++ b/web/public/locales/en/terms.json @@ -0,0 +1,62 @@ +{ + "terms_of_service": { + "title": "Terms of Service - PodcastHub", + "description": "Welcome to PodcastHub's Terms of Service. These terms are designed to protect the common interests of users and the platform.", + "heading": "PodcastHub Terms of Service", + "last_updated": "Last updated: August 21, 2025", + "intro_paragraph": "Welcome to PodcastHub! We provide leading AI technology to convert your text content into high-quality podcast audio (hereinafter referred to as \"the Service\"). Before using our services, please read the following Terms of Service (hereinafter referred to as \"these Terms\") carefully. By accessing or using our services, you agree to be bound by these Terms.", + "section1": { + "title": "1. Service Overview", + "content": "PodcastHub is a platform that allows users to upload text, configure parameters, and generate audio content. Services include but are not limited to text-to-speech (TTS), audio processing, content storage, and distribution support. We are committed to providing stable and efficient technical solutions but reserve the right to modify or suspend services at any time without prior notice." + }, + "section2": { + "title": "2. User Account and Security", + "content": "You need to register an account to use all our services. You promise to provide accurate, complete, and up-to-date registration information and are responsible for maintaining the confidentiality of your account and password. All activities conducted through your account are your responsibility. If you discover any unauthorized use of your account, please notify us immediately." + }, + "section3": { + "title": "3. User Conduct Guidelines", + "intro": "You agree to abide by all applicable laws and regulations when using this service and shall not use this service to engage in the following activities:", + "point1": "Upload, generate, or disseminate any content that is illegal, harmful, threatening, abusive, harassing, defamatory, obscene, infringes on the privacy of others, incites hatred, or is racially or ethnically objectionable.", + "point2": "Infringe on the intellectual property rights of any third party, including but not limited to copyrights, trademarks, patents, or trade secrets.", + "point3": "Impersonate any person or entity, or otherwise misrepresent your affiliation with any person or entity.", + "point4": "Interfere with or disrupt our services, servers, or networks connected to the services, or fail to comply with any requirements, procedures, policies, or regulations of networks connected to the services.", + "point5": "Engage in any activity that may impose an unreasonable or disproportionately large load on our service infrastructure." + }, + "section4": { + "title": "4. Intellectual Property", + "point1": { + "heading": "Your Content", + "content": ": You retain full ownership and intellectual property rights to all text content you submit to the service (\"User Content\"). You grant PodcastHub a worldwide, non-exclusive, royalty-free license to use, reproduce, process, adapt, modify, publish, transmit, and display your User Content solely for the purpose of operating, providing, and improving the Service." + }, + "point2": { + "heading": "Generated Content", + "content": ": The copyright of audio content generated through this service (\"Generated Content\") depends on the copyright status of the User Content you input. As a technology service provider, we do not claim any ownership of the copyright of Generated Content." + }, + "point3": { + "heading": "Our Services", + "content": ": This service and all its related technologies, trademarks, logos, and content (excluding your content and generated content) are the proprietary property of PodcastHub. You may not use them without our prior written consent." + } + }, + "section5": { + "title": "5. Disclaimers and Limitation of Liability", + "point1": "This service is provided \"as is\" and \"as available,\" and we make no express or implied warranties, including but not limited to implied warranties of merchantability, fitness for a particular purpose, and non-infringement. We do not warrant that the service will be uninterrupted, timely, secure, or error-free.", + "point2": "To the maximum extent permitted by law, PodcastHub and its affiliates, directors, employees, or licensors shall in no event be liable for any indirect, incidental, special, consequential, or punitive damages arising from your access to or use of this service, whether such damages are based on warranty, contract, tort (including negligence), or any other legal theory." + }, + "section6": { + "title": "6. Termination of Service", + "content": "We reserve the right to suspend or terminate your access to this service at any time for any reason (including your breach of these terms) without prior notice. You may also terminate this agreement at any time by deactivating your account. Upon termination, your right to use this service will cease immediately." + }, + "section7": { + "title": "7. Modification of Terms", + "content": "We reserve the right to modify or replace these terms at our sole discretion at any time. If the changes are material, we will notify you by prominent notice on the website or by sending you an email at least 30 days before the new terms take effect. What constitutes a material change will be determined by us at our sole discretion." + }, + "section8": { + "title": "8. Governing Law", + "content": "The conclusion, validity, interpretation, performance, and dispute resolution of these terms shall be governed by relevant laws." + }, + "section9": { + "title": "9. Contact Us", + "content": "If you have any questions about these terms, please contact us through our contact page." + } + } +} \ No newline at end of file diff --git a/web/public/locales/ja/components.json b/web/public/locales/ja/components.json new file mode 100644 index 0000000..f3f3e08 --- /dev/null +++ b/web/public/locales/ja/components.json @@ -0,0 +1,258 @@ +{ + "audioPlayer": { + "play": "再生", + "pause": "一時停止", + "backward10s": "10秒巻き戻し", + "forward10s": "10秒早送り", + "currentPlaybackRate": "現在の再生速度", + "mute": "ミュート", + "unmute": "ミュート解除", + "share": "共有", + "download": "ダウンロード", + "cannotGetAudioFileName": "共有する音声ファイル名を取得できません。", + "shareFailed": "共有に失敗しました: 音声ファイル名を取得できません。", + "playLinkCopied": "再生リンクがクリップボードにコピーされました!" + }, + "audioPlayerControls": { + "pause": "一時停止", + "play": "再生" + }, + "billingToggle": { + "monthly": "月払い", + "annually": "年払い", + "save20Percent": "20%割引" + }, + "configSelector": { + "loading": "読み込み中...", + "selectTTSConfig": "TTS設定を選択", + "noAvailableTTSConfig": "利用可能なTTS設定がありません", + "pleaseConfigTTS": "最初に設定でTTSサービスを設定してください" + }, + "contentSection": { + "viewAll": "すべて表示", + "noContent": "コンテンツがありません", + "refresh": "更新", + "recommendForYou": "あなたへのおすすめ" + }, + "footerLinks": { + "termsOfUse": "利用規約", + "privacyPolicy": "プライバシーポリシー", + "contactUs": "お問い合わせ", + "copyright": "© 2025 Hex2077" + }, + "languageSwitcher": { + "chinese": "中文", + "english": "英語" + }, + "loginModal": { + "loginToYourAccount": "アカウントにログイン", + "signInWithGoogle": "Googleでサインイン", + "signInWithGitHub": "GitHubでサインイン" + }, + "podcastCard": { + "podcastGenerationQueued": "ポッドキャスト生成キューに追加されました...", + "podcastGenerating": "ポッドキャスト生成中...", + "moreOperations": "その他の操作", + "mostPopular": "最も人気" + }, + "podcastContent": { + "speaker": "スピーカー", + "cannotLoadPodcastDetails": "ポッドキャストの詳細を読み込めません:", + "unknownError": "不明なエラー", + "returnToHomepage": "ホームページに戻る", + "downloadAudio": "音声をダウンロード", + "script": "スクリプト", + "outline": "概要", + "noOutlineContent": "概要コンテンツがありません。" + }, + "podcastCreator": { + "giveVoiceToCreativity": "創造性に声を", + "enterTextPlaceholder": "テキストを入力してください。Markdown形式をサポートしています...", + "addCustomInstructions": "カスタム指示を追加(任意)...例:固定のオープニングとクロージング、コンテキストテキスト、出力コンテンツの主要なポイント", + "ttsConfigSelection": "TTS設定の選択", + "speaker": "スピーカー", + "languageSelection": "言語選択", + "durationSelection": "長さの選択", + "fileUpload": "ファイルをアップロード", + "pasteContent": "コンテンツを貼り付け", + "copyContent": "コンテンツをコピー", + "credits": "クレジット", + "checkIn": "チェックイン", + "create": "作成", + "biu": "びゅう!", + "checkInSuccess": "チェックイン成功", + "checkInFailed": "チェックイン失敗", + "networkError": "ネットワークエラーまたはサーバー応答なし", + "topicCannotBeEmpty": "トピックは空にできません", + "pleaseEnterPodcastTopic": "ポッドキャストのトピックを入力してください。", + "ttsConfigNotSelected": "TTS設定が選択されていません", + "pleaseSelectTTSConfig": "TTS設定を選択してください。", + "pleaseSelectSpeaker": "スピーカーを選択してください", + "pleaseSelectAtLeastOneSpeaker": "少なくとも1人のポッドキャストスピーカーを選択してください。", + "podcastGenerationFailed": "ポッドキャストの生成に失敗しました:", + "maximum5Speakers": "最大5人のスピーカーを選択できます。", + "chinese": "中国語", + "english": "英語", + "japanese": "日本語", + "under5Minutes": "5分未満", + "between5And10Minutes": "5〜10分", + "between10And15Minutes": "10〜15分" + }, + "podcastTabs": { + "script": "スクリプト", + "outline": "概要", + "noOutlineContent": "概要コンテンツがありません。" + }, + "pointsOverview": { + "totalPoints": "合計ポイント", + "last20EntriesOnly": "最後の20エントリのみが表示されます。", + "pointDetails": "ポイント詳細", + "noPointDetails": "ポイント詳細がありません。" + }, + "pricingCard": { + "perMonth": "/月", + "getStarted": "始める", + "upgradeToPro": "Proにアップグレード", + "upgradeToBusiness": "Businessにアップグレード", + "mostPopular": "最も人気" + }, + "pricingSection": { + "creator": "クリエーター", + "pro": "プロ", + "business": "ビジネス", + "chooseYourPlan": "プランを選択してください", + "forIndividualsOrTeams": "個人クリエイターでも大規模チームでも、あなたに合ったプランがあります。", + "visitPricingPage": "料金ページを見る", + "monthlyCreatorFeatures": { + "points": "2,000ポイント/月", + "aiVoiceSynthesis": "AI音声合成", + "twoSpeakers": "最大2名スピーカー", + "commercialLicense": "商用ライセンス", + "audioDownload": "音声ダウンロード" + }, + "monthlyProFeatures": { + "points": "5,000ポイント/月", + "aiVoiceSynthesis": "AI音声合成", + "multiSpeakers": "複数スピーカー対応", + "commercialLicense": "商用ライセンス", + "audioDownload": "音声ダウンロード", + "advancedVoices": "高度な音声", + "storytellingMode": "ストーリーテリングモード" + }, + "monthlyBusinessFeatures": { + "points": "12,000ポイント/月", + "aiVoiceSynthesis": "AI音声合成", + "multiSpeakers": "複数スピーカー対応", + "commercialLicense": "商用ライセンス", + "dedicatedAccountManager": "専任アカウントマネージャー", + "audioDownload": "音声ダウンロード", + "advancedVoices": "高度な音声", + "storytellingMode": "ストーリーテリングモード", + "apiAccess": "APIアクセス" + }, + "comingSoon": "(近日公開)", + "pricing_page_title": "料金プラン", + "pricing_page_description": "すべてのクリエイターのための柔軟なプラン。" + }, + "settingsForm": { + "settings": "設定", + "apiSettingsDescription": "ポッドキャストジェネレーターのAPI設定とTTSサービスを設定", + "generalSettings": "一般設定", + "inputYourOpenAIAPIKey": "OpenAI APIキーを入力", + "model": "モデル", + "selectOrEnterModelName": "モデル名を選択または入力", + "customModelInput": "カスタムモデルを入力", + "optionalCustomBaseURL": "オプション:カスタムAPIベースURL", + "ttsServiceSettings": "TTSサービス設定", + "webAPITTSServices": "Web API TTSサービス", + "edgeTTS": "Edge TTS", + "edgeTTSDescription": "Microsoft Edgeベースの無料TTSサービスで、高品質の音声合成を提供します。", + "doubaoTTS": "Doubao TTS", + "doubaoTTSDescription": "ByteDance Volcano Engineによる音声合成サービス、baseUrl=https://openspeech.bytedance.com/api/v3/tts/unidirectional", + "inputDoubaoAppID": "Doubao App IDを入力", + "inputDoubaoAccessKey": "Doubao Access Keyを入力", + "minimaxTTS": "Minimax TTS", + "minimaxTTSDescription": "Minimaxによる音声合成サービス、baseUrl=https://api.minimaxi.com/v1/t2a_v2", + "inputMinimaxGroupID": "Minimax Group IDを入力", + "inputMinimaxAPIKey": "Minimax APIキーを入力", + "fishTTS": "Fish TTS", + "fishTTSDescription": "FishAudioによる音声合成サービス、baseUrl=https://api.fish.audio/v1/tts", + "inputFishTTSAPIKey": "Fish TTS APIキーを入力", + "geminiTTS": "Gemini TTS", + "geminiTTSDescription": "Google Geminiによる音声合成サービス、baseUrl=https://generativelanguage.googleapis.com/v1beta/models", + "inputGeminiAPIKey": "Gemini APIキーを入力", + "localAPITTSServices": "ローカルAPI TTSサービス", + "indexTTS": "Index TTS", + "indexTTSDescription": "ローカル展開用のIndexTTSサービスで、カスタム音声合成機能を提供します。", + "reset": "リセット", + "saving": "保存中...", + "saveSettings": "設定を保存", + "settingsSavedSuccessfully": "設定が正常に保存されました!", + "errorSavingSettings": "設定の保存中にエラーが発生しました。もう一度お試しください", + "configurationNotes": "設定ノート", + "apiKeyRequired": "ポッドキャストスクリプトを生成するためにOpenAIサービスを呼び出すにはAPIキーが必要です", + "ttsOptional": "TTSサービス設定はオプションです。設定されていないサービスは音声選択に表示されません", + "emptyFieldsNull": "空のフィールドはnull値として保存されます", + "settingsApplyImmediately": "設定は保存後すぐに適用され、アプリケーションの再起動は不要です", + "apiKey": "APIキー", + "baseURL": "ベースURL", + "appID": "App ID", + "accessKey": "アクセスキー", + "groupID": "グループID" + }, + "shareButton": { + "copySuccess": "コピー成功", + "pageLinkCopied": "ページリンクがクリップボードにコピーされました!", + "copyFailed": "コピー失敗", + "cannotCopyPageLink": "ページリンクをクリップボードにコピーできません。" + }, + "sidebar": { + "expandSidebar": "サイドバーを展開", + "collapseSidebar": "サイドバーを折りたたむ", + "home": "ホーム", + "library": "ライブラリ", + "explore": "探索", + "pricing": "料金", + "points": "ポイント", + "ttsSettings": "TTS設定", + "github": "Github", + "twitter": "Twitter", + "tiktok": "TikTok", + "email": "メール", + "login": "ログイン", + "logout": "ログアウト", + "areYouSureToLogout": "本当にログアウトしますか?", + "cancel": "キャンセル", + "confirmLogout": "ログアウト", + "sessionExpired": "セッションが期限切れです。ログアウトします...", + "user": "ユーザー", + "clickAvatarToLogout": "アバターをクリックしてログアウト", + "lessThanSMSizeCannotExpand": "smサイズ未満の画面では展開できません", + "showMore": "もっと見る" + }, + "toast": { + "title": "通知", + "message": "これは通知メッセージです。" + }, + "voicesModal": { + "selectSpeaker": "スピーカーを選択", + "all": "すべて", + "male": "男性", + "female": "女性", + "chinese": "中国語 (zh)", + "english": "英語 (en)", + "japanese": "日本語 (ja)", + "close": "閉じる", + "searchVoices": "声を検索...", + "noMatchingVoices": "一致する声が見つかりません。", + "language": "言語", + "unknown": "不明", + "host": "ホスト", + "confirmSelection": "選択を確定", + "max5Speakers": "最大5人のスピーカーを選択できます。", + "searchVoicesPlaceholder": "声を検索...", + "maxVoicesAlert": "最大5人のスピーカーを選択できます。", + "delete": "削除", + "presenter": "プレゼンター" + } +} \ No newline at end of file diff --git a/web/public/locales/ja/contact.json b/web/public/locales/ja/contact.json new file mode 100644 index 0000000..cac0f3c --- /dev/null +++ b/web/public/locales/ja/contact.json @@ -0,0 +1,10 @@ +{ + "contact_us_title": "お問い合わせ - PodcastHub", + "contact_us_description": "ご質問やご提案がありますか?お気軽にPodcastHubチームにお問い合わせください。皆様からのご連絡をお待ちしております。", + "contact_us_heading": "お問い合わせ", + "contact_us_subheading": "ご質問、ご提案、またはコラボレーションの機会など、お気軽にお問い合わせください。", + "email_title": "メール", + "email_description": "一般的なお問い合わせやサポートは、以下のアドレスまでメールでお問い合わせください。", + "social_media_title": "ソーシャルメディア", + "social_media_description": "最新情報については、ソーシャルメディアで私たちをフォローしてください:" +} \ No newline at end of file diff --git a/web/public/locales/ja/errors.json b/web/public/locales/ja/errors.json new file mode 100644 index 0000000..b07b2b3 --- /dev/null +++ b/web/public/locales/ja/errors.json @@ -0,0 +1,49 @@ +{ + "invalid_filename": "無効なファイル名", + "unsupported_file_type": "サポートされていないファイルタイプ", + "file_not_found": "ファイルが見つかりません", + "internal_server_error": "サーバー内部エラー" +, + "unauthorized": "未承認", + "invalid_status": "無効なステータス", + "invalid_request_parameters": "無効なリクエストパラメータ", + "request_too_old_or_future": "リクエストが古すぎるか未来です", + "points_deducted_successfully": "ポイントが正常に差し引かれました", + "insufficient_points": "ポイント不足", + "daily_sign_in": "毎日サインイン", + "already_signed_in_today": "本日はすでにサインイン済みです", + "points_added_successfully": "ポイントが正常に追加されました" +, + "config_files_list_success": "設定ファイルリストが正常に取得されました", + "config_files_list_error": "設定ファイルリストの取得に失敗しました", + "invalid_config_file_name": "無効な設定ファイル名", + "config_file_read_success": "設定ファイルが正常に読み取られました", + "read_config_file_error": "設定ファイルの読み取りに失敗しました" +, + "missing_file_name_parameter": "ファイル名クエリパラメータがありません", + "internal_server_error_backend_connection": "内部サーバーエラーまたはバックエンドサービスに接続できません" +, + "user_not_logged_in_or_session_expired": "ユーザーがログインしていないか、セッションの有効期限が切れています", + "request_body_cannot_be_empty": "リクエストボディは空にできません", + "tts_provider_cannot_be_empty": "TTSプロバイダーは空にできません", + "please_select_at_least_one_speaker": "少なくとも1人のポッドキャスト話者を選択してください", + "invalid_speaker_config_format": "無効なポッドキャスト話者設定フォーマット", + "insufficient_points_for_podcast": "ポイントが不足しています。ポッドキャストを生成するには{{pointsNeeded}}ポイントが必要です。現在{{currentPoints}}ポイントしかありません。", + "invalid_output_language": "無効な出力言語", + "invalid_podcast_duration": "無効なポッドキャスト期間", + "missing_frontend_tts_config": "フロントエンドTTS設定情報がありません", + "incomplete_backend_tts_config": "バックエンドTTS設定が不完全です。バックエンド設定ファイルを確認してください。", + "internal_server_error_default": "サーバー内部エラー" +, + "failed_to_get_task_status": "タスクステータスの取得に失敗しました" +, + "insufficient_points_raw": "ポイント不足" +, + "forbidden_user_id": "禁止:このリソースへのアクセスが許可されていないユーザーID", + "invalid_request_parameters_add_points": "無効なリクエストパラメータ。`userId`、`pointsToAdd`(正の数)、`reasonCode`、および`description`が必要です。", + "points_added_successfully_to_user": "ユーザー{{userId}}にポイントが正常に追加されました" +, + "invalid_pagination_parameters": "無効なページネーションパラメータ" +, + "cannot_read_tts_provider_config": "TTSプロバイダー構成ファイルを読み取れません" +} \ No newline at end of file diff --git a/web/public/locales/ja/home.json b/web/public/locales/ja/home.json new file mode 100644 index 0000000..b2d63c1 --- /dev/null +++ b/web/public/locales/ja/home.json @@ -0,0 +1,26 @@ +{ + "podcastGenerationFailed": "ポッドキャストの生成に失敗しました。再試行してください。", + "podcastTagsPlaceholder": "保留中のポッドキャストタグ", + "configErrorTitle": "設定エラー", + "configErrorMessage": "APIキーまたはモデルが設定されていません。設定ページで入力してください。", + "error": { + "401": "ポッドキャストの生成に失敗しました。APIキーが正しいか、ログイン状態を確認してください。", + "402": "ポッドキャストの生成に失敗しました。ポイントが十分にあるか確認してください。", + "403": "ポッドキャストの生成に失敗しました。ログインしてもう一度お試しください。", + "409": "ポッドキャストの生成に失敗しました。タスクが進行中です。", + "backend": "ポッドキャストの生成に失敗しました。バックエンドサービスまたは設定を確認してください。", + "generationFailed": "生成に失敗しました。", + "noTaskId": "生成タスクに失敗しました。タスクIDが返されませんでした。", + "unknown": "不明なエラー", + "dataProcessing": "データ処理に失敗しました。", + "cantProcessPodcastList": "ポッドキャストリストデータを処理できませんでした。" + }, + "taskCreatedTitle": "タスク作成済み", + "taskCreatedMessage": "ポッドキャスト生成タスクが開始されました。タスクID", + "recentlyGenerated": "最近生成されたもの", + "dataRetentionWarning": "データは30分間のみ保持されます。できるだけ早くダウンロードして保存してください。", + "saveSuccessTitle": "保存成功", + "saveErrorTitle": "保存失敗", + "pageInDevelopment": "開発中のページ", + "featureComingSoon": "この機能は開発中です。ご期待ください。" +} \ No newline at end of file diff --git a/web/public/locales/ja/layout.json b/web/public/locales/ja/layout.json new file mode 100644 index 0000000..032cca2 --- /dev/null +++ b/web/public/locales/ja/layout.json @@ -0,0 +1,5 @@ +{ + "title": "PodcastHub:あなたのAIポッドキャスト作成プラットフォーム - テキストを高品質なポッドキャストオーディオに簡単に変換、複数の声とスタイルをサポートし、創造性を手軽に実現。", + "description": "PodcastHubは最先端のAI技術を駆使し、あなたの創造性に無限の可能性を提供します。テキストやアイデアをプロ品質のポッドキャストオーディオに簡単に変換し、多様なパーソナライズされた声とスタイルのオプションを提供します。今すぐ効率的な作成を体験し、あなたの声を世界に広め、より多くのリスナーを引きつけ、ポッドキャスト制作プロセスを簡素化します。", + "keywords": "ポッドキャスト,AI,音声合成,TTS,オーディオ生成" +} \ No newline at end of file diff --git a/web/public/locales/ja/privacy.json b/web/public/locales/ja/privacy.json new file mode 100644 index 0000000..b255b6b --- /dev/null +++ b/web/public/locales/ja/privacy.json @@ -0,0 +1,84 @@ +{ + "privacy_policy": { + "title": "プライバシーポリシー - PodcastHub", + "description": "PodcastHubがあなたのプライバシーをどのように保護しているかをご覧ください。私たちはあなたのデータを透明に扱うことに尽力しています。", + "last_updated": "最終更新日:2025年8月21日", + "intro_paragraph": "PodcastHubをご利用いただきありがとうございます。お客様のプライバシーは私たちにとって重要です。このプライバシーポリシー(以下「本ポリシー」といいます)は、お客様の個人情報をどのように収集、使用、保存、共有、保護するかを説明することを目的としています。サービスをご利用になる前に、本ポリシーを注意深くお読みになり、ご理解ください。", + "section1": { + "title": "1. 収集する情報", + "intro": "サービスを提供し、最適化するために、以下の種類の情報を収集します:", + "point1": { + "heading": "お客様が積極的に提供する情報", + "content": "アカウントの登録、サービスの利用、または当社への連絡時に、お客様の氏名、電子メールアドレス、パスワード、ポッドキャスト生成のためにアップロードしたテキストコンテンツなどの個人情報を提供する場合があります。" + }, + "point2": { + "heading": "自動的に収集する情報", + "content": "サービスをご利用の際、当社のサーバーは、お客様のIPアドレス、ブラウザの種類とバージョン、オペレーティングシステム、デバイス情報、アクセス日時、サービス内でのインタラクションデータ(クリックストリーム、機能使用頻度など)など、特定の情報を自動的に記録します。" + }, + "point3": { + "heading": "Cookieおよび類似技術", + "content": "当社はCookieを使用して、お客様の設定を保存し、ログイン状態を維持し、利用状況を分析してユーザーエクスペリエンスを向上させます。お客様は、ご自身の好みに応じてCookieを管理または削除することができます。" + } + }, + "section2": { + "title": "2. 情報の利用方法", + "intro": "お客様の個人情報は、以下の目的で利用されます:", + "point1": { + "heading": "サービスの提供と維持", + "content": ":お客様のテキストコンテンツを処理してポッドキャストを生成し、お客様のアカウントを管理し、サービスが正常に動作することを確認します。" + }, + "point2": { + "heading": "サービスの改善と開発", + "content": ":利用状況データを分析してユーザーの好みを理解し、既存の機能を最適化し、新しい製品やサービスを開発します。" + }, + "point3": { + "heading": "お客様との連絡", + "content": ":重要なサービス通知、アカウントセキュリティアラート、更新指示、またはお客様が関心を持つ可能性のあるマーケティング情報を送信します(購読解除を選択できます)。" + }, + "point4": { + "heading": "セキュリティ保証", + "content": ":詐欺、乱用、セキュリティ上の脅威から当社のサービス、ユーザー、および一般の人々を保護します。" + }, + "point5": { + "heading": "法的義務の遵守", + "content": ":適用される法律および規制の要件を遵守します。" + } + }, + "section3": { + "title": "3. 情報の共有と開示", + "intro": "当社は、お客様の情報を機密として保持することをお約束します。以下の状況を除き、お客様の個人情報を第三者と共有することはありません:", + "point1": { + "heading": "お客様の明示的な同意を得た場合", + "content": ":お客様の明示的な同意を得て、当社はお客様の情報を他の当事者と共有します。" + }, + "point2": { + "heading": "法的要件", + "content": ":法律および規制、法的手続きの要件、または政府当局からの強制的な要求に従い、当社はお客様の個人情報を外部に開示する場合があります。" + }, + "point3": { + "heading": "サービスプロバイダー", + "content": ":当社は、信頼できる第三者サービスプロバイダー(クラウドストレージ、データ分析サービスなど)と必要な情報を共有し、サービスの提供と改善を支援する場合があります。これらのプロバイダーは、当社のデータ保護基準を遵守する義務を負います。" + }, + "point4": { + "heading": "当社の権利の保護", + "content": ":利用規約を執行し、当社の権利、財産、または安全を保護し、ユーザーまたは一般の人々を危害から保護するために、当社は必要な範囲で情報を共有する場合があります。" + } + }, + "section4": { + "title": "4. データセキュリティ", + "content": "当社は、お客様の情報を保護し、不正なアクセス、開示、使用、変更、または破壊を防ぐために、業界標準のセキュリティ対策を採用しています。これらの対策には、データ暗号化、アクセス制御、定期的なセキュリティレビューが含まれます。ただし、インターネット送信や電子ストレージの方法は100%安全ではないため、絶対的な安全性を保証することはできません。" + }, + "section5": { + "title": "5. お客様の権利", + "content": "お客様は、ご自身の個人情報に関して、アカウント情報のアクセス、修正、削除など、様々な権利を有しています。これらの権利は、アカウント設定または当社への連絡を通じて行使できます。" + }, + "section6": { + "title": "6. ポリシー変更", + "content": "当社は、本プライバシーポリシーを随時改訂する権利を留保します。重要な変更があった場合、新しい規約が発効する少なくとも30日前に、ウェブサイトに目立つ通知を掲載するか、電子メールを送信してお客様に通知します。何が重要な変更を構成するかは、当社の単独の裁量で決定されます。" + }, + "section7": { + "title": "7. お問い合わせ", + "content": "本プライバシーポリシーまたは当社のプライバシー慣行についてご質問やご不明な点がございましたら、お気軽にお問い合わせページからお問い合わせください。" + } + } +} \ No newline at end of file diff --git a/web/public/locales/ja/terms.json b/web/public/locales/ja/terms.json new file mode 100644 index 0000000..e6e1a09 --- /dev/null +++ b/web/public/locales/ja/terms.json @@ -0,0 +1,62 @@ +{ + "terms_of_service": { + "title": "利用規約 - PodcastHub", + "description": "PodcastHubの利用規約へようこそ。本規約は、ユーザーとプラットフォームの共通の利益を保護するために策定されています。", + "heading": "PodcastHub 利用規約", + "last_updated": "最終更新日:2025年8月21日", + "intro_paragraph": "PodcastHubへようこそ!当社は、お客様のテキストコンテンツを高品質なポッドキャスト音声に変換する最先端のAI技術(以下「本サービス」といいます)を提供しています。本サービスをご利用になる前に、以下の利用規約(以下「本規約」といいます)を注意深くお読みください。本サービスにアクセスまたは利用することにより、お客様は本規約に拘束されることに同意したものとみなされます。", + "section1": { + "title": "1. サービス概要", + "content": "PodcastHubは、ユーザーがテキストをアップロードし、パラメーターを設定し、音声コンテンツを生成できるプラットフォームです。サービスには、テキスト読み上げ(TTS)、音声処理、コンテンツストレージ、配信サポートが含まれますが、これらに限定されません。当社は安定した効率的な技術ソリューションの提供に尽力していますが、事前の通知なしにいつでもサービスを変更または中断する権利を留保します。" + }, + "section2": { + "title": "2. ユーザーアカウントとセキュリティ", + "content": "すべてのサービスを利用するには、アカウントを登録する必要があります。お客様は、正確、完全、最新の登録情報を提供することを約束し、アカウントとパスワードの機密性を維持する責任を負います。お客様のアカウントを通じて行われるすべての活動は、お客様自身の責任です。お客様のアカウントの不正使用を発見した場合は、直ちに当社に通知してください。" + }, + "section3": { + "title": "3. ユーザー行動ガイドライン", + "intro": "お客様は、本サービスを利用する際に適用されるすべての法令を遵守し、本サービスを以下の活動に従事するために利用しないことに同意するものとします。", + "point1": "違法、有害、脅迫的、虐待的、嫌がらせ、名誉毀損的、わいせつ的、他者のプライバシーを侵害する、憎悪を煽る、または人種的、民族的に不快なコンテンツをアップロード、生成、または配布すること。", + "point2": "著作権、商標権、特許権、企業秘密を含むがこれに限定されない、第三者の知的財産権を侵害すること。", + "point3": "個人または団体になりすます、またはその他の方法で、お客様と個人または団体との関係を虚偽表示すること。", + "point4": "当社のサービス、サーバー、またはサービスに接続されたネットワークを妨害または中断すること、またはサービスに接続されたネットワークの要件、手順、ポリシー、または規制を遵守しないこと。", + "point5": "当社のサービスインフラストラクチャに不合理または不均衡に大きな負荷をかける可能性のある活動に従事ること。" + }, + "section4": { + "title": "4. 知的財産", + "point1": { + "heading": "お客様のコンテンツ", + "content": ":お客様は、本サービスに提出するすべてのテキストコンテンツ(「ユーザーコンテンツ」)の完全な所有権および知的財産権を保持します。お客様は、PodcastHubに対し、本サービスの運営、提供、および改善のみを目的として、お客様のユーザーコンテンツを使用、複製、処理、適応、変更、公開、送信、および表示するための、世界規模の、非独占的、ロイヤリティフリーのライセンスを付与します。" + }, + "point2": { + "heading": "生成されたコンテンツ", + "content": ":本サービスを通じて生成される音声コンテンツ(「生成コンテンツ」)の著作権は、お客様が入力するユーザーコンテンツの著作権の状態に依存します。技術サービスプロバイダーとして、当社は生成コンテンツの著作権に対するいかなる所有権も主張しません。" + }, + "point3": { + "heading": "当社のサービス", + "content": ":本サービスおよびこれに関連するすべての技術、商標、ロゴ、コンテンツ(お客様のコンテンツおよび生成コンテンツを除く)は、PodcastHubの専有財産です。当社の事前の書面による同意なしにこれらを使用することはできません。" + } + }, + "section5": { + "title": "5. 免責事項と責任の制限", + "point1": "本サービスは「現状有姿」および「利用可能な限り」提供され、当社は商品性、特定目的への適合性、非侵害に関する黙示の保証を含むがこれに限定されない、いかなる明示または黙示の保証も行いません。当社は、サービスが中断されず、タイムリーで、安全で、エラーフリーであることを保証しません。", + "point2": "法律で許容される最大限の範囲で、PodcastHubおよびその関連会社、取締役、従業員、またはライセンサーは、保証、契約、不法行為(過失を含む)、またはその他の法的理論に基づくものであっても、お客様の本サービスへのアクセスまたは使用に起因するいかなる間接的、偶発的、特別、結果的、または懲罰的な損害賠償についても一切責任を負いません。" + }, + "section6": { + "title": "6. サービスの終了", + "content": "当社は、いつでも理由の如何を問わず(本規約違反を含む)、事前の通知なしに本サービスへのお客様のアクセスを一時停止または終了する権利を留保します。お客様は、アカウントを停止することにより、いつでも本契約を終了することができます。終了後、お客様の本サービスを利用する権利は直ちに停止します。" + }, + "section7": { + "title": "7. 規約の変更", + "content": "当社は、いつでも単独の裁量で本規約を修正または置換する権利を留保します。変更が重大な場合、新しい規約が発効する少なくとも30日前に、ウェブサイトに目立つ通知を掲載するか、電子メールを送信してお客様に通知します。何が重大な変更を構成するかは、当社の単独の裁量で決定されます。" + }, + "section8": { + "title": "8. 準拠法", + "content": "本規約の締結、効力、解釈、履行、および紛争解決には、関連法が適用されるものとします。" + }, + "section9": { + "title": "9. お問い合わせ", + "content": "本規約についてご質問がございましたら、お問い合わせページからお問い合わせください。" + } + } +} \ No newline at end of file diff --git a/web/public/locales/zh-CN/components.json b/web/public/locales/zh-CN/components.json new file mode 100644 index 0000000..1e06db5 --- /dev/null +++ b/web/public/locales/zh-CN/components.json @@ -0,0 +1,256 @@ +{ + "audioPlayer": { + "play": "播放", + "pause": "暂停", + "backward10s": "后退10秒", + "forward10s": "前进10秒", + "currentPlaybackRate": "当前倍速", + "mute": "静音", + "unmute": "取消静音", + "share": "分享", + "download": "下载", + "cannotGetAudioFileName": "无法获取音频文件名进行分享。", + "shareFailed": "分享失败:无法获取音频文件名。", + "playLinkCopied": "播放链接已复制到剪贴板!" + }, + "audioPlayerControls": { + "pause": "暂停", + "play": "播放" + }, + "billingToggle": { + "monthly": "连续包月", + "annually": "连续包年", + "save20Percent": "节省 20%" + }, + "configSelector": { + "loading": "加载中...", + "selectTTSConfig": "选择TTS配置", + "noAvailableTTSConfig": "暂无可用的TTS配置", + "pleaseConfigTTS": "请先在设置中配置TTS服务" + }, + "contentSection": { + "viewAll": "查看全部", + "noContent": "暂无内容", + "refresh": "刷新", + "recommendForYou": "为你推荐" + }, + "footerLinks": { + "termsOfUse": "使用条款", + "privacyPolicy": "隐私政策", + "contactUs": "联系我们", + "copyright": "© 2025 Hex2077" + }, + "languageSwitcher": { + "chinese": "中文", + "english": "英文" + }, + "loginModal": { + "loginToYourAccount": "登录您的账户", + "signInWithGoogle": "使用 Google 登录", + "signInWithGitHub": "使用 GitHub 登录" + }, + "podcastCard": { + "podcastGenerationQueued": "播客生成排队中...", + "podcastGenerating": "播客生成中...", + "moreOperations": "更多操作", + "mostPopular": "最受欢迎" + }, + "podcastContent": { + "speaker": "说话人", + "cannotLoadPodcastDetails": "无法加载播客详情:", + "unknownError": "未知错误", + "returnToHomepage": "返回首页", + "downloadAudio": "下载音频", + "script": "脚本", + "outline": "大纲", + "noOutlineContent": "暂无大纲内容。" + }, + "podcastCreator": { + "giveVoiceToCreativity": "给创意一个真实的声音", + "enterTextPlaceholder": "输入文字,支持Markdown格式...", + "addCustomInstructions": "添加自定义指令(可选)... 例如:固定的开场白和结束语,文案脚本语境,输出内容的重点", + "ttsConfigSelection": "TTS配置选择", + "speaker": "说话人", + "languageSelection": "语言选择", + "durationSelection": "时长选择", + "fileUpload": "上传文件", + "pasteContent": "粘贴内容", + "copyContent": "复制内容", + "credits": "积分", + "checkIn": "签到", + "create": "创作", + "biu": "Biu!", + "checkInSuccess": "签到成功", + "checkInFailed": "签到失败", + "networkError": "网络错误或服务器无响应", + "topicCannotBeEmpty": "主题不能为空", + "pleaseEnterPodcastTopic": "请输入播客主题。", + "ttsConfigNotSelected": "TTS配置未选择", + "pleaseSelectTTSConfig": "请选择一个TTS配置。", + "pleaseSelectSpeaker": "请选择说话人", + "pleaseSelectAtLeastOneSpeaker": "请至少选择一位播客说话人。", + "podcastGenerationFailed": "播客生成失败:", + "maximum5Speakers": "最多只能选择5个说话人。", + "chinese": "中文", + "english": "英文", + "japanese": "日文", + "under5Minutes": "5分钟以内", + "between5And10Minutes": "5-10分钟", + "between10And15Minutes": "10-15分钟" + }, + "podcastTabs": { + "script": "脚本", + "outline": "大纲", + "noOutlineContent": "暂无大纲内容。" + }, + "pointsOverview": { + "totalPoints": "总积分", + "last20EntriesOnly": "仅显示最近20条积分明细。", + "pointDetails": "积分明细", + "noPointDetails": "暂无积分明细。" + }, + "pricingCard": { + "perMonth": "/月", + "getStarted": "立即开始", + "upgradeToPro": "升级至专业版", + "upgradeToBusiness": "升级至商业版", + "mostPopular": "最受欢迎" + }, + "pricingSection": { + "creator": "创作者", + "pro": "专业版", + "business": "商业版", + "chooseYourPlan": "选择适合你的计划", + "forIndividualsOrTeams": "无论你是个人创作者还是大型团队,我们都有满足你需求的方案。", + "visitPricingPage": "访问定价页", + "monthlyCreatorFeatures": { + "points": "2,000 积分每月", + "aiVoiceSynthesis": "AI语音合成", + "twoSpeakers": "最多2位说话人", + "commercialLicense": "商业许可", + "audioDownload": "音频下载" + }, + "monthlyProFeatures": { + "points": "5,000 积分每月", + "aiVoiceSynthesis": "AI语音合成", + "multiSpeakers": "多说话人支持", + "commercialLicense": "商业许可", + "audioDownload": "音频下载", + "advancedVoices": "高级音色", + "storytellingMode": "说书模式" + }, + "monthlyBusinessFeatures": { + "points": "12,000 积分每月", + "aiVoiceSynthesis": "AI 语音合成", + "multiSpeakers": "多说话人支持", + "commercialLicense": "商业许可", + "dedicatedAccountManager": "专属客户经理", + "audioDownload": "音频下载", + "advancedVoices": "高级语音", + "storytellingMode": "说书模式", + "apiAccess": "API访问" + }, + "comingSoon": "(即将推出)" + }, + "settingsForm": { + "settings": "设置", + "apiSettingsDescription": "配置播客生成器的API设置和TTS服务", + "generalSettings": "通用设置", + "inputYourOpenAIAPIKey": "输入您的OpenAI API Key", + "model": "模型", + "selectOrEnterModelName": "选择或输入模型名称", + "customModelInput": "输入自定义模型", + "optionalCustomBaseURL": "可选:自定义API基础URL", + "ttsServiceSettings": "TTS服务设置", + "webAPITTSServices": "网络 API TTS 服务", + "edgeTTS": "Edge TTS", + "edgeTTSDescription": "基于微软Edge的TTS免费服务,提供高质量语音合成。", + "doubaoTTS": "Doubao TTS", + "doubaoTTSDescription": "由火山引擎提供支持的语音合成服务,baseUrl=https://openspeech.bytedance.com/api/v3/tts/unidirectional", + "inputDoubaoAppID": "输入Doubao App ID", + "inputDoubaoAccessKey": "输入Doubao Access Key", + "minimaxTTS": "Minimax TTS", + "minimaxTTSDescription": "由Minimax提供支持的语音合成服务,baseUrl=https://api.minimaxi.com/v1/t2a_v2", + "inputMinimaxGroupID": "输入Minimax Group ID", + "inputMinimaxAPIKey": "输入Minimax API Key", + "fishTTS": "Fish TTS", + "fishTTSDescription": "由FishAudio提供支持的语音合成服务,baseUrl=https://api.fish.audio/v1/tts", + "inputFishTTSAPIKey": "输入Fish TTS API Key", + "geminiTTS": "Gemini TTS", + "geminiTTSDescription": "由Google Gemini提供支持的语音合成服务,baseUrl=https://generativelanguage.googleapis.com/v1beta/models", + "inputGeminiAPIKey": "输入Gemini API Key", + "localAPITTSServices": "本地 API TTS 服务", + "indexTTS": "Index TTS", + "indexTTSDescription": "用于本地部署的IndexTTS服务,提供自定义语音合成能力。", + "reset": "重置", + "saving": "保存中...", + "saveSettings": "保存设置", + "settingsSavedSuccessfully": "设置保存成功!", + "errorSavingSettings": "保存设置时出现错误,请重试", + "configurationNotes": "配置说明", + "apiKeyRequired": "API Key 是必填项,用于调用OpenAI服务生成播客脚本", + "ttsOptional": "TTS服务配置为可选项,未配置的服务将不会在语音选择中显示", + "emptyFieldsNull": "空白字段将被保存为 null 值", + "settingsApplyImmediately": "配置保存后将立即生效,无需重启应用", + "apiKey": "API Key", + "baseURL": "Base URL", + "appID": "App ID", + "accessKey": "Access Key", + "groupID": "Group ID" + }, + "shareButton": { + "copySuccess": "复制成功", + "pageLinkCopied": "页面链接已复制到剪贴板!", + "copyFailed": "复制失败", + "cannotCopyPageLink": "无法复制页面链接到剪贴板。" + }, + "sidebar": { + "expandSidebar": "展开侧边栏", + "collapseSidebar": "收起侧边栏", + "home": "首页", + "library": "资料库", + "explore": "探索", + "pricing": "定价", + "points": "积分", + "ttsSettings": "TTS设置", + "github": "Github", + "twitter": "Twitter", + "tiktok": "抖音", + "email": "邮件", + "login": "登录", + "logout": "注销", + "areYouSureToLogout": "确定要注销吗?", + "cancel": "取消", + "confirmLogout": "注销", + "sessionExpired": "会话已过期,正在注销...", + "user": "用户", + "clickAvatarToLogout": "点击头像注销", + "lessThanSMSizeCannotExpand": "小于sm尺寸不可展开", + "showMore": "显示更多" + }, + "toast": { + "title": "通知", + "message": "这是一条通知消息。" + }, + "voicesModal": { + "selectSpeaker": "选择说话人", + "all": "全部", + "male": "男", + "female": "女", + "chinese": "中文 (zh)", + "english": "英文 (en)", + "japanese": "日文 (ja)", + "close": "关闭", + "searchVoices": "搜索声音...", + "noMatchingVoices": "未找到匹配的声音。", + "language": "语言", + "unknown": "未知", + "host": "主持人", + "confirmSelection": "确认选择", + "max5Speakers": "最多只能选择5个说话人。", + "searchVoicesPlaceholder": "搜索声音...", + "maxVoicesAlert": "最多只能选择5个说话人。", + "delete": "删除", + "presenter": "主讲人" + } +} \ No newline at end of file diff --git a/web/public/locales/zh-CN/contact.json b/web/public/locales/zh-CN/contact.json new file mode 100644 index 0000000..984217e --- /dev/null +++ b/web/public/locales/zh-CN/contact.json @@ -0,0 +1,10 @@ +{ + "contact_us_title": "联系我们 - PodcastHub", + "contact_us_description": "有任何问题或建议?请随时联系 PodcastHub 团队。我们期待您的声音。", + "contact_us_heading": "联系我们", + "contact_us_subheading": "我们很乐意听取您的意见。无论是问题、建议还是合作机会,请随时通过以下方式与我们联系。", + "email_title": "电子邮箱", + "email_description": "对于一般查询和支持,请发送邮件至:", + "social_media_title": "社交媒体", + "social_media_description": "在社交网络上关注我们,获取最新动态:" +} \ No newline at end of file diff --git a/web/public/locales/zh-CN/errors.json b/web/public/locales/zh-CN/errors.json new file mode 100644 index 0000000..480510d --- /dev/null +++ b/web/public/locales/zh-CN/errors.json @@ -0,0 +1,49 @@ +{ + "invalid_filename": "无效的文件名", + "unsupported_file_type": "不支持的文件类型", + "file_not_found": "文件不存在", + "internal_server_error": "服务器内部错误" +, + "unauthorized": "未授权", + "invalid_status": "无效的状态", + "invalid_request_parameters": "无效的请求参数", + "request_too_old_or_future": "请求过旧或在未来", + "points_deducted_successfully": "积分扣除成功", + "insufficient_points": "积分不足", + "daily_sign_in": "每日签到", + "already_signed_in_today": "今日已签到", + "points_added_successfully": "积分添加成功" +, + "config_files_list_success": "配置文件列表获取成功", + "config_files_list_error": "获取配置文件列表失败", + "invalid_config_file_name": "无效的配置文件名", + "config_file_read_success": "配置文件读取成功", + "read_config_file_error": "读取配置文件失败" +, + "missing_file_name_parameter": "缺少 file_name 查询参数", + "internal_server_error_backend_connection": "内部服务器错误或无法连接到后端服务" +, + "user_not_logged_in_or_session_expired": "用户未登录或会话已过期", + "request_body_cannot_be_empty": "请求正文不能为空", + "tts_provider_cannot_be_empty": "TTS服务提供商不能为空", + "please_select_at_least_one_speaker": "请至少选择一位播客说话人", + "invalid_speaker_config_format": "播客说话人配置格式无效", + "insufficient_points_for_podcast": "积分不足,生成一个播客需要 {{pointsNeeded}} 积分,您当前只有 {{currentPoints}} 积分。", + "invalid_output_language": "无效的输出语言", + "invalid_podcast_duration": "无效的播客时长", + "missing_frontend_tts_config": "缺少前端传入的TTS配置信息", + "incomplete_backend_tts_config": "后端TTS配置不完整,请检查后端配置文件。", + "internal_server_error_default": "服务器内部错误" +, + "failed_to_get_task_status": "获取任务状态失败" +, + "insufficient_points_raw": "积分不足" +, + "forbidden_user_id": "禁止:该用户ID无权访问此资源", + "invalid_request_parameters_add_points": "无效的请求参数。`userId`、`pointsToAdd`(正数)、`reasonCode`和`description`是必需的。", + "points_added_successfully_to_user": "积分已成功添加到用户 {{userId}}" +, + "invalid_pagination_parameters": "无效的分页参数" +, + "cannot_read_tts_provider_config": "无法读取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 new file mode 100644 index 0000000..854d1cf --- /dev/null +++ b/web/public/locales/zh-CN/home.json @@ -0,0 +1,26 @@ +{ + "podcastGenerationFailed": "播客生成失败,请重试", + "podcastTagsPlaceholder": "待生成的播客标签", + "configErrorTitle": "配置错误", + "configErrorMessage": "API Key 或模型未设置,请前往设置页填写。", + "error": { + "401": "生成播客失败,请检查API Key是否正确,或登录状态。", + "402": "生成播客失败,请检查积分是否足够。", + "403": "生成播客失败,请登录后重试。", + "409": "生成播客失败,有正在进行中的任务", + "backend": "生成播客失败,请检查后端服务或配置", + "generationFailed": "生成失败", + "noTaskId": "生成任务失败,未返回任务ID", + "unknown": "未知错误", + "dataProcessing": "数据处理失败", + "cantProcessPodcastList": "无法处理播客列表数据" + }, + "taskCreatedTitle": "任务已创建", + "taskCreatedMessage": "播客生成任务已启动,任务ID", + "recentlyGenerated": "最近生成", + "dataRetentionWarning": "数据只保留30分钟,请尽快下载保存", + "saveSuccessTitle": "保存成功", + "saveErrorTitle": "保存失败", + "pageInDevelopment": "页面开发中", + "featureComingSoon": "该功能正在开发中,敬请期待。" +} \ No newline at end of file diff --git a/web/public/locales/zh-CN/layout.json b/web/public/locales/zh-CN/layout.json new file mode 100644 index 0000000..29feaee --- /dev/null +++ b/web/public/locales/zh-CN/layout.json @@ -0,0 +1,5 @@ +{ + "title": "PodcastHub: 您的AI播客创作平台 - 轻松将文字转化为高质量播客音频,支持多种语音和风格,让创意触手可及", + "description": "PodcastHub 利用尖端AI技术,为您的创意提供无限可能。轻松将文字和想法转化为专业品质的播客音频,支持多种个性化语音和风格选择。立即体验高效创作,让您的声音在全球范围内传播,吸引更多听众,并简化您的播客制作流程。", + "keywords": "播客,AI,语音合成,TTS,音频生成" +} \ No newline at end of file diff --git a/web/public/locales/zh-CN/privacy.json b/web/public/locales/zh-CN/privacy.json new file mode 100644 index 0000000..976767c --- /dev/null +++ b/web/public/locales/zh-CN/privacy.json @@ -0,0 +1,84 @@ +{ + "privacy_policy": { + "title": "隐私政策 - PodcastHub", + "description": "了解 PodcastHub 如何保护您的隐私。我们致力于透明化地处理您的数据。", + "last_updated": "最近更新日期:2025年8月21日", + "intro_paragraph": "感谢您选择 PodcastHub!我们深知隐私对您的重要性。本隐私政策(以下简称“本政策”)旨在向您说明我们如何收集、使用、存储、共享和保护您的个人信息。请在使用我们的服务前仔细阅读并理解本政策。", + "section1": { + "title": "1. 我们收集的信息", + "intro": "为了向您提供和优化我们的服务,我们会收集以下类型的信息:", + "point1": { + "heading": "您主动提供的信息", + "content": ":当您注册账户、使用服务或与我们联系时,您可能会提供个人信息,如您的姓名、电子邮件地址、密码、以及您为生成播客而上传的文本内容。" + }, + "point2": { + "heading": "我们自动收集的信息", + "content": ":当您使用服务时,我们的服务器会自动记录某些信息,包括您的 IP 地址、浏览器类型与版本、操作系统、设备信息、访问日期和时间、以及您在服务中的交互数据(如点击流、功能使用频率等)。" + }, + "point3": { + "heading": "Cookies 和类似技术", + "content": ":我们使用 Cookies 来存储您的偏好设置、维持登录状态并分析使用情况,以改善用户体验。您可以根据自己的偏好管理或删除 Cookies。" + } + }, + "section2": { + "title": "2. 我们如何使用您的信息", + "intro": "我们将在以下目的范围内使用您的个人信息:", + "point1": { + "heading": "提供和维护服务", + "content": ":处理您的文本内容以生成播客,管理您的账户,并确保服务正常运行。" + }, + "point2": { + "heading": "改进和开发服务", + "content": ":分析使用数据,以了解用户偏好,优化现有功能,并开发新的产品和服务。" + }, + "point3": { + "heading": "与您沟通", + "content": ":向您发送重要的服务通知、账户安全提醒、更新说明或您可能感兴趣的市场营销信息(您可以选择退订)。" + }, + "point4": { + "heading": "安全保障", + "content": ":保护我们的服务、用户和公众免受欺诈、滥用和安全威胁。" + }, + "point5": { + "heading": "遵守法律义务", + "content": ":履行适用的法律法规要求。" + } + }, + "section3": { + "title": "3. 信息的共享与披露", + "intro": "我们承诺对您的信息保密,除非存在以下情况,我们不会与任何第三方分享您的个人信息:", + "point1": { + "heading": "获得您的明确同意", + "content": ":在获得您明确同意后,我们会与其他方共享您的信息。" + }, + "point2": { + "heading": "法律要求", + "content": ":根据法律法规、法律程序的要求或政府主管部门的强制性要求,我们可能会对外披露您的个人信息。" + }, + "point3": { + "heading": "服务提供商", + "content": ":我们可能会与可信赖的第三方服务提供商(如云存储、数据分析服务)共享必要的信息,以协助我们提供和改进服务。这些提供商有义务遵守我们的数据保护标准。" + }, + "point4": { + "heading": "保护我们的权利", + "content": ":为执行我们的服务条款、保护我们的权利、财产或安全,以及保护我们的用户或公众免受伤害,我们可能会在必要的范围内共享信息。" + } + }, + "section4": { + "title": "4. 数据安全", + "content": "我们采用了行业标准的安全措施来保护您的信息,防止未经授权的访问、披露、使用、修改或销毁。这些措施包括数据加密、访问控制和定期的安全审查。然而,没有任何互联网传输或电子存储方法是 100% 安全的,因此我们无法保证其绝对安全。" + }, + "section5": { + "title": "5. 您的权利", + "content": "您对您的个人信息享有多种权利,包括访问、更正、删除您的账户信息。您可以通过账户设置或联系我们来行使这些权利。" + }, + "section6": { + "title": "6. 政策变更", + "content": "我们可能会不时修订本隐私政策。任何重大变更,我们都会通过在网站上发布醒目通知或向您发送电子邮件的方式通知您。我们鼓励您定期查看本页面以获取最新信息。" + }, + "section7": { + "title": "7. 联系我们", + "content": "如果您对本隐私政策或我们的隐私实践有任何疑问或疑虑,请随时通过我们的联系页面与我们取得联系。" + } + } +} \ No newline at end of file diff --git a/web/public/locales/zh-CN/terms.json b/web/public/locales/zh-CN/terms.json new file mode 100644 index 0000000..4530ab3 --- /dev/null +++ b/web/public/locales/zh-CN/terms.json @@ -0,0 +1,62 @@ +{ + "terms_of_service": { + "title": "使用条款 - PodcastHub", + "description": "欢迎了解 PodcastHub 的使用条款。本条款旨在保护用户与平台的共同利益。", + "heading": "PodcastHub 使用条款", + "last_updated": "最近更新日期:2025年8月21日", + "intro_paragraph": "欢迎使用 PodcastHub!我们提供领先的 AI 技术,将您的文本内容转换为高质量的播客音频(以下简称“服务”)。在使用我们的服务之前,请仔细阅读以下使用条款(以下简称“本条款”)。通过访问或使用我们的服务,即表示您同意受本条款的约束。", + "section1": { + "title": "1. 服务概述", + "content": "PodcastHub 是一个允许用户上传文本、配置参数并生成音频内容的平台。服务包括但不限于文本转语音(TTS)、音频处理、内容存储和分发支持。我们致力于提供稳定、高效的技术解决方案,但保留随时修改或中止服务的权利,恕不另行通知。" + }, + "section2": { + "title": "2. 用户账户与安全", + "content": "您需要注册一个账户才能使用我们的全部服务。您承诺提供准确、完整、最新的注册信息,并负责维护您的账户和密码的机密性。任何通过您账户进行的所有活动,均由您本人负责。若发现任何未经授权使用您账户的情况,请立即通知我们。" + }, + "section3": { + "title": "3. 用户行为准则", + "intro": "您同意在使用本服务时遵守所有适用的法律法规,并且不得利用本服务从事以下活动:", + "point1": "上传、生成或传播任何非法、有害、威胁、辱骂、骚扰、诽谤、淫秽、侵犯他人隐私、煽动仇恨或在种族、民族等方面令人反感的内容。", + "point2": "侵犯任何第三方的知识产权,包括但不限于版权、商标权、专利权或商业秘密。", + "point3": "冒充任何个人或实体,或以其他方式虚假陈述您与任何个人或实体的关系。", + "point4": "干扰或破坏我们的服务、服务器或与服务连接的网络,或不遵守与服务连接的网络的任何要求、程序、政策或规定。", + "point5": "从事任何可能对我们的服务基础设施造成不合理或不成比例的巨大负载的活动。" + }, + "section4": { + "title": "4. 知识产权", + "point1": { + "heading": "您的内容", + "content": ":您保留您提交给服务的所有文本内容(“用户内容”)的全部所有权和知识产权。您授予 PodcastHub 一个全球性的、非独占的、免版税的许可,以使用、复制、处理、改编、修改、发布、传输和显示您的用户内容,仅用于运营、提供和改进本服务的目的。" + }, + "point2": { + "heading": "生成内容", + "content": ":通过本服务生成的音频内容(“生成内容”)的版权归属,取决于您输入的用户内容的版权状态。我们作为技术服务提供商,不对生成内容的版权主张任何所有权。" + }, + "point3": { + "heading": "我们的服务", + "content": ":本服务及其所有相关技术、商标、徽标和内容(不包括您的内容和生成内容)均为 PodcastHub 的专有财产。未经我们事先书面同意,您不得使用。" + } + }, + "section5": { + "title": "5. 免责声明与责任限制", + "point1": "本服务按“现状”和“可用”提供,我们不作任何明示或暗示的担保,包括但不限于对适销性、特定用途适用性和不侵权的暗示担保。我们不保证服务将是不间断的、及时的、安全的或无错误的。", + "point2": "在法律允许的最大范围内,PodcastHub 及其关联公司、董事、员工或许可方在任何情况下均不对因您访问或使用本服务而导致的任何间接、附带、特殊、后果性或惩罚性损害赔偿负责,无论该等损害赔偿是基于保证、合同、侵权(包括过失)还是任何其他法律理论。" + }, + "section6": { + "title": "6. 终止服务", + "content": "我们保留随时以任何理由(包括您违反本条款)暂停或终止您访问本服务的权利,恕不另行通知。您也可以随时通过停用您的账户来终止本协议。终止后,您使用本服务的权利将立即停止。" + }, + "section7": { + "title": "7. 条款修改", + "content": "我们保留随时自行决定修改或替换本条款的权利。如果修改是重大的,我们将在新条款生效前至少提前 30 天通过电子邮件或在我们的网站上发布通知。构成重大变更的内容将由我们自行决定。" + }, + "section8": { + "title": "8. 适用法律", + "content": "本条款的订立、效力、解释、履行和争议解决均适用相关法律。" + }, + "section9": { + "title": "9. 联系我们", + "content": "如果您对本条款有任何疑问,请通过我们的联系页面与我们联系。" + } + } +} \ No newline at end of file diff --git a/web/src/app/contact/page.tsx b/web/src/app/[lang]/contact/page.tsx similarity index 75% rename from web/src/app/contact/page.tsx rename to web/src/app/[lang]/contact/page.tsx index 479dece..6a31a9d 100644 --- a/web/src/app/contact/page.tsx +++ b/web/src/app/[lang]/contact/page.tsx @@ -1,24 +1,28 @@ -import React from 'react'; +import React, { use } from 'react'; import { Metadata } from 'next'; import { AiOutlineTikTok, AiFillQqCircle, AiOutlineGithub, AiOutlineTwitter, AiFillMail } from 'react-icons/ai'; +export async function generateMetadata({ params }: { params: { lang: string } }): Promise { + const { lang } = await params; + const { t } = await (await import('../../../i18n')).useTranslation(lang, 'contact'); + return { + title: t('contact_us_title'), + description: t('contact_us_description'), + alternates: { + canonical: `/${lang}/contact`, + }, + }; +} + +import { useTranslation } from '../../../i18n'; // 导入服务端的 useTranslation /** - * 设置页面元数据。 - */ -export const metadata: Metadata = { - title: '联系我们 - PodcastHub', - description: '有任何问题或建议?请随时联系 PodcastHub 团队。我们期待您的声音。', - alternates: { - canonical: '/contact', - }, -}; +* 联系我们页面组件。 +* 优化后的版本,移除了联系表单,专注于清晰地展示联系方式。 +* 采用单栏居中布局,设计简洁、现代。 +*/ +const ContactUsPage = async ({ params: { lang } }: { params: { lang: string } }) => { + const { t } = await useTranslation(lang, 'contact'); -/** - * 联系我们页面组件。 - * 优化后的版本,移除了联系表单,专注于清晰地展示联系方式。 - * 采用单栏居中布局,设计简洁、现代。 - */ -const ContactUsPage: React.FC = () => { return (
@@ -26,10 +30,10 @@ const ContactUsPage: React.FC = () => {

- 联系我们 + {t('contact_us_heading')}

- 我们很乐意听取您的意见。无论是问题、建议还是合作机会,请随时通过以下方式与我们联系。 + {t('contact_us_subheading')}

@@ -40,10 +44,10 @@ const ContactUsPage: React.FC = () => {

- 电子邮箱 + {t('email_title')}

- 对于一般查询和支持,请发送邮件至: + {t('email_description')} {

- 社交媒体 + {t('social_media_title')}

- 在社交网络上关注我们,获取最新动态: + {t('social_media_description')}

({ lng })); +// return params; +// } + +const inter = Inter({ + subsets: ['latin'], + display: 'swap', + variable: '--font-inter', +}); + +export async function generateMetadata({ params }: { params: { lang: string } }): Promise { + const { lang } = await params; + const { t } = await useTranslation(lang, 'layout'); + return { + metadataBase: new URL('https://www.podcasthub.com'), + title: t('title'), + description: t('description'), + keywords: t('keywords').split(','), + authors: [{ name: 'PodcastHub Team' }], + icons: { + icon: '/favicon.webp', + apple: '/favicon.webp', + }, + openGraph: { + title: t('title'), + description: t('description'), + type: 'website', + }, + twitter: { + card: 'summary_large_image', + title: t('title'), + }, + alternates: { + canonical: `/${lang}`, + languages: languages.reduce((acc: Record, l) => { + acc[l] = `/${l}`; + return acc; + }, {}), + }, + }; +} + +export const viewport = { + themeColor: '#000000', + width: 'device-width', + initialScale: 1, +}; + +export default async function RootLayout({ + children, + params +}: { + children: React.ReactNode; + params: { + lang: string; + } +}) { + const { lang } = await params; + return ( + + + + + + +
+ {children} +
+ {/* Toast容器 */} +
+ {/* Modal容器 */} + @@ -171,13 +177,15 @@ interface HorizontalScrollSectionProps { onPlayPodcast?: (podcast: PodcastItem) => void; variant?: 'default' | 'compact'; onTitleClick?: (podcast: PodcastItem) => void; + lang: string; // 新增 lang 属性 } const HorizontalScrollSection: React.FC = ({ items, onPlayPodcast, variant = 'default', - onTitleClick + onTitleClick, + lang // 解构 lang 属性 }) => { const scrollRef = useRef(null); const intervalRef = useRef(null); @@ -258,6 +266,7 @@ const HorizontalScrollSection: React.FC = ({ variant === 'compact' ? 'w-80' : 'w-72' }`} onTitleClick={onTitleClick} + lang={lang} /> ))}
diff --git a/web/src/components/FooterLinks.tsx b/web/src/components/FooterLinks.tsx index 144f76f..d14411b 100644 --- a/web/src/components/FooterLinks.tsx +++ b/web/src/components/FooterLinks.tsx @@ -1,5 +1,9 @@ +"use client"; + import Link from 'next/link'; +import { usePathname } from 'next/navigation'; // 导入 usePathname import React from 'react'; +import { useTranslation } from '../i18n/client'; // 导入 useTranslation /** * FooterLinks 组件用于展示页脚的法律和联系链接。 @@ -7,12 +11,15 @@ import React from 'react'; * * @returns {React.FC} 包含链接布局的 React 函数组件。 */ -const FooterLinks: React.FC = () => { +const FooterLinks: React.FC<{ lang: string }> = ({ lang: initialLang }) => { + const { t } = useTranslation(initialLang, 'components'); // 初始化 useTranslation 并指定命名空间 + const pathname = usePathname(); + const lang = pathname === '/' ? '' : initialLang; // 如果是根目录,lang赋值为空 const links = [ - { href: '/terms', label: '使用条款' }, - { href: '/privacy', label: '隐私政策' }, - { href: '/contact', label: '联系我们' }, - { href: '#', label: '© 2025 Hex2077' }, + { href: `${lang}/terms`, label: t('footerLinks.termsOfUse') }, + { href: `${lang}/privacy`, label: t('footerLinks.privacyPolicy') }, + { href: `${lang}/contact`, label: t('footerLinks.contactUs') }, + { href: '#', label: t('footerLinks.copyright') }, ]; return ( diff --git a/web/src/components/LanguageSwitcher.tsx b/web/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..37706f6 --- /dev/null +++ b/web/src/components/LanguageSwitcher.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { useRouter, usePathname } from 'next/navigation'; // 导入 usePathname +import { useTranslation } from '../i18n/client'; // 导入自定义的 useTranslation + +interface LanguageSwitcherProps { + lang: string; // 新增 lang 属性 +} + +const LanguageSwitcher: React.FC = ({ lang }) => { + const { t, i18n } = useTranslation(lang, 'components'); // 初始化 useTranslation + const router = useRouter(); + + const switchLanguage = (locale: string) => { + // 获取当前路径,并替换语言部分 + const currentPath = usePathname(); // 使用 usePathname 获取当前路径 + const newPath = `/${locale}${currentPath.substring(currentPath.indexOf('/', 1))}`; + router.push(newPath); + }; + + return ( +
+ + +
+ ); +}; + +export default LanguageSwitcher; \ No newline at end of file diff --git a/web/src/components/LoginModal.tsx b/web/src/components/LoginModal.tsx index f77ef8f..9590b59 100644 --- a/web/src/components/LoginModal.tsx +++ b/web/src/components/LoginModal.tsx @@ -6,13 +6,16 @@ import { signIn } from '@/lib/auth-client'; import { createPortal } from "react-dom"; import { XMarkIcon } from "@heroicons/react/24/outline"; // 导入关闭图标 import { AiOutlineChrome, AiOutlineGithub } from "react-icons/ai"; // 从 react-icons/ai 导入 Google 和 GitHub 图标 +import { useTranslation } from '../i18n/client'; // 导入 useTranslation interface LoginModalProps { isOpen: boolean; onClose: () => void; + lang: string; // 新增 lang 属性 } -const LoginModal: FC = ({ isOpen, onClose }) => { +const LoginModal: FC = ({ isOpen, onClose, lang }) => { + const { t } = useTranslation(lang, 'components'); // 初始化 useTranslation 并指定命名空间 const modalRef = useRef(null); // 点击背景关闭模态框 @@ -50,7 +53,7 @@ const LoginModal: FC = ({ isOpen, onClose }) => {

- 登录您的账户 + {t('loginModal.loginToYourAccount')}

@@ -59,7 +62,7 @@ const LoginModal: FC = ({ isOpen, onClose }) => { className="w-full flex items-center justify-center gap-3 px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-lg font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors" > - 使用 Google 登录 + {t('loginModal.signInWithGoogle')}
diff --git a/web/src/components/PodcastCard.tsx b/web/src/components/PodcastCard.tsx index f49e356..0b13578 100644 --- a/web/src/components/PodcastCard.tsx +++ b/web/src/components/PodcastCard.tsx @@ -5,6 +5,7 @@ import Image from 'next/image'; import { AiFillPlayCircle, AiFillPauseCircle, AiOutlineClockCircle, AiOutlineEye, AiOutlineUser, AiFillHeart, AiOutlineEllipsis } from 'react-icons/ai'; import { cn, formatTime, formatRelativeTime } from '@/lib/utils'; import type { PodcastItem } from '@/types'; +import { useTranslation } from '../i18n/client'; // 导入 useTranslation interface PodcastCardProps { podcast: PodcastItem; @@ -14,6 +15,7 @@ interface PodcastCardProps { currentPodcast?: PodcastItem | null; isPlaying?: boolean; onTitleClick?: (podcast: PodcastItem) => void; // 新增 onTitleClick 回调 + lang: string; // 新增 lang 属性 } const PodcastCard: React.FC = ({ @@ -24,7 +26,9 @@ const PodcastCard: React.FC = ({ currentPodcast, isPlaying, onTitleClick, // 解构 onTitleClick + lang }) => { + const { t } = useTranslation(lang, 'components'); // 初始化 useTranslation 并指定命名空间 const [isLiked, setIsLiked] = useState(false); const [isHovered, setIsHovered] = useState(false); @@ -118,7 +122,7 @@ const PodcastCard: React.FC = ({ {(podcast.status === 'pending' || podcast.status === 'running') && (

- {podcast.status === 'pending' ? '播客生成排队中...' : '播客生成中...'} + {podcast.status === 'pending' ? t('podcastCard.podcastGenerationQueued') : t('podcastCard.podcastGenerating')}

)} @@ -190,6 +194,7 @@ const PodcastCard: React.FC = ({ diff --git a/web/src/components/PodcastContent.tsx b/web/src/components/PodcastContent.tsx index c459744..ce6902d 100644 --- a/web/src/components/PodcastContent.tsx +++ b/web/src/components/PodcastContent.tsx @@ -3,11 +3,13 @@ import { getAudioInfo, getUserInfo } from '@/lib/podcastApi'; import AudioPlayerControls from './AudioPlayerControls'; import PodcastTabs from './PodcastTabs'; import ShareButton from './ShareButton'; // 导入 ShareButton 组件 +import { useTranslation } from '../i18n'; // 从正确路径导入 useTranslation // 脚本解析函数 (与 page.tsx 中保持一致) const parseTranscript = ( transcript: { speaker_id: number; dialog: string }[] | undefined, - podUsers: { role: string; code: string; name: string; usedname: string }[] | undefined + podUsers: { role: string; code: string; name: string; usedname: string }[] | undefined, + t: (key: string, options?: any) => string // 传递 t 函数 ) => { if (!transcript) return []; @@ -16,7 +18,7 @@ const parseTranscript = ( if (podUsers && podUsers[item.speaker_id]) { speakerName = podUsers[item.speaker_id].usedname; // 使用 podUsers 中的 usedname 字段作为 speakerName } else { - speakerName = `Speaker ${item.speaker_id}`; // 回退到 Speaker ID + speakerName = `${t('podcastContent.speaker')} ${item.speaker_id}`; // 回退到 Speaker ID } return { id: index, speaker: speakerName, dialogue: item.dialog }; }); @@ -24,18 +26,20 @@ const parseTranscript = ( interface PodcastContentProps { fileName: string; + lang: string; // 新增 lang 属性 } -export default async function PodcastContent({ fileName }: PodcastContentProps) { - const result = await getAudioInfo(fileName); +export default async function PodcastContent({ fileName, lang }: PodcastContentProps) { + const { t } = await useTranslation(lang, 'components'); // 初始化 useTranslation + const result = await getAudioInfo(fileName, lang); if (!result.success || !result.data || result.data.status!='completed') { return (
-

无法加载播客详情:{result.error || '未知错误'}

+

{t('podcastContent.cannotLoadPodcastDetails')}{result.error || t('podcastContent.unknownError')}

- 返回首页 + {t('podcastContent.returnToHomepage')}
); @@ -44,7 +48,7 @@ export default async function PodcastContent({ fileName }: PodcastContentProps) const authId = result.data?.auth_id; // 确保 auth_id 存在且安全访问 let userInfoData = null; if (authId) { - const userInfo = await getUserInfo(authId); + const userInfo = await getUserInfo(authId, lang); if (userInfo.success && userInfo.data) { userInfoData = { name: userInfo.data.name, @@ -59,7 +63,7 @@ export default async function PodcastContent({ fileName }: PodcastContentProps) }; const audioInfo = responseData; - const parsedScript = parseTranscript(audioInfo.podcast_script?.podcast_transcripts || [], audioInfo.podUsers); + const parsedScript = parseTranscript(audioInfo.podcast_script?.podcast_transcripts || [], audioInfo.podUsers, t); // 传递 t 函数 return (
@@ -70,16 +74,16 @@ export default async function PodcastContent({ fileName }: PodcastContentProps) className="flex items-center gap-1 text-neutral-500 hover:text-black transition-colors text-sm" > - 返回首页 + {t('podcastContent.returnToHomepage')}
{/* 使用 flex 容器包裹分享和下载按钮 */} - {/* 添加分享按钮 */} + {/* 添加分享按钮 */} {audioInfo.audioUrl && ( @@ -134,12 +138,14 @@ export default async function PodcastContent({ fileName }: PodcastContentProps) {/* 3. 内容导航区和内容展示区 - 使用客户端组件 */}
); diff --git a/web/src/components/PodcastCreator.tsx b/web/src/components/PodcastCreator.tsx index b253fed..5e983b4 100644 --- a/web/src/components/PodcastCreator.tsx +++ b/web/src/components/PodcastCreator.tsx @@ -22,6 +22,7 @@ import { setItem, getItem } from '@/lib/storage'; // 引入 localStorage 工具 import { useSession } from '@/lib/auth-client'; // 引入 useSession import type { PodcastGenerationRequest, TTSConfig, Voice, SettingsFormData } from '@/types'; import { Satisfy } from 'next/font/google'; // 导入艺术字体 Satisfy +import { useTranslation } from '../i18n/client'; // 导入 useTranslation // 定义艺术字体,预加载并设置 fallback const satisfy = Satisfy({ @@ -37,6 +38,7 @@ interface PodcastCreatorProps { settings: SettingsFormData | null; // 新增 settings 属性 onSignInSuccess: () => void; // 新增 onSignInSuccess 属性 enableTTSConfigPage: boolean; // 新增 enableTTSConfigPage 属性 + lang: string; // 新增 lang 属性 } const PodcastCreator: React.FC = ({ @@ -45,19 +47,21 @@ const PodcastCreator: React.FC = ({ credits, settings, // 解构 settings 属性 onSignInSuccess, // 解构 onSignInSuccess 属性 - enableTTSConfigPage // 解构 enableTTSConfigPage 属性 + enableTTSConfigPage, // 解构 enableTTSConfigPage 属性 + lang }) => { + const { t } = useTranslation(lang, 'components'); // 初始化 useTranslation 并指定命名空间 const languageOptions = [ - { value: 'Chinese', label: '简体中文' }, - { value: 'English', label: 'English' }, - { value: 'Japanese', label: '日本語' }, + { value: 'Chinese', label: t('podcastCreator.chinese') }, + { value: 'English', label: t('podcastCreator.english') }, + // { value: 'Japanese', label: t('podcastCreator.japanese') }, ]; const durationOptions = [ - { value: 'Under 5 minutes', label: '5分钟以内' }, - { value: '5-10 minutes', label: '5-10分钟' }, - { value: '10-15 minutes', label: '10-15分钟' }, + { value: 'Under 5 minutes', label: t('podcastCreator.under5Minutes') }, + { value: '5-10 minutes', label: t('podcastCreator.between5And10Minutes') }, + { value: '10-15 minutes', label: t('podcastCreator.between10And15Minutes') }, ]; const [topic, setTopic] = useState(''); @@ -75,7 +79,21 @@ const PodcastCreator: React.FC = ({ setCustomInstructions(cachedCustomInstructions); } }, []); - const [language, setLanguage] = useState(languageOptions[0].value); + + const getInitialLanguage = (currentLang: string) => { + if (currentLang.startsWith('zh')) { + return 'Chinese'; + } + if (currentLang.startsWith('en')) { + return 'English'; + } + if (currentLang.startsWith('ja')) { + return 'Japanese'; + } + return languageOptions[0].value; // 默认选中第一个选项 + }; + + const [language, setLanguage] = useState(getInitialLanguage(lang)); const [duration, setDuration] = useState(durationOptions[0].value); const [showVoicesModal, setShowVoicesModal] = useState(false); // 新增状态 const [showLoginModal, setShowLoginModal] = useState(false); // 控制登录模态框的显示 @@ -98,16 +116,16 @@ const PodcastCreator: React.FC = ({ return; } if (!topic.trim()) { - error("主题不能为空", "请输入播客主题。"); // 使用 toast.error + error(t('podcastCreator.topicCannotBeEmpty'), t('podcastCreator.pleaseEnterPodcastTopic')); return; } if (!selectedConfig) { - error("TTS配置未选择", "请选择一个TTS配置。"); // 使用 toast.error + error(t('podcastCreator.ttsConfigNotSelected'), t('podcastCreator.pleaseSelectTTSConfig')); return; } if (!selectedPodcastVoices[selectedConfigName] || selectedPodcastVoices[selectedConfigName].length === 0) { - error("请选择说话人", "请至少选择一位播客说话人。"); // 使用 toast.error + error(t('podcastCreator.pleaseSelectSpeaker'), t('podcastCreator.pleaseSelectAtLeastOneSpeaker')); return; } @@ -138,7 +156,7 @@ const PodcastCreator: React.FC = ({ setCustomInstructions(''); setItem('podcast-custom-instructions', ''); } catch (err) { - console.error("播客生成失败:", err); + console.error(t('podcastCreator.podcastGenerationFailed'), err); } }; @@ -153,20 +171,21 @@ const PodcastCreator: React.FC = ({ method: 'POST', headers: { 'Content-Type': 'application/json', + 'x-next-locale': lang, }, }); const data = await response.json(); if (data.success) { - success("签到成功", data.message); + success(t('podcastCreator.checkInSuccess'), data.message); onSignInSuccess(); // 签到成功后调用回调 } else { - error("签到失败", data.error); + error(t('podcastCreator.checkInFailed'), data.error); } } catch (err) { console.error("签到请求失败:", err); - error("签到失败", "网络错误或服务器无响应"); + error(t('podcastCreator.checkInFailed'), t('podcastCreator.networkError')); } }; @@ -248,7 +267,7 @@ const PodcastCreator: React.FC = ({

- 给创意一个真实的声音 + {t('podcastCreator.giveVoiceToCreativity')}

{/* 模式切换按钮 todo */} @@ -290,7 +309,7 @@ const PodcastCreator: React.FC = ({ setTopic(e.target.value); setItem('podcast-topic', e.target.value); // 实时保存到 localStorage }} - placeholder="输入文字,支持Markdown格式..." + placeholder={t('podcastCreator.enterTextPlaceholder')} className="w-full h-32 resize-none border-none outline-none text-lg placeholder-neutral-400 bg-white" disabled={isGenerating} /> @@ -304,7 +323,7 @@ const PodcastCreator: React.FC = ({ setCustomInstructions(e.target.value); setItem('podcast-custom-instructions', e.target.value); // 实时保存到 localStorage }} - placeholder="添加自定义指令(可选)... 例如:固定的开场白和结束语,文案脚本语境,输出内容的重点" + placeholder={t('podcastCreator.addCustomInstructions')} className="w-full h-16 resize-none border-none outline-none text-sm placeholder-neutral-400 bg-white" disabled={isGenerating} /> @@ -325,6 +344,7 @@ const PodcastCreator: React.FC = ({ setVoices(newVoices); // 更新 voices 状态 }} className="w-full" + lang={lang} // 传递 lang /> {/* 说话人按钮 */} @@ -339,7 +359,7 @@ const PodcastCreator: React.FC = ({ )} disabled={isGenerating || !selectedConfig} > - 说话人 + {t('podcastCreator.speaker')} {/* 语言选择 */} @@ -383,7 +403,7 @@ const PodcastCreator: React.FC = ({ {/*
@@ -443,19 +463,19 @@ const PodcastCreator: React.FC = ({ onClick={handleSubmit} disabled={!topic.trim() || isGenerating} className={cn( - "btn-primary flex items-center gap-1 text-sm sm:text-base px-3 py-2 sm:px-4 sm:py-2", + "btn-primary flex items-center gap-1 text-sm px-3 py-2 sm:px-4 sm:py-2", (!topic.trim() || isGenerating) && "opacity-50 cursor-not-allowed" )} > {isGenerating ? ( <> - Biu! + {t('podcastCreator.biu')} ) : ( <> - 创作 + {t('podcastCreator.create')} )} @@ -492,12 +512,14 @@ const PodcastCreator: React.FC = ({ return newState; }); }} + lang={lang} /> )} {/* Login Modal */} setShowLoginModal(false)} + lang={lang} /> ('script'); return ( @@ -24,7 +27,7 @@ export default function PodcastTabs({ parsedScript, overviewContent }: PodcastTa }`} onClick={() => setActiveTab('script')} > - 脚本 + {t('podcastTabs.script')} {/* 大纲 */}
@@ -56,7 +59,7 @@ export default function PodcastTabs({ parsedScript, overviewContent }: PodcastTa overviewContent ? ( ) : ( -

暂无大纲内容。

+

{t('podcastTabs.noOutlineContent')}

) )} diff --git a/web/src/components/PointsOverview.tsx b/web/src/components/PointsOverview.tsx index 09a837a..2090a26 100644 --- a/web/src/components/PointsOverview.tsx +++ b/web/src/components/PointsOverview.tsx @@ -1,6 +1,7 @@ // web/src/components/PointsOverview.tsx import React from 'react'; import { UserCircleIcon, WalletIcon } from '@heroicons/react/24/outline'; +import { useTranslation } from '../i18n/client'; // 导入 useTranslation interface PointEntry { transactionId: string; @@ -18,13 +19,16 @@ interface PointsOverviewProps { image: string; }; pointHistory: PointEntry[]; + lang: string; // 新增 lang 属性 } const PointsOverview: React.FC = ({ totalPoints, user, pointHistory, + lang }) => { + const { t } = useTranslation(lang, 'components'); // 初始化 useTranslation 并指定命名空间 return (
{/* Upper Section: Total Points and User Info */} @@ -41,23 +45,23 @@ const PointsOverview: React.FC = ({
-

总积分

+

{t('pointsOverview.totalPoints')}

{totalPoints}

{/* Small text for mobile view only */}

- 仅显示最近20条积分明细。 + {t('pointsOverview.last20EntriesOnly')}

{/* Lower Section: Point Details */}

- 积分明细 + {t('pointsOverview.pointDetails')}

{pointHistory.length === 0 ? ( -

暂无积分明细。

+

{t('pointsOverview.noPointDetails')}

) : (