diff --git a/.env.example b/.env.example index 18d0070..2ce6c44 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,6 @@ GITHUB_CLIENT_SECRET= // Database DATABASE_URL= + +// DashScore +DASHSCORE_API_KEY= diff --git a/CLAUDE.md b/CLAUDE.md index 12d4a58..48d99d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,6 +93,9 @@ GITHUB_CLIENT_SECRET=your-client-secret # 数据库 DATABASE_URL=postgresql://username:password@localhost:5432/database_name + +// DashScore +DASHSCORE_API_KEY= ``` ## 重要配置细节 diff --git a/README.md b/README.md index 2a25af1..b5a14dc 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ### 国际化与辅助功能 - **next-intl** - 国际化解决方案 -- **edge-tts-universal** - 跨平台文本转语音 +- **qwen3-tts-flash** - 通义千问语音合成 ### 开发工具 - **ESLint** - 代码质量检查 diff --git a/messages/de-DE.json b/messages/de-DE.json index 84b24b2..e05cba1 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -40,8 +40,8 @@ "update": "Aktualisieren", "text1": "Text 1", "text2": "Text 2", - "locale1": "Sprache 1", - "locale2": "Sprache 2", + "language1": "Sprache 1", + "language2": "Sprache 2", "edit": "Bearbeiten", "delete": "Löschen" }, diff --git a/messages/en-US.json b/messages/en-US.json index 1ac60c9..704fe14 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -40,8 +40,8 @@ "update": "Update", "text1": "Text 1", "text2": "Text 2", - "locale1": "Locale 1", - "locale2": "Locale 2", + "language1": "Locale 1", + "language2": "Locale 2", "edit": "Edit", "delete": "Delete" }, diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 965b195..f4da705 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -40,8 +40,8 @@ "update": "Mettre à jour", "text1": "Texte 1", "text2": "Texte 2", - "locale1": "Langue 1", - "locale2": "Langue 2", + "language1": "Langue 1", + "language2": "Langue 2", "edit": "Modifier", "delete": "Supprimer" }, diff --git a/messages/it-IT.json b/messages/it-IT.json index 8a9dd6a..9939837 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -40,8 +40,8 @@ "update": "Aggiorna", "text1": "Testo 1", "text2": "Testo 2", - "locale1": "Lingua 1", - "locale2": "Lingua 2", + "language1": "Lingua 1", + "language2": "Lingua 2", "edit": "Modifica", "delete": "Elimina" }, diff --git a/messages/ja-JP.json b/messages/ja-JP.json index 174d3a7..51aba0c 100644 --- a/messages/ja-JP.json +++ b/messages/ja-JP.json @@ -40,8 +40,8 @@ "update": "更新", "text1": "テキスト1", "text2": "テキスト2", - "locale1": "言語1", - "locale2": "言語2", + "language1": "言語1", + "language2": "言語2", "edit": "編集", "delete": "削除" }, diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 67b0b62..a2226ed 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -40,8 +40,8 @@ "update": "업데이트", "text1": "텍스트 1", "text2": "텍스트 2", - "locale1": "언어 1", - "locale2": "언어 2", + "language1": "언어 1", + "language2": "언어 2", "edit": "편집", "delete": "삭제" }, diff --git a/messages/ug-CN.json b/messages/ug-CN.json index 184470f..ce42853 100644 --- a/messages/ug-CN.json +++ b/messages/ug-CN.json @@ -40,8 +40,8 @@ "update": "يېڭىلاش", "text1": "تېكىست 1", "text2": "تېكىست 2", - "locale1": "تىل 1", - "locale2": "تىل 2", + "language1": "تىل 1", + "language2": "تىل 2", "edit": "تەھرىرلەش", "delete": "ئۆچۈرۈش" }, diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 3f48cf3..b4fb3bb 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -40,8 +40,8 @@ "update": "更新", "text1": "文本1", "text2": "文本2", - "locale1": "语言1", - "locale2": "语言2", + "language1": "语言1", + "language2": "语言2", "edit": "编辑", "delete": "删除" }, diff --git a/package.json b/package.json index 81c51b7..e933c3c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "bcryptjs": "^3.0.3", "better-auth": "^1.4.10", "dotenv": "^17.2.3", - "edge-tts-universal": "^1.3.3", "lucide-react": "^0.562.0", "next": "16.1.1", "next-intl": "^4.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7d12bc..d0824bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,9 +27,6 @@ importers: dotenv: specifier: ^17.2.3 version: 17.2.3 - edge-tts-universal: - specifier: ^1.3.3 - version: 1.3.3 lucide-react: specifier: ^0.562.0 version: 0.562.0(react@19.2.3) @@ -1238,10 +1235,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1299,9 +1292,6 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1314,9 +1304,6 @@ packages: resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} engines: {node: '>=4'} - axios@1.13.2: - resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} - axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -1503,10 +1490,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -1527,9 +1510,6 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - cross-fetch@4.1.0: - resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1613,10 +1593,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -1738,10 +1714,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - edge-tts-universal@1.3.3: - resolution: {integrity: sha512-jlUvYGsJ+o93FRPWQDQa/E7jqYbuLJAKKq9qvylo0/yHE1vtp4HCSzSAamviBewmpaNHlEJm+eEMimJXfu98zw==} - engines: {node: ^18.17 || ^20.9 || >=22, npm: '>=9'} - effect@3.18.4: resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} @@ -1976,15 +1948,6 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -1993,10 +1956,6 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} - engines: {node: '>= 6'} - fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -2122,10 +2081,6 @@ packages: http-status-codes@2.3.0: resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==} - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - iconv-lite@0.7.0: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} @@ -2291,11 +2246,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isomorphic-ws@5.0.0: - resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} - peerDependencies: - ws: '*' - iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -2484,14 +2434,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -2588,15 +2530,6 @@ packages: node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - node-mock-http@1.0.3: resolution: {integrity: sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==} @@ -2821,9 +2754,6 @@ packages: proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -3120,9 +3050,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -3275,10 +3202,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - valibot@1.2.0: resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} peerDependencies: @@ -3287,12 +3210,6 @@ packages: typescript: optional: true - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3321,25 +3238,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - wsl-utils@0.1.0: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} - xml-escape@1.1.0: - resolution: {integrity: sha512-B/T4sDK8Z6aUh/qNr7mjKAwwncIljFuUP+DO/D5hloYFj+90O88z8Wf7oSucZTHxBAsC1/CTP4rtx/x1Uf72Mg==} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -4582,8 +4484,6 @@ snapshots: acorn@8.15.0: {} - agent-base@7.1.4: {} - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4675,8 +4575,6 @@ snapshots: async-function@1.0.0: {} - asynckit@0.4.0: {} - available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -4685,14 +4583,6 @@ snapshots: axe-core@4.11.0: {} - axios@1.13.2: - dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.5 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axobject-query@4.1.0: {} babel-plugin-react-compiler@1.0.0: @@ -4896,10 +4786,6 @@ snapshots: color-name@1.1.4: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - commander@12.1.0: {} concat-map@0.0.1: {} @@ -4912,12 +4798,6 @@ snapshots: cookie-es@1.2.2: {} - cross-fetch@4.1.0: - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4993,8 +4873,6 @@ snapshots: defu@6.1.4: {} - delayed-stream@1.0.0: {} - denque@2.1.0: {} destr@2.0.5: {} @@ -5031,22 +4909,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - edge-tts-universal@1.3.3: - dependencies: - axios: 1.13.2 - cross-fetch: 4.1.0 - https-proxy-agent: 7.0.6 - isomorphic-ws: 5.0.0(ws@8.18.3) - uuid: 11.1.0 - ws: 8.18.3 - xml-escape: 1.1.0 - transitivePeerDependencies: - - bufferutil - - debug - - encoding - - supports-color - - utf-8-validate - effect@3.18.4: dependencies: '@standard-schema/spec': 1.0.0 @@ -5427,8 +5289,6 @@ snapshots: flatted@3.3.3: {} - follow-redirects@1.15.11: {} - for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -5438,14 +5298,6 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.5: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - fs-constants@1.0.0: {} function-bind@1.1.2: {} @@ -5579,13 +5431,6 @@ snapshots: http-status-codes@2.3.0: {} - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - iconv-lite@0.7.0: dependencies: safer-buffer: 2.1.2 @@ -5750,10 +5595,6 @@ snapshots: isexe@2.0.0: {} - isomorphic-ws@5.0.0(ws@8.18.3): - dependencies: - ws: 8.18.3 - iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -5903,12 +5744,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mimic-response@3.1.0: {} minimatch@3.1.2: @@ -6004,10 +5839,6 @@ snapshots: node-fetch-native@1.6.7: {} - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - node-mock-http@1.0.3: {} node-releases@2.0.27: {} @@ -6252,8 +6083,6 @@ snapshots: retry: 0.12.0 signal-exit: 3.0.7 - proxy-from-env@1.1.0: {} - pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -6608,8 +6437,6 @@ snapshots: dependencies: is-number: 7.0.0 - tr46@0.0.3: {} - ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -6750,19 +6577,10 @@ snapshots: util-deprecate@1.0.2: {} - uuid@11.1.0: {} - valibot@1.2.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -6812,14 +6630,10 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.3: {} - wsl-utils@0.1.0: dependencies: is-wsl: 3.1.0 - xml-escape@1.1.0: {} - xtend@4.0.2: {} yallist@3.1.1: {} diff --git a/src/app/(features)/memorize/Memorize.tsx b/src/app/(features)/memorize/Memorize.tsx index 4f13dbc..d9522cb 100644 --- a/src/app/(features)/memorize/Memorize.tsx +++ b/src/app/(features)/memorize/Memorize.tsx @@ -2,8 +2,7 @@ import { useState } from "react"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; -import { getTTSAudioUrl } from "@/lib/browser/tts"; -import { VOICES } from "@/config/locales"; +import { getTTSUrl, TTS_SUPPORTED_LANGUAGES } from "@/lib/server/bigmodel/tts"; import { useTranslations } from "next-intl"; import localFont from "next/font/local"; import { isNonNegativeInteger, SeededRandom } from "@/lib/utils"; @@ -59,20 +58,32 @@ const Memorize: React.FC = ({ textPairs }) => { if (show === "answer") { const newIndex = (index + 1) % getTextPairs().length; setIndex(newIndex); - if (dictation) - getTTSAudioUrl( - getTextPairs()[newIndex][reverse ? "text2" : "text1"], - VOICES.find( - (v) => - v.locale === - getTextPairs()[newIndex][ - reverse ? "locale2" : "locale1" - ], - )!.short_name, - ).then((url) => { + if (dictation) { + const textPair = getTextPairs()[newIndex]; + const language = textPair[reverse ? "language2" : "language1"]; + const text = textPair[reverse ? "text2" : "text1"]; + + // 映射语言到 TTS 支持的格式 + const languageMap: Record = { + "chinese": "Chinese", + "english": "English", + "japanese": "Japanese", + "korean": "Korean", + "french": "French", + "german": "German", + "italian": "Italian", + "portuguese": "Portuguese", + "spanish": "Spanish", + "russian": "Russian", + }; + + const ttsLanguage = languageMap[language?.toLowerCase()] || "Auto"; + + getTTSUrl(text, ttsLanguage).then((url) => { load(url); play(); }); + } } setShow(show === "question" ? "answer" : "question"); }; diff --git a/src/app/(features)/text-speaker/page.tsx b/src/app/(features)/text-speaker/page.tsx index ede969a..6758e8f 100644 --- a/src/app/(features)/text-speaker/page.tsx +++ b/src/app/(features)/text-speaker/page.tsx @@ -12,7 +12,6 @@ import { ChangeEvent, useEffect, useRef, useState } from "react"; import z from "zod"; import SaveList from "./SaveList"; -import { VOICES } from "@/config/locales"; import { useTranslations } from "next-intl"; import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators"; import { genIPA, genLanguage } from "@/lib/server/bigmodel/translatorActions"; @@ -160,7 +159,7 @@ export default function TextSpeakerPage() { const handleUseItem = (item: z.infer) => { if (textareaRef.current) textareaRef.current.value = item.text; textRef.current = item.text; - setLanguage(item.locale); + setLanguage(item.language); setIPA(item.ipa || ""); if (objurlRef.current) URL.revokeObjectURL(objurlRef.current); objurlRef.current = null; @@ -202,12 +201,12 @@ export default function TextSpeakerPage() { } else if (theIPA.length === 0) { save.push({ text: textRef.current, - locale: theLanguage as string, + language: theLanguage as string, }); } else { save.push({ text: textRef.current, - locale: theLanguage as string, + language: theLanguage as string, ipa: theIPA, }); } diff --git a/src/app/(features)/translator/AddToFolder.tsx b/src/app/(features)/translator/AddToFolder.tsx index 382f263..0d25875 100644 --- a/src/app/(features)/translator/AddToFolder.tsx +++ b/src/app/(features)/translator/AddToFolder.tsx @@ -57,8 +57,8 @@ const AddToFolder: React.FC = ({ item, setShow }) => { createPair({ text1: item.text1, text2: item.text2, - locale1: item.locale1, - locale2: item.locale2, + language1: item.language1, + language2: item.language2, folder: { connect: { id: folder.id, diff --git a/src/app/(features)/translator/page.tsx b/src/app/(features)/translator/page.tsx index 4090ee2..687213f 100644 --- a/src/app/(features)/translator/page.tsx +++ b/src/app/(features)/translator/page.tsx @@ -3,7 +3,6 @@ import { LightButton } from "@/components/ui/buttons"; import { IconClick } from "@/components/ui/buttons"; import IMAGES from "@/config/images"; -import { VOICES } from "@/config/locales"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; import { TranslationHistorySchema } from "@/lib/interfaces"; import { tlsoPush, tlso } from "@/lib/browser/localStorageOperators"; @@ -23,7 +22,7 @@ import FolderSelector from "./FolderSelector"; import { createPair } from "@/lib/server/services/pairService"; import { shallowEqual } from "@/lib/utils"; import { authClient } from "@/lib/auth-client"; -import { getTTSUrl } from "@/lib/server/bigmodel/tts"; +import { getTTSUrl, TTS_SUPPORTED_LANGUAGES } from "@/lib/server/bigmodel/tts"; export default function TranslatorPage() { const t = useTranslations("translator"); @@ -51,7 +50,20 @@ export default function TranslatorPage() { const tts = async (text: string, locale: string) => { if (lastTTS.current.text !== text) { try { - const url = await getTTSUrl(text, locale); + // Map language name to TTS format + let theLanguage = locale.toLowerCase().replace(/[^a-z]/g, '').replace(/^./, match => match.toUpperCase()); + + // Check if language is in TTS supported list + const supportedLanguages: TTS_SUPPORTED_LANGUAGES[] = [ + "Auto", "Chinese", "English", "German", "Italian", "Portuguese", + "Spanish", "Japanese", "Korean", "French", "Russian" + ]; + + if (!supportedLanguages.includes(theLanguage as TTS_SUPPORTED_LANGUAGES)) { + theLanguage = "Auto"; + } + + const url = await getTTSUrl(text, theLanguage as TTS_SUPPORTED_LANGUAGES); await load(url); lastTTS.current.text = text; lastTTS.current.url = url; @@ -74,15 +86,15 @@ export default function TranslatorPage() { const llmres: { text1: string | null; text2: string | null; - locale1: string | null; - locale2: string | null; + language1: string | null; + language2: string | null; ipa1: string | null; ipa2: string | null; } = { text1: text1, text2: null, - locale1: null, - locale2: null, + language1: null, + language2: null, ipa1: null, ipa2: null, }; @@ -92,21 +104,21 @@ export default function TranslatorPage() { // 检查更新历史记录 const checkUpdateLocalStorage = () => { if (historyUpdated) return; - if (llmres.text1 && llmres.text2 && llmres.locale1 && llmres.locale2) { + if (llmres.text1 && llmres.text2 && llmres.language1 && llmres.language2) { setHistory( tlsoPush({ text1: llmres.text1, text2: llmres.text2, - locale1: llmres.locale1, - locale2: llmres.locale2, + language1: llmres.language1, + language2: llmres.language2, }), ); if (autoSave && autoSaveFolderId) { createPair({ text1: llmres.text1, text2: llmres.text2, - locale1: llmres.locale1, - locale2: llmres.locale2, + language1: llmres.language1, + language2: llmres.language2, folder: { connect: { id: autoSaveFolderId, @@ -143,10 +155,10 @@ export default function TranslatorPage() { setTresult(text2); // 生成两个locale genLocale(text1).then((locale) => { - updateState("locale1", locale); + updateState("language1", locale); }); genLocale(text2).then((locale) => { - updateState("locale2", locale); + updateState("language2", locale); }); // 生成俩IPA if (genIpa) { @@ -202,7 +214,7 @@ export default function TranslatorPage() { onClick={() => { const t = taref.current?.value; if (!t) return; - tts(t, tlso.get().find((v) => v.text1 === t)?.locale1 || ""); + tts(t, tlso.get().find((v) => v.text1 === t)?.language1 || ""); }} > @@ -240,7 +252,7 @@ export default function TranslatorPage() { onClick={() => { tts( tresult, - tlso.get().find((v) => v.text2 === tresult)?.locale2 || "", + tlso.get().find((v) => v.text2 === tresult)?.language2 || "", ); }} > diff --git a/src/app/folders/[folder_id]/AddTextPairModal.tsx b/src/app/folders/[folder_id]/AddTextPairModal.tsx index 0b8e867..01be030 100644 --- a/src/app/folders/[folder_id]/AddTextPairModal.tsx +++ b/src/app/folders/[folder_id]/AddTextPairModal.tsx @@ -11,8 +11,8 @@ interface AddTextPairModalProps { onAdd: ( text1: string, text2: string, - locale1: string, - locale2: string, + language1: string, + language2: string, ) => void; } @@ -24,8 +24,8 @@ export default function AddTextPairModal({ const t = useTranslations("folder_id"); const input1Ref = useRef(null); const input2Ref = useRef(null); - const [locale1, setLocale1] = useState("en-US"); - const [locale2, setLocale2] = useState("zh-CN"); + const [language1, setLanguage1] = useState("english"); + const [language2, setLanguage2] = useState("chinese"); if (!isOpen) return null; @@ -33,8 +33,8 @@ export default function AddTextPairModal({ if ( !input1Ref.current?.value || !input2Ref.current?.value || - !locale1 || - !locale2 + !language1 || + !language2 ) return; @@ -44,14 +44,14 @@ export default function AddTextPairModal({ if ( typeof text1 === "string" && typeof text2 === "string" && - typeof locale1 === "string" && - typeof locale2 === "string" && + typeof language1 === "string" && + typeof language2 === "string" && text1.trim() !== "" && text2.trim() !== "" && - locale1.trim() !== "" && - locale2.trim() !== "" + language1.trim() !== "" && + language2.trim() !== "" ) { - onAdd(text1, text2, locale1, locale2); + onAdd(text1, text2, language1, language2); input1Ref.current.value = ""; input2Ref.current.value = ""; } @@ -84,12 +84,12 @@ export default function AddTextPairModal({
- {t("locale1")} - + {t("language1")} +
- {t("locale2")} - + {t("language2")} +
{t("add")} diff --git a/src/app/folders/[folder_id]/InFolder.tsx b/src/app/folders/[folder_id]/InFolder.tsx index b05554e..7a8963a 100644 --- a/src/app/folders/[folder_id]/InFolder.tsx +++ b/src/app/folders/[folder_id]/InFolder.tsx @@ -140,14 +140,14 @@ export default function InFolder({ folderId }: { folderId: number }) { onAdd={async ( text1: string, text2: string, - locale1: string, - locale2: string, + language1: string, + language2: string, ) => { await createPair({ text1: text1, text2: text2, - language1: locale1, - language2: locale2, + language1: language1, + language2: language2, folder: { connect: { id: folderId, diff --git a/src/app/folders/[folder_id]/UpdateTextPairModal.tsx b/src/app/folders/[folder_id]/UpdateTextPairModal.tsx index 6661caa..9ef233e 100644 --- a/src/app/folders/[folder_id]/UpdateTextPairModal.tsx +++ b/src/app/folders/[folder_id]/UpdateTextPairModal.tsx @@ -23,8 +23,8 @@ export default function UpdateTextPairModal({ const t = useTranslations("folder_id"); const input1Ref = useRef(null); const input2Ref = useRef(null); - const [locale1, setLocale1] = useState(textPair.language1); - const [locale2, setLocale2] = useState(textPair.language2); + const [language1, setLanguage1] = useState(textPair.language1); + const [language2, setLanguage2] = useState(textPair.language2); if (!isOpen) return null; @@ -32,8 +32,8 @@ export default function UpdateTextPairModal({ if ( !input1Ref.current?.value || !input2Ref.current?.value || - !locale1 || - !locale2 + !language1 || + !language2 ) return; @@ -43,14 +43,14 @@ export default function UpdateTextPairModal({ if ( typeof text1 === "string" && typeof text2 === "string" && - typeof locale1 === "string" && - typeof locale2 === "string" && + typeof language1 === "string" && + typeof language2 === "string" && text1.trim() !== "" && text2.trim() !== "" && - locale1.trim() !== "" && - locale2.trim() !== "" + language1.trim() !== "" && + language2.trim() !== "" ) { - onUpdate(textPair.id, { text1, text2, locale1, locale2 }); + onUpdate(textPair.id, { text1, text2, language1, language2 }); } }; return ( @@ -88,12 +88,12 @@ export default function UpdateTextPairModal({ >
- {t("locale1")} - + {t("language1")} +
- {t("locale2")} - + {t("language2")} +
{t("update")} diff --git a/src/components/ui/LocaleSelector.tsx b/src/components/ui/LocaleSelector.tsx index 3b13af3..bb1f47f 100644 --- a/src/components/ui/LocaleSelector.tsx +++ b/src/components/ui/LocaleSelector.tsx @@ -1,10 +1,16 @@ -import { LOCALES } from "@/config/locales"; +import { useState } from "react"; -const COMMON_LOCALES = [ - { label: "中文", value: "zh-CN" }, - { label: "英文", value: "en-US" }, - { label: "意大利语", value: "it-IT" }, - { label: "日语", value: "ja-JP" }, +const COMMON_LANGUAGES = [ + { label: "中文", value: "chinese" }, + { label: "英文", value: "english" }, + { label: "意大利语", value: "italian" }, + { label: "日语", value: "japanese" }, + { label: "韩语", value: "korean" }, + { label: "法语", value: "french" }, + { label: "德语", value: "german" }, + { label: "西班牙语", value: "spanish" }, + { label: "葡萄牙语", value: "portuguese" }, + { label: "俄语", value: "russian" }, { label: "其他", value: "other" }, ]; @@ -14,34 +20,50 @@ interface LocaleSelectorProps { } export function LocaleSelector({ value, onChange }: LocaleSelectorProps) { - const isCommonLocale = COMMON_LOCALES.some((l) => l.value === value && l.value !== "other"); - const showFullList = value === "other" || !isCommonLocale; + const [customInput, setCustomInput] = useState(""); + const isCommonLanguage = COMMON_LANGUAGES.some((l) => l.value === value && l.value !== "other"); + const showCustomInput = value === "other" || !isCommonLanguage; + + // 计算输入框的值:如果是"other"使用自定义输入,否则使用外部传入的值 + const inputValue = value === "other" ? customInput : value; + + // 处理自定义输入 + const handleCustomInputChange = (inputValue: string) => { + setCustomInput(inputValue); + onChange(inputValue); + }; + + // 当选择常见语言或"其他"时 + const handleSelectChange = (selectedValue: string) => { + if (selectedValue === "other") { + setCustomInput(""); + onChange("other"); + } else { + onChange(selectedValue); + } + }; return (
- {showFullList && ( - handleCustomInputChange(e.target.value)} + placeholder="请输入语言名称" className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f] mt-2" - > - {LOCALES.map((locale) => ( - - ))} - + /> )}
); diff --git a/src/config/locales.ts b/src/config/locales.ts deleted file mode 100644 index e66cd2f..0000000 --- a/src/config/locales.ts +++ /dev/null @@ -1,1220 +0,0 @@ -const VOICES = [ - { - locale: "af-ZA", - short_name: "af-ZA-AdriNeural", - }, - { - locale: "af-ZA", - short_name: "af-ZA-WillemNeural", - }, - { - locale: "sq-AL", - short_name: "sq-AL-AnilaNeural", - }, - { - locale: "sq-AL", - short_name: "sq-AL-IlirNeural", - }, - { - locale: "am-ET", - short_name: "am-ET-AmehaNeural", - }, - { - locale: "am-ET", - short_name: "am-ET-MekdesNeural", - }, - { - locale: "ar-DZ", - short_name: "ar-DZ-AminaNeural", - }, - { - locale: "ar-DZ", - short_name: "ar-DZ-IsmaelNeural", - }, - { - locale: "ar-BH", - short_name: "ar-BH-AliNeural", - }, - { - locale: "ar-BH", - short_name: "ar-BH-LailaNeural", - }, - { - locale: "ar-EG", - short_name: "ar-EG-SalmaNeural", - }, - { - locale: "ar-EG", - short_name: "ar-EG-ShakirNeural", - }, - { - locale: "ar-IQ", - short_name: "ar-IQ-BasselNeural", - }, - { - locale: "ar-IQ", - short_name: "ar-IQ-RanaNeural", - }, - { - locale: "ar-JO", - short_name: "ar-JO-SanaNeural", - }, - { - locale: "ar-JO", - short_name: "ar-JO-TaimNeural", - }, - { - locale: "ar-KW", - short_name: "ar-KW-FahedNeural", - }, - { - locale: "ar-KW", - short_name: "ar-KW-NouraNeural", - }, - { - locale: "ar-LB", - short_name: "ar-LB-LaylaNeural", - }, - { - locale: "ar-LB", - short_name: "ar-LB-RamiNeural", - }, - { - locale: "ar-LY", - short_name: "ar-LY-ImanNeural", - }, - { - locale: "ar-LY", - short_name: "ar-LY-OmarNeural", - }, - { - locale: "ar-MA", - short_name: "ar-MA-JamalNeural", - }, - { - locale: "ar-MA", - short_name: "ar-MA-MounaNeural", - }, - { - locale: "ar-OM", - short_name: "ar-OM-AbdullahNeural", - }, - { - locale: "ar-OM", - short_name: "ar-OM-AyshaNeural", - }, - { - locale: "ar-QA", - short_name: "ar-QA-AmalNeural", - }, - { - locale: "ar-QA", - short_name: "ar-QA-MoazNeural", - }, - { - locale: "ar-SA", - short_name: "ar-SA-HamedNeural", - }, - { - locale: "ar-SA", - short_name: "ar-SA-ZariyahNeural", - }, - { - locale: "ar-SY", - short_name: "ar-SY-AmanyNeural", - }, - { - locale: "ar-SY", - short_name: "ar-SY-LaithNeural", - }, - { - locale: "ar-TN", - short_name: "ar-TN-HediNeural", - }, - { - locale: "ar-TN", - short_name: "ar-TN-ReemNeural", - }, - { - locale: "ar-AE", - short_name: "ar-AE-FatimaNeural", - }, - { - locale: "ar-AE", - short_name: "ar-AE-HamdanNeural", - }, - { - locale: "ar-YE", - short_name: "ar-YE-MaryamNeural", - }, - { - locale: "ar-YE", - short_name: "ar-YE-SalehNeural", - }, - { - locale: "az-AZ", - short_name: "az-AZ-BabekNeural", - }, - { - locale: "az-AZ", - short_name: "az-AZ-BanuNeural", - }, - { - locale: "bn-BD", - short_name: "bn-BD-NabanitaNeural", - }, - { - locale: "bn-BD", - short_name: "bn-BD-PradeepNeural", - }, - { - locale: "bn-IN", - short_name: "bn-IN-BashkarNeural", - }, - { - locale: "bn-IN", - short_name: "bn-IN-TanishaaNeural", - }, - { - locale: "bs-BA", - short_name: "bs-BA-GoranNeural", - }, - { - locale: "bs-BA", - short_name: "bs-BA-VesnaNeural", - }, - { - locale: "bg-BG", - short_name: "bg-BG-BorislavNeural", - }, - { - locale: "bg-BG", - short_name: "bg-BG-KalinaNeural", - }, - { - locale: "my-MM", - short_name: "my-MM-NilarNeural", - }, - { - locale: "my-MM", - short_name: "my-MM-ThihaNeural", - }, - { - locale: "ca-ES", - short_name: "ca-ES-EnricNeural", - }, - { - locale: "ca-ES", - short_name: "ca-ES-JoanaNeural", - }, - { - locale: "zh-HK", - short_name: "zh-HK-HiuGaaiNeural", - }, - { - locale: "zh-HK", - short_name: "zh-HK-HiuMaanNeural", - }, - { - locale: "zh-HK", - short_name: "zh-HK-WanLungNeural", - }, - { - locale: "zh-CN", - short_name: "zh-CN-XiaoxiaoNeural", - }, - { - locale: "zh-CN", - short_name: "zh-CN-XiaoyiNeural", - }, - { - locale: "zh-CN", - short_name: "zh-CN-YunjianNeural", - }, - { - locale: "zh-CN", - short_name: "zh-CN-YunxiNeural", - }, - { - locale: "zh-CN", - short_name: "zh-CN-YunxiaNeural", - }, - { - locale: "zh-CN", - short_name: "zh-CN-YunyangNeural", - }, - { - locale: "zh-CN-liaoning", - short_name: "zh-CN-liaoning-XiaobeiNeural", - }, - { - locale: "zh-TW", - short_name: "zh-TW-HsiaoChenNeural", - }, - { - locale: "zh-TW", - short_name: "zh-TW-YunJheNeural", - }, - { - locale: "zh-TW", - short_name: "zh-TW-HsiaoYuNeural", - }, - { - locale: "zh-CN-shaanxi", - short_name: "zh-CN-shaanxi-XiaoniNeural", - }, - { - locale: "hr-HR", - short_name: "hr-HR-GabrijelaNeural", - }, - { - locale: "hr-HR", - short_name: "hr-HR-SreckoNeural", - }, - { - locale: "cs-CZ", - short_name: "cs-CZ-AntoninNeural", - }, - { - locale: "cs-CZ", - short_name: "cs-CZ-VlastaNeural", - }, - { - locale: "da-DK", - short_name: "da-DK-ChristelNeural", - }, - { - locale: "da-DK", - short_name: "da-DK-JeppeNeural", - }, - { - locale: "nl-BE", - short_name: "nl-BE-ArnaudNeural", - }, - { - locale: "nl-BE", - short_name: "nl-BE-DenaNeural", - }, - { - locale: "nl-NL", - short_name: "nl-NL-ColetteNeural", - }, - { - locale: "nl-NL", - short_name: "nl-NL-FennaNeural", - }, - { - locale: "nl-NL", - short_name: "nl-NL-MaartenNeural", - }, - { - locale: "en-AU", - short_name: "en-AU-NatashaNeural", - }, - { - locale: "en-AU", - short_name: "en-AU-WilliamNeural", - }, - { - locale: "en-CA", - short_name: "en-CA-ClaraNeural", - }, - { - locale: "en-CA", - short_name: "en-CA-LiamNeural", - }, - { - locale: "en-HK", - short_name: "en-HK-SamNeural", - }, - { - locale: "en-HK", - short_name: "en-HK-YanNeural", - }, - { - locale: "en-IN", - short_name: "en-IN-NeerjaNeural", - }, - { - locale: "en-IN", - short_name: "en-IN-PrabhatNeural", - }, - { - locale: "en-IE", - short_name: "en-IE-ConnorNeural", - }, - { - locale: "en-IE", - short_name: "en-IE-EmilyNeural", - }, - { - locale: "en-KE", - short_name: "en-KE-AsiliaNeural", - }, - { - locale: "en-KE", - short_name: "en-KE-ChilembaNeural", - }, - { - locale: "en-NZ", - short_name: "en-NZ-MitchellNeural", - }, - { - locale: "en-NZ", - short_name: "en-NZ-MollyNeural", - }, - { - locale: "en-NG", - short_name: "en-NG-AbeoNeural", - }, - { - locale: "en-NG", - short_name: "en-NG-EzinneNeural", - }, - { - locale: "en-PH", - short_name: "en-PH-JamesNeural", - }, - { - locale: "en-PH", - short_name: "en-PH-RosaNeural", - }, - { - locale: "en-SG", - short_name: "en-SG-LunaNeural", - }, - { - locale: "en-SG", - short_name: "en-SG-WayneNeural", - }, - { - locale: "en-ZA", - short_name: "en-ZA-LeahNeural", - }, - { - locale: "en-ZA", - short_name: "en-ZA-LukeNeural", - }, - { - locale: "en-TZ", - short_name: "en-TZ-ElimuNeural", - }, - { - locale: "en-TZ", - short_name: "en-TZ-ImaniNeural", - }, - { - locale: "en-GB", - short_name: "en-GB-LibbyNeural", - }, - { - locale: "en-GB", - short_name: "en-GB-MaisieNeural", - }, - { - locale: "en-GB", - short_name: "en-GB-RyanNeural", - }, - { - locale: "en-GB", - short_name: "en-GB-SoniaNeural", - }, - { - locale: "en-GB", - short_name: "en-GB-ThomasNeural", - }, - { - locale: "en-US", - short_name: "en-US-AriaNeural", - }, - { - locale: "en-US", - short_name: "en-US-AnaNeural", - }, - { - locale: "en-US", - short_name: "en-US-ChristopherNeural", - }, - { - locale: "en-US", - short_name: "en-US-EricNeural", - }, - { - locale: "en-US", - short_name: "en-US-GuyNeural", - }, - { - locale: "en-US", - short_name: "en-US-JennyNeural", - }, - { - locale: "en-US", - short_name: "en-US-MichelleNeural", - }, - { - locale: "en-US", - short_name: "en-US-RogerNeural", - }, - { - locale: "en-US", - short_name: "en-US-SteffanNeural", - }, - { - locale: "et-EE", - short_name: "et-EE-AnuNeural", - }, - { - locale: "et-EE", - short_name: "et-EE-KertNeural", - }, - { - locale: "fil-PH", - short_name: "fil-PH-AngeloNeural", - }, - { - locale: "fil-PH", - short_name: "fil-PH-BlessicaNeural", - }, - { - locale: "fi-FI", - short_name: "fi-FI-HarriNeural", - }, - { - locale: "fi-FI", - short_name: "fi-FI-NooraNeural", - }, - { - locale: "fr-BE", - short_name: "fr-BE-CharlineNeural", - }, - { - locale: "fr-BE", - short_name: "fr-BE-GerardNeural", - }, - { - locale: "fr-CA", - short_name: "fr-CA-AntoineNeural", - }, - { - locale: "fr-CA", - short_name: "fr-CA-JeanNeural", - }, - { - locale: "fr-CA", - short_name: "fr-CA-SylvieNeural", - }, - { - locale: "fr-FR", - short_name: "fr-FR-DeniseNeural", - }, - { - locale: "fr-FR", - short_name: "fr-FR-EloiseNeural", - }, - { - locale: "fr-FR", - short_name: "fr-FR-HenriNeural", - }, - { - locale: "fr-CH", - short_name: "fr-CH-ArianeNeural", - }, - { - locale: "fr-CH", - short_name: "fr-CH-FabriceNeural", - }, - { - locale: "gl-ES", - short_name: "gl-ES-RoiNeural", - }, - { - locale: "gl-ES", - short_name: "gl-ES-SabelaNeural", - }, - { - locale: "ka-GE", - short_name: "ka-GE-EkaNeural", - }, - { - locale: "ka-GE", - short_name: "ka-GE-GiorgiNeural", - }, - { - locale: "de-AT", - short_name: "de-AT-IngridNeural", - }, - { - locale: "de-AT", - short_name: "de-AT-JonasNeural", - }, - { - locale: "de-DE", - short_name: "de-DE-AmalaNeural", - }, - { - locale: "de-DE", - short_name: "de-DE-ConradNeural", - }, - { - locale: "de-DE", - short_name: "de-DE-KatjaNeural", - }, - { - locale: "de-DE", - short_name: "de-DE-KillianNeural", - }, - { - locale: "de-CH", - short_name: "de-CH-JanNeural", - }, - { - locale: "de-CH", - short_name: "de-CH-LeniNeural", - }, - { - locale: "el-GR", - short_name: "el-GR-AthinaNeural", - }, - { - locale: "el-GR", - short_name: "el-GR-NestorasNeural", - }, - { - locale: "gu-IN", - short_name: "gu-IN-DhwaniNeural", - }, - { - locale: "gu-IN", - short_name: "gu-IN-NiranjanNeural", - }, - { - locale: "he-IL", - short_name: "he-IL-AvriNeural", - }, - { - locale: "he-IL", - short_name: "he-IL-HilaNeural", - }, - { - locale: "hi-IN", - short_name: "hi-IN-MadhurNeural", - }, - { - locale: "hi-IN", - short_name: "hi-IN-SwaraNeural", - }, - { - locale: "hu-HU", - short_name: "hu-HU-NoemiNeural", - }, - { - locale: "hu-HU", - short_name: "hu-HU-TamasNeural", - }, - { - locale: "is-IS", - short_name: "is-IS-GudrunNeural", - }, - { - locale: "is-IS", - short_name: "is-IS-GunnarNeural", - }, - { - locale: "id-ID", - short_name: "id-ID-ArdiNeural", - }, - { - locale: "id-ID", - short_name: "id-ID-GadisNeural", - }, - { - locale: "ga-IE", - short_name: "ga-IE-ColmNeural", - }, - { - locale: "ga-IE", - short_name: "ga-IE-OrlaNeural", - }, - { - locale: "it-IT", - short_name: "it-IT-DiegoNeural", - }, - { - locale: "it-IT", - short_name: "it-IT-ElsaNeural", - }, - { - locale: "it-IT", - short_name: "it-IT-IsabellaNeural", - }, - { - locale: "ja-JP", - short_name: "ja-JP-KeitaNeural", - }, - { - locale: "ja-JP", - short_name: "ja-JP-NanamiNeural", - }, - { - locale: "jv-ID", - short_name: "jv-ID-DimasNeural", - }, - { - locale: "jv-ID", - short_name: "jv-ID-SitiNeural", - }, - { - locale: "kn-IN", - short_name: "kn-IN-GaganNeural", - }, - { - locale: "kn-IN", - short_name: "kn-IN-SapnaNeural", - }, - { - locale: "kk-KZ", - short_name: "kk-KZ-AigulNeural", - }, - { - locale: "kk-KZ", - short_name: "kk-KZ-DauletNeural", - }, - { - locale: "km-KH", - short_name: "km-KH-PisethNeural", - }, - { - locale: "km-KH", - short_name: "km-KH-SreymomNeural", - }, - { - locale: "ko-KR", - short_name: "ko-KR-InJoonNeural", - }, - { - locale: "ko-KR", - short_name: "ko-KR-SunHiNeural", - }, - { - locale: "lo-LA", - short_name: "lo-LA-ChanthavongNeural", - }, - { - locale: "lo-LA", - short_name: "lo-LA-KeomanyNeural", - }, - { - locale: "lv-LV", - short_name: "lv-LV-EveritaNeural", - }, - { - locale: "lv-LV", - short_name: "lv-LV-NilsNeural", - }, - { - locale: "lt-LT", - short_name: "lt-LT-LeonasNeural", - }, - { - locale: "lt-LT", - short_name: "lt-LT-OnaNeural", - }, - { - locale: "mk-MK", - short_name: "mk-MK-AleksandarNeural", - }, - { - locale: "mk-MK", - short_name: "mk-MK-MarijaNeural", - }, - { - locale: "ms-MY", - short_name: "ms-MY-OsmanNeural", - }, - { - locale: "ms-MY", - short_name: "ms-MY-YasminNeural", - }, - { - locale: "ml-IN", - short_name: "ml-IN-MidhunNeural", - }, - { - locale: "ml-IN", - short_name: "ml-IN-SobhanaNeural", - }, - { - locale: "mt-MT", - short_name: "mt-MT-GraceNeural", - }, - { - locale: "mt-MT", - short_name: "mt-MT-JosephNeural", - }, - { - locale: "mr-IN", - short_name: "mr-IN-AarohiNeural", - }, - { - locale: "mr-IN", - short_name: "mr-IN-ManoharNeural", - }, - { - locale: "mn-MN", - short_name: "mn-MN-BataaNeural", - }, - { - locale: "mn-MN", - short_name: "mn-MN-YesuiNeural", - }, - { - locale: "ne-NP", - short_name: "ne-NP-HemkalaNeural", - }, - { - locale: "ne-NP", - short_name: "ne-NP-SagarNeural", - }, - { - locale: "nb-NO", - short_name: "nb-NO-FinnNeural", - }, - { - locale: "nb-NO", - short_name: "nb-NO-PernilleNeural", - }, - { - locale: "ps-AF", - short_name: "ps-AF-GulNawazNeural", - }, - { - locale: "ps-AF", - short_name: "ps-AF-LatifaNeural", - }, - { - locale: "fa-IR", - short_name: "fa-IR-DilaraNeural", - }, - { - locale: "fa-IR", - short_name: "fa-IR-FaridNeural", - }, - { - locale: "pl-PL", - short_name: "pl-PL-MarekNeural", - }, - { - locale: "pl-PL", - short_name: "pl-PL-ZofiaNeural", - }, - { - locale: "pt-BR", - short_name: "pt-BR-AntonioNeural", - }, - { - locale: "pt-BR", - short_name: "pt-BR-FranciscaNeural", - }, - { - locale: "pt-PT", - short_name: "pt-PT-DuarteNeural", - }, - { - locale: "pt-PT", - short_name: "pt-PT-RaquelNeural", - }, - { - locale: "ro-RO", - short_name: "ro-RO-AlinaNeural", - }, - { - locale: "ro-RO", - short_name: "ro-RO-EmilNeural", - }, - { - locale: "ru-RU", - short_name: "ru-RU-DmitryNeural", - }, - { - locale: "ru-RU", - short_name: "ru-RU-SvetlanaNeural", - }, - { - locale: "sr-RS", - short_name: "sr-RS-NicholasNeural", - }, - { - locale: "sr-RS", - short_name: "sr-RS-SophieNeural", - }, - { - locale: "si-LK", - short_name: "si-LK-SameeraNeural", - }, - { - locale: "si-LK", - short_name: "si-LK-ThiliniNeural", - }, - { - locale: "sk-SK", - short_name: "sk-SK-LukasNeural", - }, - { - locale: "sk-SK", - short_name: "sk-SK-ViktoriaNeural", - }, - { - locale: "sl-SI", - short_name: "sl-SI-PetraNeural", - }, - { - locale: "sl-SI", - short_name: "sl-SI-RokNeural", - }, - { - locale: "so-SO", - short_name: "so-SO-MuuseNeural", - }, - { - locale: "so-SO", - short_name: "so-SO-UbaxNeural", - }, - { - locale: "es-AR", - short_name: "es-AR-ElenaNeural", - }, - { - locale: "es-AR", - short_name: "es-AR-TomasNeural", - }, - { - locale: "es-BO", - short_name: "es-BO-MarceloNeural", - }, - { - locale: "es-BO", - short_name: "es-BO-SofiaNeural", - }, - { - locale: "es-CL", - short_name: "es-CL-CatalinaNeural", - }, - { - locale: "es-CL", - short_name: "es-CL-LorenzoNeural", - }, - { - locale: "es-CO", - short_name: "es-CO-GonzaloNeural", - }, - { - locale: "es-CO", - short_name: "es-CO-SalomeNeural", - }, - { - locale: "es-CR", - short_name: "es-CR-JuanNeural", - }, - { - locale: "es-CR", - short_name: "es-CR-MariaNeural", - }, - { - locale: "es-CU", - short_name: "es-CU-BelkysNeural", - }, - { - locale: "es-CU", - short_name: "es-CU-ManuelNeural", - }, - { - locale: "es-DO", - short_name: "es-DO-EmilioNeural", - }, - { - locale: "es-DO", - short_name: "es-DO-RamonaNeural", - }, - { - locale: "es-EC", - short_name: "es-EC-AndreaNeural", - }, - { - locale: "es-EC", - short_name: "es-EC-LuisNeural", - }, - { - locale: "es-SV", - short_name: "es-SV-LorenaNeural", - }, - { - locale: "es-SV", - short_name: "es-SV-RodrigoNeural", - }, - { - locale: "es-GQ", - short_name: "es-GQ-JavierNeural", - }, - { - locale: "es-GQ", - short_name: "es-GQ-TeresaNeural", - }, - { - locale: "es-GT", - short_name: "es-GT-AndresNeural", - }, - { - locale: "es-GT", - short_name: "es-GT-MartaNeural", - }, - { - locale: "es-HN", - short_name: "es-HN-CarlosNeural", - }, - { - locale: "es-HN", - short_name: "es-HN-KarlaNeural", - }, - { - locale: "es-MX", - short_name: "es-MX-DaliaNeural", - }, - { - locale: "es-MX", - short_name: "es-MX-JorgeNeural", - }, - { - locale: "es-MX", - short_name: "es-MX-LorenzoEsCLNeural", - }, - { - locale: "es-NI", - short_name: "es-NI-FedericoNeural", - }, - { - locale: "es-NI", - short_name: "es-NI-YolandaNeural", - }, - { - locale: "es-PA", - short_name: "es-PA-MargaritaNeural", - }, - { - locale: "es-PA", - short_name: "es-PA-RobertoNeural", - }, - { - locale: "es-PY", - short_name: "es-PY-MarioNeural", - }, - { - locale: "es-PY", - short_name: "es-PY-TaniaNeural", - }, - { - locale: "es-PE", - short_name: "es-PE-AlexNeural", - }, - { - locale: "es-PE", - short_name: "es-PE-CamilaNeural", - }, - { - locale: "es-PR", - short_name: "es-PR-KarinaNeural", - }, - { - locale: "es-PR", - short_name: "es-PR-VictorNeural", - }, - { - locale: "es-ES", - short_name: "es-ES-AlvaroNeural", - }, - { - locale: "es-ES", - short_name: "es-ES-ElviraNeural", - }, - { - locale: "es-ES", - short_name: "es-ES-ManuelEsCUNeural", - }, - { - locale: "es-US", - short_name: "es-US-AlonsoNeural", - }, - { - locale: "es-US", - short_name: "es-US-PalomaNeural", - }, - { - locale: "es-UY", - short_name: "es-UY-MateoNeural", - }, - { - locale: "es-UY", - short_name: "es-UY-ValentinaNeural", - }, - { - locale: "es-VE", - short_name: "es-VE-PaolaNeural", - }, - { - locale: "es-VE", - short_name: "es-VE-SebastianNeural", - }, - { - locale: "su-ID", - short_name: "su-ID-JajangNeural", - }, - { - locale: "su-ID", - short_name: "su-ID-TutiNeural", - }, - { - locale: "sw-KE", - short_name: "sw-KE-RafikiNeural", - }, - { - locale: "sw-KE", - short_name: "sw-KE-ZuriNeural", - }, - { - locale: "sw-TZ", - short_name: "sw-TZ-DaudiNeural", - }, - { - locale: "sw-TZ", - short_name: "sw-TZ-RehemaNeural", - }, - { - locale: "sv-SE", - short_name: "sv-SE-MattiasNeural", - }, - { - locale: "sv-SE", - short_name: "sv-SE-SofieNeural", - }, - { - locale: "ta-IN", - short_name: "ta-IN-PallaviNeural", - }, - { - locale: "ta-IN", - short_name: "ta-IN-ValluvarNeural", - }, - { - locale: "ta-MY", - short_name: "ta-MY-KaniNeural", - }, - { - locale: "ta-MY", - short_name: "ta-MY-SuryaNeural", - }, - { - locale: "ta-SG", - short_name: "ta-SG-AnbuNeural", - }, - { - locale: "ta-SG", - short_name: "ta-SG-VenbaNeural", - }, - { - locale: "ta-LK", - short_name: "ta-LK-KumarNeural", - }, - { - locale: "ta-LK", - short_name: "ta-LK-SaranyaNeural", - }, - { - locale: "te-IN", - short_name: "te-IN-MohanNeural", - }, - { - locale: "te-IN", - short_name: "te-IN-ShrutiNeural", - }, - { - locale: "th-TH", - short_name: "th-TH-NiwatNeural", - }, - { - locale: "th-TH", - short_name: "th-TH-PremwadeeNeural", - }, - { - locale: "tr-TR", - short_name: "tr-TR-AhmetNeural", - }, - { - locale: "tr-TR", - short_name: "tr-TR-EmelNeural", - }, - { - locale: "uk-UA", - short_name: "uk-UA-OstapNeural", - }, - { - locale: "uk-UA", - short_name: "uk-UA-PolinaNeural", - }, - { - locale: "ur-IN", - short_name: "ur-IN-GulNeural", - }, - { - locale: "ur-IN", - short_name: "ur-IN-SalmanNeural", - }, - { - locale: "ur-PK", - short_name: "ur-PK-AsadNeural", - }, - { - locale: "ur-PK", - short_name: "ur-PK-UzmaNeural", - }, - { - locale: "uz-UZ", - short_name: "uz-UZ-MadinaNeural", - }, - { - locale: "uz-UZ", - short_name: "uz-UZ-SardorNeural", - }, - { - locale: "vi-VN", - short_name: "vi-VN-HoaiMyNeural", - }, - { - locale: "vi-VN", - short_name: "vi-VN-NamMinhNeural", - }, - { - locale: "cy-GB", - short_name: "cy-GB-AledNeural", - }, - { - locale: "cy-GB", - short_name: "cy-GB-NiaNeural", - }, - { - locale: "zu-ZA", - short_name: "zu-ZA-ThandoNeural", - }, - { - locale: "zu-ZA", - short_name: "zu-ZA-ThembaNeural", - }, -]; - -const LOCALES = Array.from( - new Set( - VOICES.map((v) => v.locale) - .filter((v) => v.length === 5) - .toSorted(), - ), -); - -export { VOICES, LOCALES }; diff --git a/src/lib/interfaces.ts b/src/lib/interfaces.ts index 1347b4a..7c502a7 100644 --- a/src/lib/interfaces.ts +++ b/src/lib/interfaces.ts @@ -19,15 +19,15 @@ export type SupportedAlphabets = export const TextSpeakerItemSchema = z.object({ text: z.string(), ipa: z.string().optional(), - locale: z.string(), + language: z.string(), }); export const TextSpeakerArraySchema = z.array(TextSpeakerItemSchema); export const WordDataSchema = z.object({ - locales: z + languages: z .tuple([z.string(), z.string()]) .refine(([first, second]) => first !== second, { - message: "Locales must be different", + message: "Languages must be different", }), wordPairs: z .array(z.tuple([z.string(), z.string()])) @@ -47,8 +47,8 @@ export const WordDataSchema = z.object({ export const TranslationHistorySchema = z.object({ text1: z.string(), text2: z.string(), - locale1: z.string(), - locale2: z.string(), + language1: z.string(), + language2: z.string(), }); export const TranslationHistoryArraySchema = z.array(TranslationHistorySchema); diff --git a/src/lib/server/bigmodel/tts.ts b/src/lib/server/bigmodel/tts.ts index 7a16b26..b8150f6 100644 --- a/src/lib/server/bigmodel/tts.ts +++ b/src/lib/server/bigmodel/tts.ts @@ -1,3 +1,6 @@ +"use server"; + + // ==================== 类型定义 ==================== /** * 支持的语音合成模型 @@ -135,13 +138,12 @@ class QwenTTSService { body: JSON.stringify(requestBody), }); - const data: TTSResponse = await response.json(); - // 4. 错误处理 - if (data.status_code !== 200) { - throw new Error(`API错误: [${data.code}] ${data.message}`); + if (response.status !== 200) { + throw new Error(`TTS API错误: [${response.status}] ${response.statusText}}`); } + const data: TTSResponse = await response.json(); return data; } catch (error) { @@ -149,74 +151,6 @@ class QwenTTSService { throw error; } } - - /** - * 流式合成语音(边生成边输出Base64音频数据) - */ - async synthesizeStream( - text: string, - options: { - voice?: string; - language?: SupportedLanguage; - model?: TTSModel; - onAudioChunk?: (chunk: string) => void; // 接收音频片段的回调 - } = {} - ): Promise { - const { - voice = 'Cherry', - language = 'Auto', - model = 'qwen3-tts-flash', - onAudioChunk - } = options; - - this.validateTextLength(text, model); - - const requestBody: TTSRequest = { - model, - input: { - text, - voice, - language_type: language - }, - parameters: { - stream: true // 启用流式输出 - } - }; - - try { - const response = await fetch(this.baseUrl, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${this.apiKey}`, - 'Content-Type': 'application/json', - 'X-DashScope-SSE': 'enable' // 关键:启用服务器发送事件 - }, - body: JSON.stringify(requestBody), - }); - - if (!response.ok || !response.body) { - throw new Error(`流式请求失败: ${response.status}`); - } - - // 处理流式响应(此处为简化示例,实际需解析SSE格式) - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - const chunk = decoder.decode(value); - if (onAudioChunk && chunk.trim()) { - onAudioChunk(chunk); // 处理音频数据片段 - } - } - - } catch (error) { - console.error('流式合成失败:', error); - throw error; - } - } } export type TTS_SUPPORTED_LANGUAGES = 'Auto' | 'Chinese' | 'English' | 'German' | 'Italian' | 'Portuguese' | 'Spanish' | 'Japanese' | 'Korean' | 'French' | 'Russian'; @@ -231,7 +165,7 @@ export async function getTTSUrl(text: string, lang: TTS_SUPPORTED_LANGUAGES) { throw "API Key设置错误"; } const ttsService = new QwenTTSService( - process.env.DASHSCOPE_API_KEY || 'sk-xxx', + process.env.DASHSCORE_API_KEY ); const result = await ttsService.synthesize( text,