feat: 增加国际化

This commit is contained in:
liuyonghe0111 2025-12-23 14:35:26 +08:00
parent d571477a0e
commit f2df1504df
42 changed files with 660 additions and 1228 deletions

View File

@ -1 +0,0 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 391 B

View File

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 475 KiB

View File

@ -1,9 +0,0 @@
<svg width="240" height="112" viewBox="0 0 240 112" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0L120 43L240 0V112H0V0Z" fill="url(#paint0_linear_560_21736)"/>
<defs>
<linearGradient id="paint0_linear_560_21736" x1="120" y1="0" x2="120" y2="112" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFF87C" stop-opacity="0"/>
<stop offset="0.745192" stop-color="#FFB940" stop-opacity="0.8"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 439 B

View File

@ -1,9 +0,0 @@
<svg width="176" height="112" viewBox="0 0 176 112" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0L88 43L176 0V112H0V0Z" fill="url(#paint0_linear_560_21745)"/>
<defs>
<linearGradient id="paint0_linear_560_21745" x1="88" y1="0" x2="88" y2="112" gradientUnits="userSpaceOnUse">
<stop stop-color="#CBC1FF" stop-opacity="0"/>
<stop offset="0.75" stop-color="#5740FF" stop-opacity="0.8"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 432 B

View File

@ -1,10 +0,0 @@
<svg width="176" height="112" viewBox="0 0 176 112" fill="none" xmlns="http://www.w3.org/2000/svg">
<foreignObject x="0" y="0" width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="backdrop-filter:blur(8px);clip-path:url(#bgblur_0_560_21727_clip_path);height:100%;width:100%"></div></foreignObject><path data-figma-bg-blur-radius="16" d="M0 0L88 43L176 0V112H0V0Z" fill="url(#paint0_linear_560_21727)"/>
<defs>
<clipPath id="bgblur_0_560_21727_clip_path" transform="translate(0 0)"><path d="M0 0L88 43L176 0V112H0V0Z"/>
</clipPath><linearGradient id="paint0_linear_560_21727" x1="88" y1="0" x2="88" y2="112" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC47C" stop-opacity="0"/>
<stop offset="0.745192" stop-color="#FF9040" stop-opacity="0.8"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@ -1,851 +0,0 @@
{
"_.notfound.title.empty_title": "Oops, theres nothing here…",
"common.mockprovider.text.div": "Initializing development environment...",
"common.mockprovider.text.p": "Initializing development environment...",
"common.uselogout.toast.toast_success": "Logged out",
"common.useregister.toast.toast_success": "Successful registration!",
"common.useregister.toast.toast_error": "Registration failed.",
"debug_mock.debugmockpage.text.div": "Waiting for the test results...",
"debug_mock.debugmockpage.text.h1": "🔧 Mock API 调试页面",
"debug_mock.debugmockpage.text.h2": "Test Results",
"debug_mock.debugmockpage.text.button": "Clear result",
"debug_mock.debugmockpage.text.strong": "Service Worker支持:",
"demo.demopage.text.div": "Toggle Loading 4 Toggle Loading 4 Toggle Loading 4",
"demo.demopage.text.h1": "Component demo",
"demo.demopage.text.section": "Toggle Loading 4 Toggle Loading 4 Toggle Loading 4",
"demo.demopage.text.h2": "Manually Control Loading Status Manually Control Loading Status Manually Control Loading Status",
"demo.demopage.text.p": "The left sidebar supports the following functions:",
"demo.demopage.text.ul": "Smooth animation transitions",
"demo.demopage.text.li": "Smooth animation transitions",
"demo.demopage.text.alertdialog": "Continue operation",
"demo.demopage.text.alertdialogtrigger": "No close button",
"demo.demopage.text.button": "Toggle Loading 4 Toggle Loading 4 Toggle Loading 4",
"demo.demopage.text.alertdialogcontent": "Continue operation",
"demo.demopage.text.alertdialogheader": "This is an important reminder that you must choose an option to proceed.",
"demo.demopage.text.alertdialogtitle": "Important Note",
"demo.demopage.text.alertdialogdescription": "This is an important reminder that you must choose an option to proceed.",
"demo.demopage.text.alertdialogfooter": "Continue operation",
"demo.demopage.text.alertdialogcancel": "Oh, I see.",
"demo.demopage.text.alertdialogaction": "Continue operation",
"demo.demopage.text.iconbutton": "🗑️",
"demo.demopage.text.span": "🗑️",
"server_device_test.serverdevicetestpage.text.div": "This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 +",
"server_device_test.serverdevicetestpage.text.h1": "🖥️ 服务端设备ID测试",
"server_device_test.serverdevicetestpage.text.p": "This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 +",
"server_device_test.serverdevicetestpage.text.card": "This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 +",
"server_device_test.serverdevicetestpage.text.cardheader": "📋 服务端设备ID处理流程",
"server_device_test.serverdevicetestpage.text.cardtitle": "📋 服务端设备ID处理流程",
"server_device_test.serverdevicetestpage.text.cardcontent": "This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 + This is an architectural limitation of Next.js 13 +",
"server_device_test.serverdevicetestpage.text.h4": "rendering environment",
"server_device_test.serverdevicetestpage.text.ul": "• Device ID is passed to server level component via header • Device ID is passed to server level component via header",
"server_device_test.serverdevicetestpage.text.li": "• Device ID is passed to server level component via header • Device ID is passed to server level component via header",
"server_device_test.serverdevicetestpage.text.h5": "4. Restrictions 4. Restrictions",
"test_avatar_crop.testavatarcroppage.text.div": "• Smooth interactive animation effects • Smooth interactive animation effects",
"test_avatar_crop.testavatarcroppage.text.h1": "Avatar Crop Component Test",
"test_avatar_crop.testavatarcroppage.text.p": "Avatar clipping pop-up window based on design draft restoration",
"test_avatar_crop.testavatarcroppage.text.card": "• Smooth interactive animation effects • Smooth interactive animation effects",
"test_avatar_crop.testavatarcroppage.text.h2": "Design Restore",
"test_avatar_crop.testavatarcroppage.text.button": "Download avatar",
"test_avatar_crop.testavatarcroppage.text.span": "Selected:",
"test_avatar_crop.testavatarcroppage.alt.image_alt": "Cropped avatar",
"test_avatar_crop.testavatarcroppage.text.h3": "🎨 设计细节",
"test_avatar_setting.testavatarsettingpage.text.div": "Open avatar settings",
"test_avatar_setting.testavatarsettingpage.text.h1": "avatar setup test",
"test_avatar_setting.testavatarsettingpage.alt.img_alt": "Current Avatar",
"test_avatar_setting.testavatarsettingpage.text.button": "Open avatar settings",
"test_discord.testdiscordpage.toast.toast_error": "Mock login failed",
"test_discord.testdiscordpage.toast.toast_warning": "Missing configuration",
"test_discord.testdiscordpage.toast.toast_success": "Exit successfully",
"test_discord.testdiscordpage.text.div": "POST /web/third/login { \"appClient\": \"WEB\", \"deviceCode\": \"设备ID\", \"thirdToken\": \"discord_code\", \"thirdType\": \"DISCORD\" }",
"test_discord.testdiscordpage.text.h1": "Discord login test page",
"test_discord.testdiscordpage.text.p": "Http://localhost:3000/api/auth/discord/callback",
"test_discord.testdiscordpage.text.card": "POST /web/third/login { \"appClient\": \"WEB\", \"deviceCode\": \"设备ID\", \"thirdToken\": \"discord_code\", \"thirdType\": \"DISCORD\" }",
"test_discord.testdiscordpage.text.cardheader": "configuration instructions",
"test_discord.testdiscordpage.text.cardtitle": "configuration instructions",
"test_discord.testdiscordpage.text.cardcontent": "POST /web/third/login { \"appClient\": \"WEB\", \"deviceCode\": \"设备ID\", \"thirdToken\": \"discord_code\", \"thirdType\": \"DISCORD\" }",
"test_discord.testdiscordpage.text.strong": "Need to improve information:",
"test_discord.testdiscordpage.text.button": "clear log",
"test_discord.testdiscordpage.text.h4": "API interface:",
"test_discord.testdiscordpage.text.ol": "The front-end login page detects the code, and calls the back-end API to complete the login.",
"test_discord.testdiscordpage.text.li": "The front-end login page detects the code, and calls the back-end API to complete the login.",
"test_discord.testdiscordpage.text.pre": "POST /web/third/login { \"appClient\": \"WEB\", \"deviceCode\": \"设备ID\", \"thirdToken\": \"discord_code\", \"thirdType\": \"DISCORD\" }",
"test_image_crop.testimagecroppage.text.div": "Original image preview",
"test_image_crop.testimagecroppage.text.h1": "Image cropping test",
"test_image_crop.testimagecroppage.text.p": "Test various image cropping features and preset configurations",
"test_image_crop.testimagecroppage.text.card": "Original image preview",
"test_image_crop.testimagecroppage.text.h2": "Original image preview",
"test_image_crop.testimagecroppage.text.button": "download",
"test_image_crop.testimagecroppage.text.span": "Selected:",
"test_image_crop.testimagecroppage.alt.image_alt": "original image",
"test_image_crop.testimagecroppage.title.imagecropmodal_title": "Advanced image crop",
"test_image_crop.testimagecroppage.title.simpleimagecropmodal_title": "Simple image cropping",
"test_lamejs.testlamejs.text.div": "这个页面用于测试 lamejs 库是否能正确导入和初始化。 请打开浏览器控制台查看详细的日志信息。",
"test_lamejs.testlamejs.text.h1": "lamejs 导入测试",
"test_lamejs.testlamejs.text.button": "测试 lamejs 导入",
"test_lamejs.testlamejs.text.h3": "说明",
"test_lamejs.testlamejs.text.p": "这个页面用于测试 lamejs 库是否能正确导入和初始化。 请打开浏览器控制台查看详细的日志信息。",
"test_middleware.testmiddlewarepage.text.div": "If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing.",
"test_middleware.testmiddlewarepage.text.h1": "🔧 Middleware 测试页面",
"test_middleware.testmiddlewarepage.text.h2": "Navigation test",
"test_middleware.testmiddlewarepage.text.button": "Programmatically navigate to /profile Programmatically navigate to /profile",
"test_middleware.testmiddlewarepage.text.h3": "explain",
"test_middleware.testmiddlewarepage.text.p": "If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing. If you click the above button to navigate to /profile and do not see the middleware log in the console, MSW or something else is preventing middleware from executing.",
"test_mp3_conversion.testmp3conversion.toast.toast_success": "原始文件已下载",
"test_mp3_conversion.testmp3conversion.toast.toast_error": "录音失败",
"test_mp3_conversion.testmp3conversion.text.div": "您的浏览器不支持音频播放。",
"test_mp3_conversion.testmp3conversion.text.h1": "MP3转换测试",
"test_mp3_conversion.testmp3conversion.text.button": "下载MP3文件",
"test_mp3_conversion.testmp3conversion.text.p": "压缩率:",
"test_mp3_conversion.testmp3conversion.text.h3": "音频播放测试",
"test_mp3_conversion.testmp3conversion.text.audio": "您的浏览器不支持音频播放。",
"test_s3_upload.tests3uploadpage.text.div": "import { useS3Upload } from '@/hooks/useS3Upload' import { BizTypeEnum } from '@/services/common/types' const { uploading, progress, error, uploadFile } = useS3Upload({ bizType: BizTypeEnum.Role, maxRetries: 3, retryDelay: 2000, onSuccess: (url) => console.log('上传成功:', url), onError: (error) => console.error('上传失败:', error), onProgress: (progress) => console.log('进度:', progress.percentage + '%') }) // 使用 await uploadFile(file)",
"test_s3_upload.tests3uploadpage.text.h1": "AWS S3 Upload Test AWS S3 Upload Test AWS S3 Upload Test",
"test_s3_upload.tests3uploadpage.text.p": "Testing the file upload feature using the AWS S3 SDK Testing the file upload feature using the AWS S3 SDK Testing the file upload feature using the AWS S3 SDK Testing the file upload feature using the AWS S3 SDK Testing the file upload feature using the AWS S3 SDK",
"test_s3_upload.tests3uploadpage.text.h2": "How to use Hook How to use Hook",
"test_s3_upload.tests3uploadpage.text.pre": "import { useS3Upload } from '@/hooks/useS3Upload' import { BizTypeEnum } from '@/services/common/types' const { uploading, progress, error, uploadFile } = useS3Upload({ bizType: BizTypeEnum.Role, maxRetries: 3, retryDelay: 2000, onSuccess: (url) => console.log('上传成功:', url), onError: (error) => console.error('上传失败:', error), onProgress: (progress) => console.log('进度:', progress.percentage + '%') }) // 使用 await uploadFile(file)",
"common.airelationtag.text.div": "·",
"common.airelationtag.text.span": "&gt;",
"common.airelationtag.text.tag": "&gt;",
"common.s3uploaddemo.text.div": "✅ Presigned URL secure upload ✅ Presigned URL secure upload ✅ Presigned URL secure upload ✅ Presigned URL secure upload",
"common.s3uploaddemo.text.h3": "S3 file upload demo S3 file upload demo",
"common.s3uploaddemo.text.p": "MB)",
"common.s3uploaddemo.text.button": "reset",
"common.s3uploaddemo.text.span": "MB",
"common.s3uploaddemo.text.h4": "Functional features:",
"common.s3uploaddemo.text.ul": "✅ Presigned URL secure upload ✅ Presigned URL secure upload ✅ Presigned URL secure upload ✅ Presigned URL secure upload",
"common.s3uploaddemo.text.li": "✅ Presigned URL secure upload ✅ Presigned URL secure upload ✅ Presigned URL secure upload ✅ Presigned URL secure upload",
"common.abandoncreationdialog.text.alertdialog": "cancel",
"common.abandoncreationdialog.text.alertdialogcontent": "cancel",
"common.abandoncreationdialog.text.alertdialogheader": "Give up creation",
"common.abandoncreationdialog.text.alertdialogtitle": "Give up creation",
"common.abandoncreationdialog.text.alertdialogdescription": "If you choose to exit or regenerate the image, the already created image will disappear, and the number of creations will be consumed once.",
"common.abandoncreationdialog.text.alertdialogfooter": "cancel",
"common.abandoncreationdialog.text.alertdialogcancel": "cancel",
"common.handleconfirm.toast.toast_error": "Please enter nickname",
"common.aigeneratebutton.text.div": "AI Generate",
"common.aigeneratebutton.text.span": "AI Generate",
"common.aigeneratebutton.text.alertdialog": "Continue",
"common.aigeneratebutton.text.alertdialogcontent": "Continue",
"common.aigeneratebutton.text.alertdialogheader": "Warning",
"common.aigeneratebutton.text.alertdialogtitle": "Warning",
"common.aigeneratebutton.text.alertdialogdescription": "Using AI generation will overwrite the content you have already entered. Do you want to continue?",
"common.aigeneratebutton.text.alertdialogfooter": "Continue",
"common.aigeneratebutton.text.alertdialogcancel": "Cancel",
"common.aigeneratebutton.text.alertdialogaction": "Continue",
"common.albumdeletealert.text.alertdialog": "Delete",
"common.albumdeletealert.text.alertdialogcontent": "Delete",
"common.albumdeletealert.text.alertdialogheader": "Delete Album",
"common.albumdeletealert.text.alertdialogtitle": "Delete Album",
"common.albumdeletealert.text.alertdialogfooter": "Delete",
"common.albumdeletealert.text.alertdialogaction": "Got it",
"common.albumdeletealert.text.button": "Delete",
"common.schema.validation.message": "At least 20 diamonds",
"common.albumpricesetting.text.alertdialog": "Confirm",
"common.albumpricesetting.text.alertdialogcontent": "Confirm",
"common.albumpricesetting.text.alertdialogheader": "Unlock Method",
"common.albumpricesetting.text.alertdialogtitle": "Unlock Method",
"common.albumpricesetting.text.form": "Confirm",
"common.albumpricesetting.text.alertdialogdescription": "Price",
"common.albumpricesetting.text.div": "Price",
"common.albumpricesetting.text.formitem": "Price",
"common.albumpricesetting.text.formlabel": "Price",
"common.albumpricesetting.text.tooltip": "Set up several free images to attract interlocutors to interact with your avatar",
"common.albumpricesetting.text.tooltipcontent": "Set up several free images to attract interlocutors to interact with your avatar",
"common.albumpricesetting.text.p": "Set up several free images to attract interlocutors to interact with your avatar",
"common.albumpricesetting.text.formcontrol": "Free",
"common.albumpricesetting.text.select": "Free",
"common.albumpricesetting.text.selectcontent": "Free",
"common.albumpricesetting.text.selectitem": "Free",
"common.albumpricesetting.placeholder.input_placeholder": "Price",
"common.albumpricesetting.alt.image_alt": "Diamond",
"common.albumpricesetting.text.alertdialogfooter": "Confirm",
"common.albumpricesetting.text.button": "Confirm",
"common.handlepayment.toast.toast_error": "Payment failure, please try again",
"common.renderlist.text.div": "$",
"common.renderlist.alt.image_alt": "diamond",
"common.chargedrawer.text.drawer": "Cancel",
"common.chargedrawer.text.drawercontent": "Cancel",
"common.chargedrawer.text.drawerheader": "Charge",
"common.chargedrawer.text.drawertitle": "Charge",
"common.chargedrawer.text.drawerdescription": "The Crush coin is insufficient and cannot continue, please recharge",
"common.chargedrawer.text.div": "Cancel",
"common.chargedrawer.text.span": "Agreement",
"common.chargedrawer.text.link": "Agreement",
"common.chargedrawer.text.drawerfooter": "Cancel",
"common.questionicon.text.tooltip": "Voice call message price refers to the price of a voice call conversation with the character, calculated by item",
"common.questionicon.text.tooltipcontent": "Voice call message price refers to the price of a voice call conversation with the character, calculated by item",
"common.questionicon.text.div": "Voice call message price refers to the price of a voice call conversation with the character, calculated by item",
"common.questionicon.text.p": "Voice call message price refers to the price of a voice call conversation with the character, calculated by item",
"common.coininsufficientdialog.text.alertdialog": "Recharge",
"common.coininsufficientdialog.text.alertdialogcontent": "Recharge",
"common.coininsufficientdialog.text.alertdialogheader": "Crush Coin insufficient",
"common.coininsufficientdialog.text.alertdialogtitle": "Crush Coin insufficient",
"common.coininsufficientdialog.text.p": "Have a role-playing conversation with AI",
"common.coininsufficientdialog.text.div": "Recharge",
"common.coininsufficientdialog.text.h3": "Role-playing model",
"common.coininsufficientdialog.text.span": "Your Balance",
"common.coininsufficientdialog.text.button": "Recharge",
"common.createreachedlimitdialog.text.alertdialog": "you have reached the maximum number of AI creations.",
"common.createreachedlimitdialog.text.alertdialogcontent": "you have reached the maximum number of AI creations.",
"common.createreachedlimitdialog.text.alertdialogheader": "you have reached the maximum number of AI creations.",
"common.createreachedlimitdialog.text.alertdialogdescription": "you have reached the maximum number of AI creations.",
"common.deviceinfo.text.card": "• Will not be cleared when logging out (only clearAll will be cleared) • Will not be cleared when logging out (only clearAll will be cleared)",
"common.deviceinfo.text.cardheader": "📱 设备信息",
"common.deviceinfo.text.cardtitle": "📱 设备信息",
"common.deviceinfo.text.cardcontent": "• Will not be cleared when logging out (only clearAll will be cleared) • Will not be cleared when logging out (only clearAll will be cleared)",
"common.deviceinfo.text.div": "• Will not be cleared when logging out (only clearAll will be cleared) • Will not be cleared when logging out (only clearAll will be cleared)",
"common.deviceinfo.text.h4": "Authentication token (st) Authentication token (st)",
"common.deviceinfo.text.p": "Stored in a cookie, field named'st ', sent as AUTH_TK in the request header",
"common.deviceinfo.text.button": "Clear all data",
"common.deviceinfo.text.h5": "💡 设备ID说明",
"common.deviceinfo.text.ul": "• Will not be cleared when logging out (only clearAll will be cleared) • Will not be cleared when logging out (only clearAll will be cleared)",
"common.deviceinfo.text.li": "• Will not be cleared when logging out (only clearAll will be cleared) • Will not be cleared when logging out (only clearAll will be cleared)",
"common.items.aria.i_aria_hidden": "true",
"common.genderinput.aria.div_aria_label": "Gender",
"common.sidebar.alt.image_alt": "Expand",
"common.topbarwithoutlogin.alt.image_alt": "Logo",
"common.topbar.text.header": "Login in or Sign up",
"common.topbar.text.div": "Login in or Sign up",
"common.topbar.alt.image_alt": "logo",
"common.topbar.text.link": "Login in or Sign up",
"common.topbar.text.button": "Login in or Sign up",
"common.avatarcropmodal.text.div": "Confirm",
"common.empty.alt.image_alt": "Empty",
"common.imagecrop.text.div": "Rotate:",
"common.imagecrop.text.label": "Rotate:",
"common.imageviewer.text.imageviewerpaginationcontent": "/",
"common.imageviewer.text.div": "Choose",
"common.imageviewer.text.button": "Choose",
"common.defaultloadingmore.text.span": "Loading...",
"common.input.text.div": "/",
"common.input.text.span": "/",
"common.selectitem.alt.image_alt": "Check",
"_auth__about.aboutpage.text.div": "and the risky emotional gambles you feared to take.",
"_auth__about.aboutpage.alt.img_alt": "Banner",
"_auth__login.loginpage.text.div": "From \"Hello\" to \"I do\", every conversation is full of heart",
"_auth__login.loginpage.alt.image_alt": "Anime character",
"_main__chat.chatpage.title.empty_title": "Oops, theres nothing here…",
"_main__contact.contactcard.text.div": "°",
"_main__contact.contactcard.alt.img_alt": "Heart",
"_main__contact.contactcard.text.span": "°",
"_main__contact.emptystate.text.div": "Start chatting with AI characters to build your contacts",
"_main__contact.emptystate.alt.img_alt": "Empty",
"_main__contact.emptystate.text.h3": "No contacts found",
"_main__contact.emptystate.text.p": "Start chatting with AI characters to build your contacts",
"_main__contact.contactspage.text.div": "Contacts",
"_main__contact.contactspage.text.h1": "Contacts",
"_main__creator.creatorpage.text.div": "Stay tuned",
"_main__creator.creatorpage.text.h1": "Creator",
"_main__creator.creatorpage.alt.image_alt": "gift",
"_main__crushcoin.crushcoinpage.text.div": "If you miss a check-in, the check-in count will reset and start again from day one.",
"_main__crushcoin.crushcoinpage.text.h1": "Daily free Crush Coin",
"_main__crushcoin.crushcoinpage.text.p": "If you miss a check-in, the check-in count will reset and start again from day one.",
"_main__explore.explorepage.text.div": "The third phase function is being polished, * & *... *...%...%... &% &...% &%... &% &... The third phase function is being polished, * & *... *...%...%... &% &...% &%... &% &...",
"_main__explore.explorepage.alt.image_alt": "Logo",
"_main__explore.explorepage.text.p": "The third phase function is being polished, * & *... *...%...%... &% &...% &%... &% &... The third phase function is being polished, * & *... *...%...%... &% &...% &%... &% &...",
"_main__leaderboard.leaderboardpage.text.div": "The gift list is ranked by the sum of the gift value received by the AI character.",
"_main__leaderboard.leaderboardpage.alt.image_alt": "Bg",
"_main__leaderboard.leaderboardpage.text.h1": "Leaderboard",
"_main__leaderboard.leaderboardpage.text.tooltip": "The gift list is ranked by the sum of the gift value received by the AI character.",
"_main__leaderboard.leaderboardpage.text.tooltipcontent": "The gift list is ranked by the sum of the gift value received by the AI character.",
"_main__leaderboard.leaderboardpage.text.p": "The gift list is ranked by the sum of the gift value received by the AI character.",
"_main__profile.profilepage.text.div": "ID:",
"_main__profile.profilepage.alt.image_alt": "Gender",
"_main__profile.profilepage.text.span": "ID:",
"_main__profile.profilepage.toast.toast_success": "Copied to clipboard",
"_main__test_voice_wave.testvoicewavepage.text.div": "import { VoiceWaveAnimation } from \"@/components/ui/voice-wave-animation\"; // 基础使用 <VoiceWaveAnimation animated={true} /> // 自定义声波条数量 <VoiceWaveAnimation animated={true} barCount={32} className=\"w-80 h-32\" /> // 停止动画时的效果 <VoiceWaveAnimation animated={false} // 所有声波条变为统一短高度 barCount={24} className=\"w-80 h-32\" />",
"_main__test_voice_wave.testvoicewavepage.text.h1": "Voice ripple animation effect display",
"_main__test_voice_wave.testvoicewavepage.text.h2": "How to use",
"_main__test_voice_wave.testvoicewavepage.text.p": "Features:",
"_main__test_voice_wave.testvoicewavepage.text.strong": "Features:",
"_main__test_voice_wave.testvoicewavepage.text.h3": "Article)",
"_main__test_voice_wave.testvoicewavepage.text.pre": "import { VoiceWaveAnimation } from \"@/components/ui/voice-wave-animation\"; // 基础使用 <VoiceWaveAnimation animated={true} /> // 自定义声波条数量 <VoiceWaveAnimation animated={true} barCount={32} className=\"w-80 h-32\" /> // 停止动画时的效果 <VoiceWaveAnimation animated={false} // 所有声波条变为统一短高度 barCount={24} className=\"w-80 h-32\" />",
"_main__test_voice_wave.testvoicewavepage.text.code": "import { VoiceWaveAnimation } from \"@/components/ui/voice-wave-animation\"; // 基础使用 <VoiceWaveAnimation animated={true} /> // 自定义声波条数量 <VoiceWaveAnimation animated={true} barCount={32} className=\"w-80 h-32\" /> // 停止动画时的效果 <VoiceWaveAnimation animated={false} // 所有声波条变为统一短高度 barCount={24} className=\"w-80 h-32\" />",
"_main__test_voice_wave.testvoicewavepage.text.ul": "Pure CSS implementation, excellent performance",
"_main__test_voice_wave.testvoicewavepage.text.li": "Pure CSS implementation, excellent performance",
"_main__vip.vippage.text.div": "Subscribe Now",
"_main__vip.vippage.text.h1": "Crush Level VIP",
"_main__vip.vippage.text.h2": "Crush Level VIP",
"_main__vip.vippage.text.button": "Subscribe Now",
"_main__wallet.walletpage.text.div": "Transaction Details &gt;",
"_main__wallet.walletpage.text.h1": "Wallet",
"_main__wallet.walletpage.text.tabs": "Transaction Details &gt;",
"_main__wallet.walletpage.text.tabslist": "Income",
"_main__wallet.walletpage.text.tabstrigger": "Income",
"_main__wallet.walletpage.text.link": "Transaction Details &gt;",
"common.chatconversationsdeletedialog.text.alertdialog": "After deleting all messages, all message records will be cleared",
"common.chatconversationsdeletedialog.text.alertdialogcontent": "After deleting all messages, all message records will be cleared",
"common.chatconversationsdeletedialog.text.alertdialogheader": "Delete message",
"common.chatconversationsdeletedialog.text.alertdialogtitle": "Delete message",
"common.chatconversationsdeletedialog.text.alertdialogdescription": "After deleting all messages, all message records will be cleared",
"common.chatconversationsdeletedialog.text.alertdialogaction": "Delete",
"common.personitem.alt.image_alt": "Heart",
"common.personemptystate.title.empty_title": "We find nothing here",
"common.chatsearchresults.text.div": "Person",
"common.chatsearchresults.text.tabs": "Person",
"common.chatsearchresults.text.tabslist": "Person",
"common.chatsearchresults.text.tabstrigger": "Person",
"common.chatsearchresults.text.span": "Person",
"common.chatsidebar.text.div": "Chats",
"common.chatsidebar.text.span": "Chats",
"common.chatsidebar.placeholder.input_placeholder": "Search",
"common.chatsidebaraction.text.dropdownmenu": "Iconfont icon-Search",
"common.chatsidebaraction.text.dropdownmenucontent": "Iconfont icon-Search",
"common.chatsidebaraction.text.dropdownmenuitem": "Delete All",
"common.chatsidebaraction.text.span": "Delete All",
"common.chatsidebaritem.alt.image_alt": "heart",
"common.notice.text.div": "Notice",
"common.notice.alt.image_alt": "Notice",
"common.notice.text.span": "Notice",
"common.rendercheckbutton.text.button": "Check",
"common.noticedrawer.text.drawer": "Notice",
"common.noticedrawer.text.drawercontent": "Notice",
"common.noticedrawer.text.drawerheader": "Notice",
"_auth__login.discordbutton.toast.toast_error": "Discord登录失败",
"_auth__login.discordbutton.toast.toast_success": "Login successful",
"_auth__login.discordbutton.text.socialbutton": "Continue with Discord",
"_auth__login.imagecarousel.text.div": "No picture.",
"_auth__login.loginform.toast.toast_info": "Apple Sign In",
"_auth__login.loginform.text.div": "Privacy Policy",
"_auth__login.loginform.text.h2": "Log in/Sign up",
"_auth__login.loginform.text.p": "Privacy Policy",
"_auth__login.loginform.text.socialbutton": "Continue with Apple",
"_auth__login.loginform.text.link": "Privacy Policy",
"_auth__login_fields.schema.validation.message": "Please select your gender",
"_auth__login_fields.onsubmit.error.form_seterror": "Nickname already exists",
"_auth__login_fields.onsubmit.validation.message": "Nickname already exists",
"_auth__login_fields.fieldspage.text.div": "Submit",
"_auth__login_fields.fieldspage.alt.image_alt": "Anime character",
"_auth__login_fields.fieldspage.text.h2": "🎉🎉🎉 Welcome Join Us! 🎉🎉🎉",
"_auth__login_fields.fieldspage.text.form": "Submit",
"_auth__login_fields.fieldspage.text.formitem": "Can not edit after setting up",
"_auth__login_fields.fieldspage.text.formlabel": "Gender",
"_auth__login_fields.fieldspage.placeholder.input_placeholder": "Enter your nickname",
"_auth__login_fields.fieldspage.text.label": "Birthday",
"_auth__login_fields.fieldspage.placeholder.selectvalue_placeholder": "Day",
"_auth__login_fields.fieldspage.text.button": "Submit",
"_auth__policy_privacy.privacypolicypage.text.div": "Thank you for trusting Crushlevel. We strive to protect your information security!",
"_auth__policy_privacy.privacypolicypage.text.p": "Thank you for trusting Crushlevel. We strive to protect your information security!",
"_auth__policy_privacy.privacypolicypage.text.strong": "Account Deactivation:",
"_auth__policy_privacy.privacypolicypage.text.ul": "Troubleshoot issues, maintain service integrity, and protect your rights.",
"_auth__policy_privacy.privacypolicypage.text.li": "Troubleshoot issues, maintain service integrity, and protect your rights.",
"_auth__policy_recharge.rechargeagreementpage.text.div": "If you violate any provisions of this Agreement (including but not limited to purchasing Virtual Currency through unauthorized channels, using funds from illegal sources, etc.), the Platform has the right to take appropriate measures in accordance with the severity of the violation, including but not limited to warning, restricting account functions, temporarily or permanently banning your account, and requiring you to bear corresponding legal liability. If your violation causes losses to the Platform or third parties, you shall be liable for full compensation.",
"_auth__policy_recharge.rechargeagreementpage.text.p": "If you violate any provisions of this Agreement (including but not limited to purchasing Virtual Currency through unauthorized channels, using funds from illegal sources, etc.), the Platform has the right to take appropriate measures in accordance with the severity of the violation, including but not limited to warning, restricting account functions, temporarily or permanently banning your account, and requiring you to bear corresponding legal liability. If your violation causes losses to the Platform or third parties, you shall be liable for full compensation.",
"_auth__policy_recharge.rechargeagreementpage.text.strong": "the Platform does not provide refund services for this part of the Virtual Currency",
"_auth__policy_recharge.rechargeagreementpage.text.ul": "Other circumstances where you have intent or gross negligence (such as failure to update account security settings in a timely manner, ignoring account abnormal login reminders, etc.).",
"_auth__policy_recharge.rechargeagreementpage.text.li": "Other circumstances where you have intent or gross negligence (such as failure to update account security settings in a timely manner, ignoring account abnormal login reminders, etc.).",
"_auth__policy_tos.termsofservicepage.text.div": "Users are required to carefully read and strictly comply with the above agreement. Thank you for your support and trust in Crushlevel. We hope you enjoy using it!",
"_auth__policy_tos.termsofservicepage.text.p": "Users are required to carefully read and strictly comply with the above agreement. Thank you for your support and trust in Crushlevel. We hope you enjoy using it!",
"_auth__policy_tos.termsofservicepage.text.ul": "Other content that violates public order, good morals, or the provisions of this Agreement.",
"_auth__policy_tos.termsofservicepage.text.li": "Other content that violates public order, good morals, or the provisions of this Agreement.",
"_auth__share__userid_.notfound.alt.image_alt": "Anime character",
"_auth__share__userid_.notfound.title.empty_title": "Oops, theres nothing here…",
"_auth__share__userid_.sharepage.text.div": "Chat. Crush. AI Date",
"_auth__share__userid_.sharepage.alt.image_alt": "Crushlevel",
"_auth__share__userid_.sharepage.text.button": "Chat",
"_auth__share__userid_.sharepage.text.p": "Intro:",
"_auth__share__userid_.sharepage.text.span": "Are you?",
"_main__chat__aiid_.notfound.title.empty_title": "Oops, theres nothing here…",
"_main__contact.rendercontactstatustext.text.div": "Rank according to the total heart value of these characters",
"_main__contact.rendercontactstatustext.alt.image_alt": "Question-border",
"_main__contact.rendercontactstatustext.text.tooltip": "Rank according to the total heart value of these characters",
"_main__contact.rendercontactstatustext.text.tooltipcontent": "Rank according to the total heart value of these characters",
"_main__contact.rendercontactstatustext.text.p": "Rank according to the total heart value of these characters",
"_main__create.characterformschema.validation.message": "Please select gender",
"_main__create.onsubmit.error.form_seterror": "Nickname already exists",
"_main__create.onsubmit.validation.message": "Nickname already exists",
"_main__create.characterform.text.div": "Next",
"_main__create.characterform.text.h1": "Create Character",
"_main__create.characterform.text.form": "Next",
"_main__create.characterform.text.h2": "Character",
"_main__create.characterform.text.formitem": "Preferences",
"_main__create.characterform.text.formlabel": "Preferences",
"_main__create.characterform.placeholder.input_placeholder": "Enter character name",
"_main__create.characterform.text.label": "Birthday",
"_main__create.characterform.placeholder.selectvalue_placeholder": "Day",
"_main__create.characterform.placeholder.textarea_placeholder": "Describe the character's background, personality, and identity",
"_main__create.characterform.text.button": "Next",
"_main__create.closeiconbutton.text.alertdialog": "Quit",
"_main__create.closeiconbutton.text.alertdialogcontent": "Quit",
"_main__create.closeiconbutton.text.alertdialogheader": "The content has not been saved, Continue to quit?",
"_main__create.closeiconbutton.text.alertdialogtitle": "Unsaved changes",
"_main__create.closeiconbutton.text.alertdialogdescription": "The content has not been saved, Continue to quit?",
"_main__create.closeiconbutton.text.alertdialogfooter": "Quit",
"_main__create.closeiconbutton.text.alertdialogcancel": "Cancel",
"_main__create.closeiconbutton.text.alertdialogaction": "Quit",
"_main__create.copyrightrulemodal.text.alertdialog": "Confirm",
"_main__create.copyrightrulemodal.text.alertdialogcontent": "Confirm",
"_main__create.copyrightrulemodal.alt.image_alt": "Copyright Rule",
"_main__create.copyrightrulemodal.text.alertdialogdescription": "By clicking \"Confirm\", you represent and warrant that you are the original creator of this virtual character and that it does not infringe upon any third party's intellectual property rights or other legal rights.",
"_main__create.copyrightrulemodal.text.p": "By clicking \"Confirm\", you represent and warrant that you are the original creator of this virtual character and that it does not infringe upon any third party's intellectual property rights or other legal rights.",
"_main__create.copyrightrulemodal.text.alertdialogfooter": "Confirm",
"_main__create.copyrightrulemodal.text.alertdialogaction": "Confirm",
"_main__create.dialogueformschema.validation.message": "Please select voice",
"_main__create.dialogueform.text.div": "Female",
"_main__create.dialogueform.text.form": "Female",
"_main__create.dialogueform.text.h2": "Dialogue",
"_main__create.dialogueform.text.formitem": "Voice Tone",
"_main__create.dialogueform.text.formlabel": "Voice Tone",
"_main__create.dialogueform.placeholder.textarea_placeholder": "Please describe the way the characters chat, the tone of the dialogue",
"_main__create.dialogueform.text.span": "(Optional)",
"_main__create.dialogueform.text.label": "Suggestions",
"_main__create.dialogueform.text.tabs": "Female",
"_main__create.dialogueform.text.tabslist": "Female",
"_main__create.dialogueform.text.tabstrigger": "Female",
"_main__create.imageformschema.validation.message": "请上传或生成一张图片",
"_main__create.imageform.toast.toast_success": "Character created successfully!",
"_main__create.imageform.text.div": "Submit",
"_main__create.imageform.text.form": "Submit",
"_main__create.imageform.text.h2": "Image",
"_main__create.imageform.text.formitem": "Public",
"_main__create.imageform.text.formlabel": "Privacy",
"_main__create.imageform.alt.image_alt": "Avatar",
"_main__create.imageform.placeholder.textarea_placeholder": "Introduce the virtual character to the user",
"_main__create.imageform.text.chip": "Privacy",
"_main__create.imageform.text.button": "Submit",
"_main__create.imagegeneration.text.div": "Generating image...",
"_main__create.imagegeneration.text.h4": "Description",
"_main__create.imagegeneration.text.button": "Using Character Information",
"_main__create.imagegeneration.placeholder.textarea_placeholder": "Describe the character image you want to generate, for example: beautiful anime girl with long blue hair, wearing school uniform...",
"_main__create.imagegeneration.text.p": "Generating image...",
"_main__create.imagepreview.text.div": "Main image selected",
"_main__create.imagepreview.alt.image_alt": "Character image",
"_main__create.imagepreview.text.p": "Main image selected",
"_main__create.imageselector.text.div": "Click to Generate an Image",
"_main__create.imageupload.text.div": "files",
"_main__create.imageupload.text.p": "files",
"_main__create.typeform.text.div": "Tags",
"_main__create.typeform.text.form": "Tags",
"_main__create.typeform.text.h2": "Type",
"_main__create.typeform.text.formitem": "Tags",
"_main__create.typeform.text.formlabel": "Tags",
"_main__crushcoin.checkincard.text.div": "Not Started",
"_main__crushcoin.checkincard.alt.image_alt": "Star",
"_main__crushcoin.checkingrid.toast.toast_success": "Signed in today",
"_.genaralimagecard.text.div": "%...",
"_.genaralimagecard.alt.image_alt": "image",
"_.genaralimagecard.text.p": "%...",
"_.generalbuytimesdialog.text.alertdialog": "Continue",
"_.generalbuytimesdialog.text.alertdialogcontent": "Continue",
"_.generalbuytimesdialog.alt.image_alt": "diamond",
"_.generalbuytimesdialog.text.div": "Continue",
"_.generalbuytimesdialog.text.h2": "Buy Times",
"_.generalbuytimesdialog.text.span": "/time",
"_.generalbuytimesdialog.text.button": "Continue",
"_.renderunlocktext.text.div": "Unlock: Free",
"_.genaralimagecardmultiple.text.div": "Generating...",
"_.genaralimagecardmultiple.text.p": "Generating...",
"_.generalimagelist.text.div": "Your image will show here",
"_.generalimagelist.text.p": "Your image will show here",
"_.generalimagewithcountbutton.text.button": "Buy Times",
"_.generalimagewithcountbutton.alt.image_alt": "vip",
"_.renderpayaction.text.div": "Unlock Method: Free",
"_.multiplevieweraction.text.div": "Select",
"_.validatefile.toast.toast_error": "Image files cannot exceed 10MB.",
"_.referenceupload.alt.img_alt": "Reference",
"_.referenceupload.text.div": "Click or drag image here to Upload",
"_.referenceupload.text.p": "Click or drag image here to Upload",
"_main__generate_image.generateimageformschema.validation.message": "Please enter",
"_main__generate_image.generateimagepage.text.div": "Confirm",
"_main__generate_image.generateimagepage.text.form": "Generate",
"_main__generate_image.generateimagepage.text.h1": "Generate an Image",
"_main__generate_image.generateimagepage.text.formitem": "Prompt",
"_main__generate_image.generateimagepage.text.formlabel": "Prompt",
"_main__generate_image.generateimagepage.placeholder.textarea_placeholder": "Please describe the character's skin color, clothing, hairstyle, facial features, movements, background, etc.",
"_main__generate_image.generateimagepage.text.button": "Confirm",
"_main__generate_image_2_background.generateimageformschema.validation.message": "Please enter",
"_main__generate_image_2_background.image2backgroundpage.text.div": "Confirm",
"_main__generate_image_2_background.image2backgroundpage.text.form": "AI will create based on the basic image of the character",
"_main__generate_image_2_background.image2backgroundpage.text.h1": "Generate an Image",
"_main__generate_image_2_background.image2backgroundpage.text.formitem": "Prompt",
"_main__generate_image_2_background.image2backgroundpage.text.formlabel": "Prompt",
"_main__generate_image_2_background.image2backgroundpage.placeholder.textarea_placeholder": "Please describe the character's skin color, clothing, hairstyle, facial features, movements, background, etc.",
"_main__generate_image_2_background.image2backgroundpage.alt.image_alt": "Diamond",
"_main__generate_image_2_background.image2backgroundpage.text.button": "Confirm",
"_main__generate_image_2_image.generateimageformschema.validation.message": "Please enter",
"_main__generate_image_2_image.imagepage.text.div": "Confirm",
"_main__generate_image_2_image.imagepage.text.form": "AI will create based on the basic image of the character",
"_main__generate_image_2_image.imagepage.text.h1": "Generate an Image",
"_main__generate_image_2_image.imagepage.text.formitem": "Prompt",
"_main__generate_image_2_image.imagepage.text.formlabel": "Prompt",
"_main__generate_image_2_image.imagepage.placeholder.textarea_placeholder": "Please describe the character's skin color, clothing, hairstyle, facial features, movements, background, etc.",
"_main__generate_image_2_image.imagepage.text.button": "Confirm",
"_main__generate_image_edit.generateimageformschema.validation.message": "Please enter",
"_main__generate_image_edit.imageeditpage.text.div": "Confirm",
"_main__generate_image_edit.imageeditpage.text.form": "AI will create based on the basic image of the character.",
"_main__generate_image_edit.imageeditpage.text.h1": "Generate an Image",
"_main__generate_image_edit.imageeditpage.text.formitem": "Prompt",
"_main__generate_image_edit.imageeditpage.text.formlabel": "Prompt",
"_main__generate_image_edit.imageeditpage.placeholder.textarea_placeholder": "Please describe the character's skin color, clothing, hairstyle, facial features, movements, background, etc.",
"_main__generate_image_edit.imageeditpage.text.button": "Confirm",
"_.homeheader.text.div": "Daily Free Crush Coin",
"_.homeheader.text.link": "Daily Free Crush Coin",
"_.homeheader.alt.image_alt": "Banner",
"_.leaderboardbanner.text.div": "Gifts",
"_.leaderboardbanner.text.chip": "Gifts",
"_main__leaderboard.largerankcard.text.link": "1st",
"_main__leaderboard.largerankcard.text.div": "1st",
"_main__leaderboard.topheader.text.div": "No leaderboard data yet",
"_main__profile_account.handledisableaccount.toast.toast_success": "Account disabled successfully",
"_main__profile_account.accountpage.text.div": "Disable",
"_main__profile_account.accountpage.text.h1": "Account",
"_main__profile_account.accountpage.text.alertdialog": "Disable",
"_main__profile_account.accountpage.text.alertdialogtrigger": "Disable the Account",
"_main__profile_account.accountpage.text.button": "Disable the Account",
"_main__profile_account.accountpage.text.alertdialogcontent": "Disable",
"_main__profile_account.accountpage.text.alertdialogheader": "Disable the Account",
"_main__profile_account.accountpage.text.alertdialogtitle": "Disable the Account",
"_main__profile_account.accountpage.text.alertdialogdescription": "Are you sure you want to disable your account?",
"_main__profile_account.accountpage.text.alertdialogfooter": "Disable",
"_main__profile_account.accountpage.text.alertdialogcancel": "Cancel",
"_main__profile_account.accountpage.text.alertdialogaction": "Disable",
"_main__profile.handlefileupload.toast.toast_error": "Image files cannot exceed 10MB.",
"_main__profile.avatarsetting.text.div": "Upload",
"_main__profile.avatarsetting.text.p": "The avatar must be in JPG, JPEG, or PNG format, and the file size cannot exceed 10MB.",
"_main__profile.avatarsetting.text.button": "Upload",
"_main__profile.charactercardadd.text.div": "Add a Character",
"_main__profile.charactercardadd.alt.image_alt": "VIP",
"_main__profile.charactercardadd.text.span": "Add a Character",
"_main__profile.charactercardvipadd.text.div": "Add More Character",
"_main__profile.charactercardvipadd.text.span": "Add More Character",
"_main__profile.characterlist.text.div": "Unlock more",
"_main__profile.characterlist.text.h2": "Characters",
"_main__profile.characterlist.text.span": "Unlock more",
"_main__profile.characterlist.alt.img_alt": "VIP",
"_main__profile.profiledropdown.text.div": "Cancel",
"_main__profile.profiledropdown.text.dropdownmenu": "Log out",
"_main__profile.profiledropdown.text.dropdownmenucontent": "Log out",
"_main__profile.profiledropdown.text.link": "Privacy Policy",
"_main__profile.profiledropdown.text.profiledropdownitem": "Log out",
"_main__profile.profiledropdown.text.alertdialog": "Cancel",
"_main__profile.profiledropdown.text.alertdialogcontent": "Cancel",
"_main__profile.profiledropdown.text.alertdialogheader": "Log out",
"_main__profile.profiledropdown.text.alertdialogtitle": "Log out",
"_main__profile.profiledropdown.text.alertdialogdescription": "Are you sure you want to log out?",
"_main__profile.profiledropdown.text.alertdialogfooter": "Log out",
"_main__profile.profiledropdown.text.alertdialogcancel": "Cancel",
"_main__profile.profiledropdown.text.alertdialogaction": "Log out",
"_main__profile.profilefeaturelist.text.div": "Creator",
"_main__profile.profilefeaturelist.text.link": "Creator",
"_main__profile.profilefeaturelist.alt.image_alt": "Creator",
"_main__profile.profilefeaturelist.text.span": "Creator",
"_main__profile_edit.schema.validation.message": "Please select a gender",
"_main__profile_edit.onsubmit.error.form_seterror": "Nickname already exists",
"_main__profile_edit.onsubmit.validation.message": "Nickname already exists",
"_main__profile_edit.editpage.text.div": "Save",
"_main__profile_edit.editpage.text.h1": "Edit Profile",
"_main__profile_edit.editpage.text.form": "Save",
"_main__profile_edit.editpage.text.formitem": "Age",
"_main__profile_edit.editpage.text.formlabel": "Age",
"_main__profile_edit.editpage.placeholder.input_placeholder": "Placeholder",
"_main__profile_edit.editpage.text.p": "Can not edit after setting up",
"_main__profile_edit.editpage.placeholder.selectvalue_placeholder": "Day",
"_main__profile_edit.editpage.text.button": "Save",
"_main__user__userid_.notfound.title.empty_title": "Oops, theres nothing here…",
"_main__wallet.incomeitem.text.div": "+",
"_main__wallet.incomeitem.alt.image_alt": "Diamond",
"_main__wallet.incomeemptystate.title.empty_title": "No income record",
"_main__wallet.incomelist.text.div": "Income Details",
"_main__wallet.incomelist.text.h2": "Income Details",
"_main__wallet.handlepayment.toast.toast_error": "Payment failure, please try again",
"_main__wallet.rechargelist.text.div": "Agreement",
"_main__wallet.rechargelist.text.h2": "Recharge",
"_main__wallet.rechargelist.alt.image_alt": "diamond",
"_main__wallet.rechargelist.text.span": "Agreement",
"_main__wallet.rechargelist.text.link": "Agreement",
"_main__wallet_transactions.transactionspage.text.div": "Transaction Details",
"_main__chat__aiid_.chatbackground.alt.image_alt": "Background",
"_main__chat__aiid_.chatmessageuserheader.text.div": "All responses are AI-Generated and fictional.",
"_main__chat__aiid_.crushlevelaction.alt.image_alt": "Heart",
"_main__create.voiceselector.text.div": "Tap to Listen",
"_main__create.voiceselector.label.voicetoneslider_label": "Speed",
"_main__create.voiceselector.text.span": "Tap to Listen",
"_main__edit__aiid__character.characterformschema.validation.message": "Please select gender",
"_main__edit__aiid__character.onsubmit.error.form_seterror": "Nickname already exists",
"_main__edit__aiid__character.onsubmit.validation.message": "Nickname already exists",
"_main__edit__aiid__character.characterform.text.div": "Next",
"_main__edit__aiid__character.characterform.text.h1": "Edit Character",
"_main__edit__aiid__character.characterform.text.form": "Next",
"_main__edit__aiid__character.characterform.text.h2": "Character",
"_main__edit__aiid__character.characterform.text.formitem": "Preferences",
"_main__edit__aiid__character.characterform.text.formlabel": "Preferences",
"_main__edit__aiid__character.characterform.placeholder.input_placeholder": "Enter character name",
"_main__edit__aiid__character.characterform.placeholder.selectvalue_placeholder": "Day",
"_main__edit__aiid__character.characterform.placeholder.textarea_placeholder": "Describe the character's background, personality, and identity",
"_main__edit__aiid__character.characterform.text.button": "Next",
"_main__edit__aiid_.closeiconbutton.text.alertdialog": "Quit",
"_main__edit__aiid_.closeiconbutton.text.alertdialogcontent": "Quit",
"_main__edit__aiid_.closeiconbutton.text.alertdialogheader": "The content has not been saved, Continue to quit?",
"_main__edit__aiid_.closeiconbutton.text.alertdialogtitle": "Unsaved changes",
"_main__edit__aiid_.closeiconbutton.text.alertdialogdescription": "The content has not been saved, Continue to quit?",
"_main__edit__aiid_.closeiconbutton.text.alertdialogfooter": "Quit",
"_main__edit__aiid_.closeiconbutton.text.alertdialogcancel": "Cancel",
"_main__edit__aiid_.closeiconbutton.text.alertdialogaction": "Quit",
"_main__edit__aiid__dialogue.dialogueformschema.validation.message": "Please select voice",
"_main__edit__aiid__dialogue.dialogueform.text.div": "Next",
"_main__edit__aiid__dialogue.dialogueform.text.h1": "Edit Character",
"_main__edit__aiid__dialogue.dialogueform.text.form": "Next",
"_main__edit__aiid__dialogue.dialogueform.text.h2": "Dialogue",
"_main__edit__aiid__dialogue.dialogueform.text.formitem": "Voice Tone",
"_main__edit__aiid__dialogue.dialogueform.text.formlabel": "Voice Tone",
"_main__edit__aiid__dialogue.dialogueform.placeholder.textarea_placeholder": "Please describe the way the characters chat, the tone of the dialogue",
"_main__edit__aiid__dialogue.dialogueform.text.span": "(Optional)",
"_main__edit__aiid__dialogue.dialogueform.text.label": "Suggestions",
"_main__edit__aiid__dialogue.dialogueform.text.tabs": "Female",
"_main__edit__aiid__dialogue.dialogueform.text.tabslist": "Female",
"_main__edit__aiid__dialogue.dialogueform.text.tabstrigger": "Female",
"_main__edit__aiid__dialogue.dialogueform.text.button": "Next",
"_main__edit__aiid__image.imageformschema.validation.message": "请上传或生成一张图片",
"_main__edit__aiid__image.imageform.text.div": "Submit",
"_main__edit__aiid__image.imageform.text.h1": "Edit Character",
"_main__edit__aiid__image.imageform.text.form": "Submit",
"_main__edit__aiid__image.imageform.text.h2": "Image",
"_main__edit__aiid__image.imageform.text.formitem": "Public",
"_main__edit__aiid__image.imageform.text.formlabel": "Privacy",
"_main__edit__aiid__image.imageform.alt.image_alt": "Avatar",
"_main__edit__aiid__image.imageform.placeholder.textarea_placeholder": "Introduce the virtual character to the user",
"_main__edit__aiid__image.imageform.text.chip": "Privacy",
"_main__edit__aiid__image.imageform.text.button": "Submit",
"_main__edit__aiid__type.typeform.text.div": "Next",
"_main__edit__aiid__type.typeform.text.h1": "Edit Character",
"_main__edit__aiid__type.typeform.text.form": "Next",
"_main__edit__aiid__type.typeform.text.h2": "Type",
"_main__edit__aiid__type.typeform.text.formitem": "Tags",
"_main__edit__aiid__type.typeform.text.formlabel": "Tags",
"_main__edit__aiid__type.typeform.text.button": "Next",
"_.filterdrawer.text.drawer": "Confirm",
"_.filterdrawer.text.drawercontent": "Confirm",
"_.filterdrawer.text.drawerheader": "Filter",
"_.filterdrawer.text.div": "Confirm",
"_.filterdrawer.text.drawertitle": "Filter",
"_.filterdrawer.text.h3": "Type",
"_.filterdrawer.text.button": "Confirm",
"_.meetcard.aria.div_aria_hidden": "true",
"_.meetheader.text.div": "#",
"_.meetheader.text.h1": "Meet",
"_.meetheader.text.tabs": "All",
"_.meetheader.text.tabslist": "All",
"_.meetheader.text.tabstrigger": "All",
"_.meetheader.text.span": "All",
"_.meetheader.text.chip": "#",
"_.errorstate.text.div": "Reload",
"_.errorstate.text.h3": "Load failed",
"_.errorstate.text.p": "The network connection is abnormal. Please try again later",
"_.errorstate.text.button": "Reload",
"_main__user__userid_.aboutsection.text.div": "Introduce",
"_main__user__userid_.aboutsection.text.h3": "Introduce",
"_main__user__userid_.renderpayaction.text.div": "Unlock Method: Free",
"_main__user__userid_.renderpayaction.alt.image_alt": "diamond",
"_main__user__userid_.renderdefaultaction.text.div": "Default",
"_main__user__userid_.renderdefaultaction.text.alertdialog": "Confirm",
"_main__user__userid_.renderdefaultaction.text.alertdialogcontent": "Confirm",
"_main__user__userid_.renderdefaultaction.text.alertdialogheader": "Default image",
"_main__user__userid_.renderdefaultaction.text.alertdialogtitle": "Default image",
"_main__user__userid_.renderdefaultaction.text.alertdialogdescription": "After setting as the default picture, the unlock method of the picture can only be \"free\".",
"_main__user__userid_.renderdefaultaction.text.alertdialogfooter": "Confirm",
"_main__user__userid_.renderdefaultaction.text.alertdialogcancel": "Cancel",
"_main__user__userid_.renderdefaultaction.text.button": "Confirm",
"_main__user__userid_.albumimagevieweraction.text.button": "Unlock",
"_main__user__userid_.albumimagevieweraction.text.div": "Like",
"_main__user__userid_.renderoverlay.text.div": "Unlock",
"_main__user__userid_.renderdefaulttag.text.tag": "Default",
"_main__user__userid_.albumitem.alt.image_alt": "Album image",
"_main__user__userid_.albumitemaction.text.dropdownmenu": "Delete",
"_main__user__userid_.albumitemaction.text.dropdownmenucontent": "Delete",
"_main__user__userid_.albumitemaction.text.albumdeletealert": "Delete",
"_main__user__userid_.albumitemaction.text.dropdownmenuitem": "Delete",
"_main__user__userid_.albumitemaction.text.span": "Delete",
"_main__user__userid_.handleunlock.toast.toast_success": "Unlocked successfully!",
"_main__user__userid_.albumlist.title.empty_title": "No photos yet",
"_main__user__userid_.giftgrid.text.div": "X",
"_main__user__userid_.giftgrid.text.h3": "Gift",
"_main__user__userid_.giftgrid.title.empty_title": "No gifts yet",
"_main__user__userid_.tabnavigation.text.div": "Album",
"_main__user__userid_.tabnavigation.text.button": "Album",
"_main__user__userid_.useractiondropdown.text.div": "Delete Character",
"_main__user__userid_.useractiondropdown.text.dropdownmenu": "Delete Character",
"_main__user__userid_.useractiondropdown.text.dropdownmenucontent": "Delete Character",
"_main__user__userid_.useractiondropdown.text.dropdownmenuitem": "Delete Character",
"_main__user__userid_.useractiondropdown.text.span": "Delete Character",
"_main__user__userid_.useractiondropdown.text.alertdialog": "Delete",
"_main__user__userid_.useractiondropdown.text.alertdialogcontent": "Delete",
"_main__user__userid_.useractiondropdown.text.alertdialogheader": "Delete Character",
"_main__user__userid_.useractiondropdown.text.alertdialogtitle": "Delete Character",
"_main__user__userid_.useractiondropdown.text.alertdialogdescription": "Are you sure you want to delete this character?",
"_main__user__userid_.useractiondropdown.text.alertdialogfooter": "Delete",
"_main__user__userid_.useractiondropdown.text.alertdialogaction": "Delete",
"_main__user__userid_.usercard.text.button": "Chat",
"_main__user__userid_.usercard.alt.image_alt": "Gender",
"_main__user__userid_.userprofiletabs.text.div": "Create a Photo",
"_main__user__userid_.userprofiletabs.text.tabslist": "Album",
"_main__user__userid_.userprofiletabs.text.tabstrigger": "Album",
"_main__user__userid_.userprofiletabs.text.span": "Album",
"_main__user__userid_.userprofiletabs.text.button": "Create a Photo",
"_main__user__userid_.usershare.text.dropdownmenu": "Share to X",
"_main__user__userid_.usershare.text.dropdownmenucontent": "Share to X",
"_main__user__userid_.usershare.text.dropdownmenuitem": "Share to X",
"_main__user__userid_.usershare.text.span": "Share to X",
"_main__vip.subscribetext.text.div": "$5.99/month",
"_main__vip.subscribetext.text.span": "$5.99/month",
"_main__vip.subscribeproductitem.text.div": ".",
"_main__vip.subscribevipdrawer.text.drawer": "Subscribe Now",
"_main__vip.subscribevipdrawer.text.drawercontent": "Subscribe Now",
"_main__vip.subscribevipdrawer.text.div": "CrushLevel Vip",
"_main__vip.subscribevipdrawer.text.drawerheader": "CrushLevel Vip",
"_main__vip.subscribevipdrawer.text.drawertitle": "CrushLevel Vip",
"_main__wallet_charge_result.resultpage.text.div": "Payment in progress, please do not leave the current page",
"_main__wallet_charge_result.resultpage.alt.image_alt": "pending",
"_main__wallet_charge_result.resultpage.text.button": ")",
"_main__wallet.handlewithdraw.dialog.alert": "No amount available for withdrawal",
"_main__wallet.incomecard.text.div": "Withdraw",
"_main__wallet.incomecard.alt.image_alt": "diamond-icon",
"_main__wallet.incomecard.text.tooltip": "Earnings can be withdrawn after 30 days",
"_main__wallet.incomecard.text.tooltipcontent": "Earnings can be withdrawn after 30 days",
"_main__wallet.incomecard.text.p": "Earnings can be withdrawn after 30 days",
"_main__wallet.incomecard.text.button": "Withdraw",
"_main__wallet.incomecard.text.span": "Withdraw",
"_main__wallet.rechargecard.text.div": "Crush Coin Balance",
"_main__wallet_transactions.diamondicon.alt.image_alt": "Diamond",
"_main__wallet_transactions.transactionemptystate.title.empty_title": "No transaction records",
"_main__chat__aiid_.renderaction.text.button": "Click to interrupt",
"_main__chat__aiid_.renderaction.text.div": "Listening...",
"_main__chat__aiid_.chatcallstatus.text.div": "Waiting to be connected",
"_main__chat__aiid_.backgroundimagevieweraction.text.div": "Select",
"_main__chat__aiid_.backgrounditem.text.div": "Default",
"_main__chat__aiid_.backgrounditem.text.tag": "Default",
"_main__chat__aiid_.chatbackgrounddrawer.text.inlinedrawer": "Confirm",
"_main__chat__aiid_.chatbackgrounddrawer.text.inlinedrawercontent": "Confirm",
"_main__chat__aiid_.chatbackgrounddrawer.text.inlinedrawerheader": "Chat Background",
"_main__chat__aiid_.chatbackgrounddrawer.text.inlinedrawerdescription": "Create Image",
"_main__chat__aiid_.chatbackgrounddrawer.text.div": "Create Image",
"_main__chat__aiid_.chatbackgrounddrawer.text.inlinedrawerfooter": "Confirm",
"_main__chat__aiid_.chatbackgrounddrawer.text.button": "Confirm",
"_main__chat__aiid_.chatbuttleitem.text.div": "Hi",
"_main__chat__aiid_.chatbuttleitem.text.chatbubble": "Hi",
"_main__chat__aiid_.renderconfirmbutton.text.button": "Unlock",
"_main__chat__aiid_.renderconfirmbutton.text.div": "Unlock",
"_main__chat__aiid_.renderconfirmbutton.alt.image_alt": "vip",
"_main__chat__aiid_.renderconfirmbutton.text.span": "Unlock",
"_main__chat__aiid_.chatbuttledrawer.text.inlinedrawer": "Chat Buttle",
"_main__chat__aiid_.chatbuttledrawer.text.inlinedrawercontent": "Chat Buttle",
"_main__chat__aiid_.chatbuttledrawer.text.inlinedrawerheader": "Chat Buttle",
"_main__chat__aiid_.chatmodeldrawer.text.inlinedrawer": "Save",
"_main__chat__aiid_.chatmodeldrawer.text.inlinedrawercontent": "Save",
"_main__chat__aiid_.chatmodeldrawer.text.inlinedrawerheader": "Chat Model",
"_main__chat__aiid_.chatmodeldrawer.text.inlinedrawerdescription": "/Voice call message",
"_main__chat__aiid_.chatmodeldrawer.text.div": "/Voice call message",
"_main__chat__aiid_.chatmodeldrawer.text.tooltip": "Voice Call Message price refers to the cost of having a voice call conversation with a character. It is charged per message.",
"_main__chat__aiid_.chatmodeldrawer.text.tooltipcontent": "Voice Call Message price refers to the cost of having a voice call conversation with a character. It is charged per message.",
"_main__chat__aiid_.chatmodeldrawer.text.p": "Voice Call Message price refers to the cost of having a voice call conversation with a character. It is charged per message.",
"_main__chat__aiid_.chatmodeldrawer.alt.image_alt": "diamond",
"_main__chat__aiid_.chatmodeldrawer.text.span": "/Voice call message",
"_main__chat__aiid_.chatmodeldrawer.text.inlinedrawerfooter": "Save",
"_main__chat__aiid_.chatmodeldrawer.text.button": "Save",
"_main__chat__aiid_.characterformschema.validation.message": "Please select gender",
"_main__chat__aiid_.onsubmit.error.form_seterror": "Nickname already exists",
"_main__chat__aiid_.onsubmit.validation.message": "Nickname already exists",
"_main__chat__aiid_.chatprofileeditdrawer.text.inlinedrawer": "(Optional)",
"_main__chat__aiid_.chatprofileeditdrawer.text.inlinedrawercontent": "(Optional)",
"_main__chat__aiid_.chatprofileeditdrawer.text.inlinedrawerheader": "My Chat Personal",
"_main__chat__aiid_.chatprofileeditdrawer.text.inlinedrawerdescription": "(Optional)",
"_main__chat__aiid_.chatprofileeditdrawer.text.form": "(Optional)",
"_main__chat__aiid_.chatprofileeditdrawer.text.formitem": "(Optional)",
"_main__chat__aiid_.chatprofileeditdrawer.text.formlabel": "(Optional)",
"_main__chat__aiid_.chatprofileeditdrawer.placeholder.input_placeholder": "Enter nickname",
"_main__chat__aiid_.chatprofileeditdrawer.text.div": "Birthday",
"_main__chat__aiid_.chatprofileeditdrawer.text.label": "Birthday",
"_main__chat__aiid_.chatprofileeditdrawer.placeholder.selectvalue_placeholder": "Day",
"_main__chat__aiid_.chatprofileeditdrawer.text.span": "(Optional)",
"_main__chat__aiid_.chatprofileeditdrawer.placeholder.textarea_placeholder": "Describe the character's background and personality traits.",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialog": "Exit",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialogcontent": "Exit",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialogheader": "The edited content will not be saved after exiting. Please confirm whether to continue exiting?",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialogtitle": "Unsaved Edits",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialogdescription": "The edited content will not be saved after exiting. Please confirm whether to continue exiting?",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialogfooter": "Exit",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialogcancel": "Cancel",
"_main__chat__aiid_.chatprofileeditdrawer.text.alertdialogaction": "Exit",
"_main__chat__aiid_.crushlevelretrievedrawer.text.inlinedrawer": "Purchase",
"_main__chat__aiid_.crushlevelretrievedrawer.text.inlinedrawercontent": "Purchase",
"_main__chat__aiid_.crushlevelretrievedrawer.alt.image_alt": "Crush Level",
"_main__chat__aiid_.crushlevelretrievedrawer.text.div": "Purchase",
"_main__chat__aiid_.crushlevelretrievedrawer.text.inlinedrawerheader": "Retrieve",
"_main__chat__aiid_.crushlevelretrievedrawer.text.inlinedrawerdescription": "Total",
"_main__chat__aiid_.crushlevelretrievedrawer.text.inlinedrawerfooter": "Purchase",
"_main__chat__aiid_.crushlevelretrievedrawer.text.button": "Purchase",
"_main__chat__aiid_.handlesendgift.toast.toast_error": "Gift sending failed. Please try again.",
"_main__chat__aiid_.getbutton.text.button": "Gift",
"_main__chat__aiid_.sendgiftsdrawer.text.inlinedrawer": "Balance:",
"_main__chat__aiid_.sendgiftsdrawer.text.inlinedrawercontent": "Balance:",
"_main__chat__aiid_.sendgiftsdrawer.text.inlinedrawerheader": "Send Gifts",
"_main__chat__aiid_.sendgiftsdrawer.text.inlinedrawerfooter": "Balance:",
"_main__chat__aiid_.sendgiftsdrawer.text.div": "Balance:",
"_main__chat__aiid_.sendgiftsdrawer.text.span": "Balance:",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialog": "Go",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialogcontent": "Go",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialogheader": "Create Album",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialogtitle": "Create Album",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialogdescription": "Generate images for your character's album to attract fans and earn revenue.",
"_main__chat__aiid_.chatfirstguidedialog.text.p": "Generate images for your character's album to attract fans and earn revenue.",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialogfooter": "Go",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialogcancel": "Not now",
"_main__chat__aiid_.chatfirstguidedialog.text.alertdialogaction": "Go",
"_main__chat__aiid_.aireplysuggestions.text.div": "/",
"_main__chat__aiid_.aireplysuggestions.text.p": "Choose one or edit",
"_main__chat__aiid_.aireplysuggestions.text.span": "/",
"_main__chat__aiid_.chatactionplus.text.dropdownmenu": "Share to X",
"_main__chat__aiid_.chatactionplus.text.dropdownmenucontent": "Share to X",
"_main__chat__aiid_.chatactionplus.text.dropdownmenuitem": "Share to X",
"_main__chat__aiid_.chatactionplus.text.span": "Share to X",
"_main__chat__aiid_.chatactionplus.text.div": "Share to",
"_main__chat__aiid_._______uploading__imageuploading_______uploadfile_______cancelupload______error__uploaderror______progress__uploadprogress_____.validation.message": "图片上传失败,请重试",
"_main__chat__aiid_.handleimageselect.validation.message": "图片处理失败,请重试",
"_main__chat__aiid_.handlevoicerecord.toast.toast_error": "Voice too short",
"_main__chat__aiid_.chatmessageaction.placeholder.chatinput_placeholder": "Enter some thing here",
"_main__chat__aiid_.debouncedfeedback.toast.toast_error": "Operation failed, please try again",
"_main__chat__aiid_.handlecopy.toast.toast_success": "Copied to clipboard",
"_main__chat__aiid_.chatothertextcontainer.text.div": "Bad",
"_main__chat__aiid_.chatothertextcontainer.text.span": "|",
"_main__chat__aiid_.chatothertextcontainer.text.tooltip": "Bad",
"_main__chat__aiid_.chatothertextcontainer.text.tooltipcontent": "Bad",
"_main__chat__aiid_.chatothertextcontainer.text.p": "Bad",
"_main__chat__aiid_.chatusertextcontainer.alt.image_alt": "Sending-failed",
"_main__chat__aiid_.crushlevelavatar.text.div": "/profile",
"_main__chat__aiid_.crushlevelavatar.alt.image_alt": "heart",
"_main__chat__aiid_.crushlevelavatar.text.link": "/profile",
"_main__chat__aiid_.chatprofileaction.text.div": "Delete",
"_main__chat__aiid_.chatprofileaction.text.dropdownmenu": "Delete",
"_main__chat__aiid_.chatprofileaction.text.dropdownmenucontent": "Delete",
"_main__chat__aiid_.chatprofileaction.text.dropdownmenuitem": "Delete",
"_main__chat__aiid_.chatprofileaction.text.span": "Delete",
"_main__chat__aiid_.chatprofilepersona.text.div": "Who i am",
"_main__chat__aiid_.deletemessagedialog.text.alertdialog": "Deletion is permanent. Your accumulated Affection points and the character's memories will not be affected. Please confirm deletion.",
"_main__chat__aiid_.deletemessagedialog.text.alertdialogcontent": "Deletion is permanent. Your accumulated Affection points and the character's memories will not be affected. Please confirm deletion.",
"_main__chat__aiid_.deletemessagedialog.text.alertdialogheader": "Delete",
"_main__chat__aiid_.deletemessagedialog.text.alertdialogtitle": "Delete",
"_main__chat__aiid_.deletemessagedialog.text.alertdialogdescription": "Deletion is permanent. Your accumulated Affection points and the character's memories will not be affected. Please confirm deletion.",
"_main__chat__aiid_.deletemessagedialog.text.alertdialogfooter": "Delete",
"_main__chat__aiid_.deletemessagedialog.text.alertdialogaction": "Delete",
"_main__chat__aiid_.chatprofiledrawer.text.inlinedrawer": "Auto play voice",
"_main__chat__aiid_.chatprofiledrawer.text.inlinedrawercontent": "Auto play voice",
"_main__chat__aiid_.chatprofiledrawer.text.div": "Auto play voice",
"_main__chat__aiid_.chatprofiledrawer.alt.image_alt": "Gender",
"_main__chat__aiid_.renderlinetext.text.div": "·",
"_main__chat__aiid_.renderlinetext.text.span": "·",
"_main__chat__aiid_.crushleveldrawer.text.inlinedrawer": "Hide Relationship",
"_main__chat__aiid_.crushleveldrawer.text.inlinedrawercontent": "Hide Relationship",
"_main__chat__aiid_.crushleveldrawer.text.div": "Hide Relationship",
"_main__chat__aiid_.crushleveldrawer.text.inlinedrawerheader": "Hide Relationship",
"_main__chat__aiid_.crushleveldrawer.text.tooltip": "* The crush value will increase the crush level, and unlock titles, functions, and different character dialogue stages through upgrades",
"_main__chat__aiid_.crushleveldrawer.text.tooltipcontent": "* The crush value will increase the crush level, and unlock titles, functions, and different character dialogue stages through upgrades",
"_main__chat__aiid_.crushleveldrawer.text.p": "* The crush value will increase the crush level, and unlock titles, functions, and different character dialogue stages through upgrades",
"_main__chat__aiid_.crushleveldrawer.text.dropdownmenu": "Hide Relationship",
"_main__chat__aiid_.crushleveldrawer.text.dropdownmenucontent": "Hide Relationship",
"_main__chat__aiid_.crushleveldrawer.text.dropdownmenuitem": "Hide Relationship",
"_main__chat__aiid_.crushleveldrawer.text.span": "&#8451;",
"_main__chat__aiid_.crushleveldrawer.text.button": "Retrieve",
"_main__chat__aiid_.callcancelitem.text.div": "Call Canceled",
"_main__chat__aiid_.chatimagecontainer.text.div": "×",
"_main__chat__aiid_.handleunlock.toast.toast_success": "Unlock success",
"_main__chat__aiid_.handleunlock.toast.toast_error": "Unlock failed"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 128 B

View File

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

Before

Width:  |  Height:  |  Size: 385 B

View File

@ -1,6 +1,6 @@
import Empty from '@/components/ui/empty'
import Image from 'next/image'
import Link from 'next/link'
import Empty from '@/components/ui/empty';
import Link from 'next/link';
import IconFont from '@/components/ui/iconFont';
export default async function NotFound() {
return (
@ -8,17 +8,11 @@ export default async function NotFound() {
<div className="flex flex-col items-center gap-4">
<Link href="/">
<div className="relative mx-auto h-[88px] w-[221px]">
<Image
src="/icons/login-logo.svg"
alt="Anime character"
fill
className="object-contain"
priority
/>
<IconFont type="icon-Logo" size={160} />
</div>
</Link>
<Empty title="Oops, theres nothing here…" />
</div>
</div>
)
);
}

View File

@ -41,20 +41,20 @@ const ChatMessageUserHeader = React.memo(() => {
// 检测文本是否超过三行
useEffect(() => {
if (textRef.current && character.description) {
if (textRef.current && character?.description) {
// 直接比较滚动高度和可见高度
// 如果内容的实际高度大于容器的可见高度,说明内容被截断了
const isOverflowing = textRef.current.scrollHeight > textRef.current.clientHeight;
setShouldShowExpandButton(isOverflowing);
}
}, [character.description]);
}, [character?.description]);
return (
<div className="flex flex-col mt-4 items-center gap-6">
<CharacterAvatorAndName
id={characterId}
name={character.name || '-'}
avator={character.headPortrait || ''}
name={character?.name || '-'}
avator={character?.headPortrait || ''}
/>
<div className="bg-surface-element-normal border-outline-normal flex w-full flex-col gap-2 rounded-lg border border-solid p-4 backdrop-blur-2xl">
<div
@ -67,7 +67,7 @@ const ChatMessageUserHeader = React.memo(() => {
wordBreak: 'break-word',
}}
>
{character.description}
{character?.description}
</div>
<div className="flex items-center justify-between">

View File

@ -7,6 +7,7 @@ import React, { useState } from 'react';
import { Tag } from '@/components/ui/tag';
import { ImageViewer } from '@/components/ui/image-viewer';
import { useImageViewer } from '@/hooks/useImageViewer';
import { useTranslations } from 'next-intl';
type BackgroundItem = {
backgroundId: number;
@ -26,6 +27,7 @@ const BackgroundImageViewerAction = ({
isSelected: boolean;
onChange: (backgroundId: number) => void;
}) => {
const tCommon = useTranslations('common');
const handleSelect = () => {
// 如果只有一张背景且当前已选中,不允许取消选中
if (datas.length === 1 && isSelected) {
@ -42,7 +44,7 @@ const BackgroundImageViewerAction = ({
onClick={() => handleSelect()}
>
<Checkbox shape="round" checked={isSelected} />
<div className="txt-label-s">Select</div>
<div className="txt-label-s">{tCommon('select')}</div>
</div>
</>
);
@ -63,6 +65,7 @@ const BackgroundItemCard = ({
onImagePreview: () => void;
totalCount: number;
}) => {
const tCommon = useTranslations('common');
const handleClick = () => {
// 如果只有一张背景且当前已选中,不允许取消选中
if (totalCount === 1 && selected) {
@ -83,7 +86,7 @@ const BackgroundItemCard = ({
</div>
{item.isDefault && (
<Tag className="absolute top-2 left-2" variant="dark" size="small">
Default
{tCommon('default')}
</Tag>
)}
{inUse && <Checkbox shape="round" checked className="absolute top-2 right-2" />}

View File

@ -5,8 +5,10 @@ import { Checkbox } from '@/components/ui/checkbox';
import Image from 'next/image';
import { useModels } from '@/hooks/services/chat';
import { useStreamChatStore } from '../stream-chat';
import { useTranslations } from 'next-intl';
export default function ChatModel() {
const t = useTranslations('chat.drawer.chatModel');
const { data: models = [] } = useModels();
const chatSetting = useStreamChatStore((store) => store.chatSetting);
const setChatSetting = useStreamChatStore((store) => store.setChatSetting);
@ -30,17 +32,13 @@ export default function ChatModel() {
<TooltipContent className="max-w-[300px]">
<div className="space-y-2">
<p className="break-words">
Text Message Price: Refers to the cost of chatting with the character via
text messages, including sending text, images, or gifts. Charged per
message.
{t('textMessagePrice')}: {t('textMessagePriceDesc')}
</p>
<p className="break-words">
Voice Message Price: Refers to the cost of sending a voice message to the
character or playing the characters voice. Charged per use.
{t('voiceMessagePrice')}: {t('voiceMessagePriceDesc')}
</p>
<p className="break-words">
Voice Call Price: Refers to the cost of having a voice call with the
character. Charged per minute.
{t('voiceCallPrice')}: {t('voiceCallPriceDesc')}
</p>
</div>
</TooltipContent>
@ -48,33 +46,35 @@ export default function ChatModel() {
</div>
<Checkbox checked={chatSetting.chatModel === model} shape="round" />
</div>
<div className="txt-body-m text-txt-secondary-normal mt-1">
Role-play a conversation with AI
</div>
<div className="txt-body-m text-txt-secondary-normal mt-1">{t('rolePlayDesc')}</div>
<div className="bg-surface-district-normal mt-3 rounded-sm p-3">
<div className="flex items-center gap-1">
<Image src="/icons/diamond.svg" alt="diamond" width={16} height={16} />
<span className="txt-label-m text-txt-primary-normal">1/Text Message</span>
<span className="txt-label-m text-txt-primary-normal">1/{t('textMessage')}</span>
</div>
<div className="mt-3 flex items-center justify-between gap-1">
<div className="flex min-w-0 flex-1 items-center gap-1">
<Image src="/icons/diamond.svg" alt="diamond" width={16} height={16} />
<span className="txt-label-m text-txt-primary-normal">10/Send or play voice</span>
<span className="txt-label-m text-txt-primary-normal">
10/{t('sendOrPlayVoice')}
</span>
</div>
</div>
<div className="mt-3 flex items-center justify-between gap-1">
<div className="flex min-w-0 flex-1 items-center gap-1">
<Image src="/icons/diamond.svg" alt="diamond" width={16} height={16} />
<span className="txt-label-m text-txt-primary-normal">20/min Voice call</span>
<span className="txt-label-m text-txt-primary-normal">
20/{t('voiceCallPerMin')}
</span>
</div>
</div>
</div>
</div>
))}
<div className="txt-body-m text-txt-secondary-normal mt-6">Stay tuned for more models</div>
<div className="txt-body-m text-txt-secondary-normal mt-6">{t('stayTuned')}</div>
</div>
</div>
);

View File

@ -2,6 +2,7 @@
import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils';
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
import { useTranslations } from 'next-intl';
type FontOption = {
value: number;
@ -10,6 +11,7 @@ type FontOption = {
};
export default function Font() {
const t = useTranslations('chat.drawer.font');
const chatSetting = useStreamChatStore((store) => store.chatSetting);
const setChatSetting = useStreamChatStore((store) => store.setChatSetting);
@ -37,7 +39,7 @@ export default function Font() {
<div className="txt-title-s flex items-center gap-2">
{option.label}
{option.isStandard && (
<span className="txt-body-m text-txt-secondary-normal">(standard)</span>
<span className="txt-body-m text-txt-secondary-normal">({t('standard')})</span>
)}
</div>
<Checkbox shape="round" checked={chatSetting.font === option.value} />

View File

@ -16,6 +16,7 @@ import { useAsyncFn } from '@/hooks/tools';
import { useRouter } from 'next/navigation';
import { useModels } from '@/hooks/services/chat';
import { toast } from 'sonner';
import { useTranslations } from 'next-intl';
const genderMap = {
0: '/icons/male.svg',
@ -24,44 +25,46 @@ const genderMap = {
};
const ChatProfilePersona = React.memo(({ onActiveTab }: ProfileProps) => {
const t = useTranslations('chat.drawer');
const tCommon = useTranslations('common');
const whoAmI = 'whoAmI';
return (
<div className="flex w-full flex-col gap-3">
<div className="flex items-center justify-between gap-3">
<div className="txt-title-s">Masked Identity Mode</div>
<div className="txt-title-s">{t('maskedIdentityMode')}</div>
<div
className="txt-label-m text-primary-variant-normal cursor-pointer"
onClick={() => onActiveTab('mask')}
>
Edit
{tCommon('edit')}
</div>
</div>
<div className="bg-surface-element-normal rounded-m py-1">
<div className="flex items-center justify-between gap-4 px-4 py-3">
<div className="txt-label-l text-txt-secondary-normal">Nickname</div>
<div className="txt-label-l text-txt-secondary-normal">{t('profile.nickname')}</div>
<div className="txt-body-l text-txt-primary-normal flex-1 truncate text-right">{''}</div>
</div>
<div className="flex items-center justify-between gap-4 px-4 py-3">
<div className="txt-label-l text-txt-secondary-normal">Gender</div>
<div className="txt-label-l text-txt-secondary-normal">{t('profile.gender')}</div>
<div className="txt-body-l text-txt-primary-normal">
{genderMap[0 as keyof typeof genderMap]}
</div>
</div>
<div className="flex items-center justify-between gap-4 px-4 py-3">
<div className="txt-label-l text-txt-secondary-normal">Age</div>
<div className="txt-label-l text-txt-secondary-normal">{t('profile.age')}</div>
<div className="txt-body-l text-txt-primary-normal">{getAge(Number(23))}</div>
</div>
<div className="flex items-center justify-between gap-4 px-4 py-3">
<div className="txt-label-l text-txt-secondary-normal">Who am I</div>
<div className="txt-label-l text-txt-secondary-normal">{t('profile.whoAmI')}</div>
<div
className={cn(
'txt-body-l text-txt-primary-normal flex-1 truncate text-right',
whoAmI ? 'text-txt-primary-normal' : 'text-txt-secondary-normal'
)}
>
{whoAmI || 'Unfilled'}
{whoAmI || t('profile.unfilled')}
</div>
</div>
</div>
@ -80,6 +83,8 @@ type ProfileProps = {
};
export default function Profile({ onActiveTab }: ProfileProps) {
const t = useTranslations('chat.drawer.profile');
const tCommon = useTranslations('common');
const { id } = useParams<{ id: string }>();
const characterId = id.split('-')[2];
const {} = useModels();
@ -114,12 +119,12 @@ export default function Profile({ onActiveTab }: ProfileProps) {
[
{
onClick: () => onActiveTab('model'),
label: 'Chat Model',
label: t('chatModel'),
value: chatSetting?.chatModel,
},
{
onClick: () => null,
label: 'Long text',
label: t('longText'),
value: (
<Switch
onCheckedChange={() => setChatSetting({ longText: chatSetting.longText ? 0 : 1 })}
@ -131,19 +136,19 @@ export default function Profile({ onActiveTab }: ProfileProps) {
[
{
onClick: () => onActiveTab('max_token'),
label: 'Maximum Replies',
label: t('maximumReplies'),
value: String(chatSetting?.maximumReplies),
},
],
[
{
onClick: () => onActiveTab('font'),
label: 'Font',
label: t('font'),
value: String(chatSetting?.font),
},
{
onClick: () => onActiveTab('background'),
label: 'Chat Background',
label: t('chatBackground'),
value: String(chatSetting?.background),
},
],
@ -153,8 +158,8 @@ export default function Profile({ onActiveTab }: ProfileProps) {
[
{
onClick: () => onActiveTab('voice_actor'),
label: 'Voice Artist',
value: 'Default',
label: t('voiceArtist'),
value: tCommon('default'),
},
],
];
@ -200,14 +205,14 @@ export default function Profile({ onActiveTab }: ProfileProps) {
<div className="flex flex-1 overflow-y-auto pr-1 show-scrollbar flex-col gap-4">
<CharacterAvatorAndName
id={characterId}
avator={character.headPortrait}
name={character.name}
avator={character?.headPortrait}
name={character?.name}
/>
{/* Tags */}
<div className="flex w-full flex-col items-start justify-start gap-4">
<div className="flex w-full flex-wrap items-start justify-center gap-2">
{character.tags?.map((tag: any) => (
{character?.tags?.map((tag: any) => (
<Tag key={tag.tagId}>{tag.name}</Tag>
))}
</div>
@ -215,9 +220,9 @@ export default function Profile({ onActiveTab }: ProfileProps) {
<ChatProfilePersona onActiveTab={onActiveTab} />
{bundleRender('Chat Setting', chatSettingItems)}
{bundleRender(t('chatSetting'), chatSettingItems)}
{bundleRender('Voice Setting', voiceSettingItems)}
{bundleRender(t('voiceSetting'), voiceSettingItems)}
</div>
<div className="flex flex-col gap-2 mt-2">
<Button
@ -226,7 +231,7 @@ export default function Profile({ onActiveTab }: ProfileProps) {
loading={deleting}
onClick={deleteChannelAsync}
>
Detele
{t('delete')}
</Button>
<Button
variant="primary"
@ -234,7 +239,7 @@ export default function Profile({ onActiveTab }: ProfileProps) {
onClick={createChannelAndPush}
className="w-full"
>
+ New Chat
+ {t('newChat')}
</Button>
</div>
</div>

View File

@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
import { useTranslations } from 'next-intl';
type VoiceGender = 'all' | 'male' | 'female';
@ -17,6 +18,7 @@ type VoiceActorItem = {
};
export default function VoiceActor() {
const t = useTranslations('chat.drawer.voiceActor');
const chatSetting = useStreamChatStore((store) => store.chatSetting);
const setChatSetting = useStreamChatStore((store) => store.setChatSetting);
// 语音演员列表(静态数据)
@ -24,49 +26,49 @@ export default function VoiceActor() {
{
id: 1,
name: 'Voice Actor 1',
description: 'Have a role-playing conversation with AI',
description: t('description'),
avatarUrl: 'https://i.pravatar.cc/150?img=1',
gender: 'female',
},
{
id: 2,
name: 'Voice Actor 2',
description: 'Have a role-playing conversation with AI',
description: t('description'),
avatarUrl: 'https://i.pravatar.cc/150?img=2',
gender: 'female',
},
{
id: 3,
name: 'Voice Actor 3',
description: 'Have a role-playing conversation with AI',
description: t('description'),
avatarUrl: 'https://i.pravatar.cc/150?img=3',
gender: 'male',
},
{
id: 4,
name: 'Voice Actor 4',
description: 'Have a role-playing conversation with AI',
description: t('description'),
avatarUrl: 'https://i.pravatar.cc/150?img=4',
gender: 'female',
},
{
id: 5,
name: 'Voice Actor 5',
description: 'Have a role-playing conversation with AI',
description: t('description'),
avatarUrl: 'https://i.pravatar.cc/150?img=5',
gender: 'male',
},
{
id: 6,
name: 'Voice Actor 6',
description: 'Have a role-playing conversation with AI',
description: t('description'),
avatarUrl: 'https://i.pravatar.cc/150?img=6',
gender: 'female',
},
{
id: 7,
name: 'Voice Actor 7',
description: 'Have a role-playing conversation with AI',
description: t('description'),
avatarUrl: 'https://i.pravatar.cc/150?img=7',
gender: 'male',
},
@ -86,9 +88,9 @@ export default function VoiceActor() {
{/* Gender Tabs */}
<div className="mb-6 flex gap-6">
{[
{ value: 'all' as const, label: 'All' },
{ value: 'male' as const, label: 'Male' },
{ value: 'female' as const, label: 'Female' },
{ value: 'all' as const, label: t('all') },
{ value: 'male' as const, label: t('male') },
{ value: 'female' as const, label: t('female') },
].map((tab) => (
<button
key={tab.value}

View File

@ -21,6 +21,7 @@ import {
import { useStreamChatStore } from '../stream-chat';
import IconFont from '@/components/ui/iconFont';
import MaskCreate from './MaskCreate';
import { useTranslations } from 'next-intl';
type SettingProps = {
open: boolean;
@ -37,17 +38,6 @@ export type ActiveTabType =
| 'background'
| 'model';
const titleMap = {
mask: 'Masked Identity Mode',
mask_create: 'Create Mask',
history: 'History',
voice_actor: 'Voice Actor',
font: 'Font',
max_token: 'Max Token',
background: 'Background',
model: 'Chat Model',
};
const backMap = {
mask: 'profile',
mask_create: 'mask',
@ -60,9 +50,21 @@ const backMap = {
} as const;
export default function SettingDialog({ open, onOpenChange }: SettingProps) {
const t = useTranslations('chat.drawer');
const [activeTab, setActiveTab] = useState<ActiveTabType>('profile');
const updateUserChatSetting = useStreamChatStore((store) => store.updateUserChatSetting);
const titleMap = {
mask: t('maskedIdentityMode'),
mask_create: t('createMask'),
history: t('history'),
voice_actor: t('voiceActorTitle'),
font: t('fontTitle'),
max_token: t('maxToken'),
background: t('background'),
model: t('model'),
};
const handleChange = (open: boolean) => {
if (!open) {
updateUserChatSetting();

View File

@ -5,9 +5,11 @@ import { IconButton } from '@/components/ui/button';
import Link from 'next/link';
import React from 'react';
import { useLayoutStore } from '@/stores';
import { useTranslations } from 'next-intl';
const Header = React.memo(() => {
const response = useLayoutStore((s) => s.response);
const t = useTranslations('home');
return (
// <Link href="/crushcoin">
@ -26,7 +28,7 @@ const Header = React.memo(() => {
/>
<div>
<div className="flex gap-5 txt-display-m sm:txt-display-l">
Check-in{' '}
{t('check_in')}{' '}
<Image
src="/images/home/left-star.png"
className="h-6 w-6 sm:h-12 sm:w-12 object-cover"
@ -46,7 +48,7 @@ const Header = React.memo(() => {
backgroundClip: 'text',
}}
>
Daily Free crush coinsh
{t('check_in_desc')}
</span>
<IconButton iconfont="icon-arrow-right-border" size="small" variant="primary" />
</div>

View File

@ -4,8 +4,11 @@ import { useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { cn } from '@/lib/utils';
import { useTranslations } from 'next-intl';
import IconFont from '@/components/ui/iconFont';
const HomePageFooter = () => {
const t = useTranslations('footer');
const [isExpanded, setIsExpanded] = useState(false);
return (
@ -30,73 +33,70 @@ const HomePageFooter = () => {
{/* Logo 和 Slogan */}
<div className="col-span-1">
<div className="mb-4 flex items-center gap-2">
<Image src="/logo.svg" alt="logo" width={160} height={50} />
<IconFont type="icon-Logo" size={160} />
</div>
<p className="txt-body-m text-txt-secondary-normal">
Grow your love story with CrushLevel AIFrom 'Hi' to 'I Do', sparked by every
chat
</p>
<p className="txt-body-m text-txt-secondary-normal">{t('slogan')}</p>
</div>
{/* Features */}
<div>
<h3 className="txt-title-s mb-4">Features</h3>
<h3 className="txt-title-s mb-4">{t('features')}</h3>
<div className="space-y-3">
<Link
href="/wallet"
className="txt-label-m text-txt-secondary-normal hover:text-txt-primary-normal block transition-colors"
>
Recharge
{t('recharge')}
</Link>
<Link
href="/vip"
className="txt-label-m text-txt-secondary-normal hover:text-txt-primary-normal block transition-colors"
>
CrushLevel VIP
{t('crushLevelVip')}
</Link>
</div>
</div>
{/* Explore */}
<div>
<h3 className="txt-title-s mb-4">Explore</h3>
<h3 className="txt-title-s mb-4">{t('explore')}</h3>
<div className="space-y-3">
<Link
href="/crushcoin"
className="txt-label-m text-txt-secondary-normal hover:text-txt-primary-normal block transition-colors"
>
Daily Free CrushCoins
{t('dailyFreeCrushCoins')}
</Link>
<Link
href="/about"
className="txt-label-m text-txt-secondary-normal hover:text-txt-primary-normal block transition-colors"
>
About Us
{t('aboutUs')}
</Link>
<a
href="mailto:support@crushlevel.ai"
className="txt-label-m text-txt-secondary-normal hover:text-txt-primary-normal block transition-colors"
>
Contact Us
{t('contactUs')}
</a>
</div>
</div>
{/* Legal */}
<div>
<h3 className="txt-title-s mb-4">Legal</h3>
<h3 className="txt-title-s mb-4">{t('legal')}</h3>
<div className="space-y-3">
<Link
href="/policy/tos"
className="txt-label-m text-txt-secondary-normal hover:text-txt-primary-normal block transition-colors"
>
User Agreement
{t('userAgreement')}
</Link>
<Link
href="/policy/privacy"
className="txt-label-m text-txt-secondary-normal hover:text-txt-primary-normal block transition-colors"
>
Privacy Policy
{t('privacyPolicy')}
</Link>
</div>
</div>
@ -109,7 +109,7 @@ const HomePageFooter = () => {
<div className="px-16 py-4">
<div className="mx-auto flex max-w-[1136px] items-center justify-center">
<div className="txt-body-s text-txt-tertiary-normal flex items-center gap-2">
<span>Copyright © 2025 Crushlevel. All rights reserved</span>
<span>{t('copyright', { year: new Date().getFullYear() })}</span>
<Image
className={cn(isExpanded ? 'rotate-180' : '')}
src="/images/home/icon-arrow-bottom.svg"

View File

@ -19,8 +19,10 @@ import { ThirdType } from '@/services/auth';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'sonner';
import { useTranslations } from 'next-intl';
const AccountPage = () => {
const t = useTranslations('profile.account_page');
const router = useRouter();
const { data: user } = useCurrentUser();
const [isDisabling, setIsDisabling] = useState(false);
@ -30,7 +32,7 @@ const AccountPage = () => {
setIsDisabling(true);
try {
await deleteUser();
toast.success('Account disabled successfully');
toast.success(t('accountDisabledSuccess'));
} catch (error) {
console.error('禁用账户失败:', error);
} finally {
@ -53,7 +55,7 @@ const AccountPage = () => {
};
return (
<ProfileLayout title="Account">
<ProfileLayout title={t('account')}>
{/* 账户信息容器 */}
<div className="bg-surface-base-normal rounded-2xl p-6">
<div className="space-y-6">
@ -78,24 +80,22 @@ const AccountPage = () => {
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="large" loading={isDisabling}>
Disable the Account
{t('disableAccount')}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Disable the Account</AlertDialogTitle>
<AlertDialogTitle>{t('disableAccount')}</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogDescription>
Are you sure you want to disable your account?
</AlertDialogDescription>
<AlertDialogDescription>{t('disableAccountConfirm')}</AlertDialogDescription>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
loading={isDisabling}
onClick={handleDisableAccount}
>
Disable
{t('disable')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@ -1,91 +1,96 @@
'use client'
'use client';
import { useState, useCallback } from 'react'
import { Button, IconButton } from '@/components/ui/button'
import { AvatarCropModal } from '@/components/ui/avatar-crop-modal'
import Image from 'next/image'
import { cn } from '@/lib/utils'
import { toast } from 'sonner'
import { useState, useCallback } from 'react';
import { Button, IconButton } from '@/components/ui/button';
import { AvatarCropModal } from '@/components/ui/avatar-crop-modal';
import Image from 'next/image';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import { useTranslations } from 'next-intl';
interface AvatarSettingProps {
isOpen: boolean
onClose: () => void
currentAvatar?: string
className?: string
isOpen: boolean;
onClose: () => void;
currentAvatar?: string;
className?: string;
}
const AvatarSetting = ({ isOpen, onClose, currentAvatar, className }: AvatarSettingProps) => {
const [showCropModal, setShowCropModal] = useState(false)
const [tempImageUrl, setTempImageUrl] = useState<string>('')
const t = useTranslations('profile.avatarSetting');
const [showCropModal, setShowCropModal] = useState(false);
const [tempImageUrl, setTempImageUrl] = useState<string>('');
// 处理文件上传
const handleFileUpload = useCallback((files: File[]) => {
if (files.length > 0) {
const file = files[0]
const handleFileUpload = useCallback(
(files: File[]) => {
if (files.length > 0) {
const file = files[0];
// 验证文件类型
if (!file.type.startsWith('image/')) {
toast.error('Please select an image file')
return
// 验证文件类型
if (!file.type.startsWith('image/')) {
toast.error(t('selectImageFile'));
return;
}
// 排除 GIF 格式
if (file.type === 'image/gif') {
toast.error(t('gifNotSupported'));
return;
}
// 验证文件大小 (5MB)
if (file.size > 10 * 1024 * 1024) {
toast.error(t('imageSizeLimit'));
return;
}
// 创建预览URL
const url = URL.createObjectURL(file);
setTempImageUrl(url);
setShowCropModal(true);
}
// 排除 GIF 格式
if (file.type === 'image/gif') {
toast.error('GIF format is not supported, please select JPG, JPEG or PNG format images')
return
}
// 验证文件大小 (5MB)
if (file.size > 10 * 1024 * 1024) {
toast.error('Image files cannot exceed 10MB.')
return
}
// 创建预览URL
const url = URL.createObjectURL(file)
setTempImageUrl(url)
setShowCropModal(true)
}
}, [])
},
[t]
);
// 处理文件选择
const handleFileSelect = useCallback(() => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/jpeg,image/jpg,image/png'
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/jpeg,image/jpg,image/png';
input.onchange = (e) => {
const target = e.target as HTMLInputElement
const target = e.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
handleFileUpload(Array.from(target.files))
handleFileUpload(Array.from(target.files));
}
}
};
input.click()
}, [handleFileUpload])
input.click();
}, [handleFileUpload]);
// 处理裁剪确认
const handleCropConfirm = useCallback(() => {
setShowCropModal(false)
setTempImageUrl('')
onClose()
}, [onClose])
setShowCropModal(false);
setTempImageUrl('');
onClose();
}, [onClose]);
// 处理裁剪取消
const handleCropCancel = useCallback(() => {
setShowCropModal(false)
setShowCropModal(false);
if (tempImageUrl && tempImageUrl !== currentAvatar) {
URL.revokeObjectURL(tempImageUrl)
URL.revokeObjectURL(tempImageUrl);
}
setTempImageUrl('')
}, [tempImageUrl, currentAvatar])
setTempImageUrl('');
}, [tempImageUrl, currentAvatar]);
// 处理关闭按钮点击
const handleClose = useCallback(() => {
onClose()
}, [onClose])
onClose();
}, [onClose]);
if (!isOpen) return null
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-40 flex items-center justify-center">
@ -121,13 +126,13 @@ const AvatarSetting = ({ isOpen, onClose, currentAvatar, className }: AvatarSett
{/* 文件格式提示 */}
<div className="txt-body-l mt-8 text-center">
<p>Please upload a JPG, JPEG, or PNG image under 10MB.</p>
<p>{t('uploadPrompt')}</p>
</div>
{/* 底部操作按钮 */}
<div className="mt-8">
{/* 上传按钮 */}
<Button onClick={handleFileSelect}>Upload</Button>
<Button onClick={handleFileSelect}>{t('upload')}</Button>
</div>
</div>
</div>
@ -145,7 +150,7 @@ const AvatarSetting = ({ isOpen, onClose, currentAvatar, className }: AvatarSett
/>
)}
</div>
)
}
);
};
export default AvatarSetting
export default AvatarSetting;

View File

@ -15,6 +15,7 @@ import { useLayoutStore } from '@/stores';
import { useStreamChatStore } from '../../chat/[id]/stream-chat';
import { useAsyncFn } from '@/hooks/tools';
import IconFont from '@/components/ui/iconFont';
import { useTranslations } from 'next-intl';
const ProfileDropdownItem = ({
icon,
@ -40,6 +41,8 @@ const ProfileDropdownItem = ({
};
const ProfileDropdown = () => {
const t = useTranslations('profile');
const tCommon = useTranslations('common');
const { mutateAsync: logout } = useLogout();
const [isLogoutDialogOpen, setIsLogoutDialogOpen] = useState(false);
const setSidebarExpanded = useLayoutStore((s) => s.setSidebarExpanded);
@ -70,45 +73,45 @@ const ProfileDropdown = () => {
> = [
{
type: 'item',
label: 'Edit Profile',
label: t('editProfile'),
icon: 'icon-icon_order_remark',
href: '/profile/edit',
},
{
type: 'item',
label: 'Masked Identity Mode',
label: t('maskedIdentityMode'),
icon: <IconFont type="icon-shezhi" />,
href: '/profile/mask',
},
{
type: 'item',
label: 'Account',
label: t('account'),
icon: 'icon-card',
href: '/profile/account',
},
{ type: 'separator' },
{
type: 'item',
label: 'About Us',
label: t('aboutUs'),
icon: 'icon-info',
href: '/about',
},
{
type: 'item',
label: 'Terms of Services',
label: t('termsOfServices'),
icon: 'icon-audits',
href: '/policy/tos',
},
{
type: 'item',
label: 'Privacy Policy',
label: t('privacyPolicy'),
icon: 'icon-shield',
href: '/policy/privacy',
},
{ type: 'separator' },
{
type: 'item',
label: 'Log out',
label: t('logOut'),
icon: 'icon-icon_exit',
onClick: () => setIsLogoutDialogOpen(true),
},
@ -147,13 +150,13 @@ const ProfileDropdown = () => {
<AlertDialog open={isLogoutDialogOpen} onOpenChange={setIsLogoutDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Log Out</AlertDialogTitle>
<AlertDialogTitle>{t('logOut')}</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogDescription>Are you sure you want to log out?</AlertDialogDescription>
<AlertDialogDescription>{t('logOutConfirm')}</AlertDialogDescription>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogCancel>{tCommon('cancel')}</AlertDialogCancel>
<AlertDialogAction variant="destructive" loading={loading} onClick={handleLogout}>
Log out
{t('logOut')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@ -1,17 +1,19 @@
import Image from 'next/image';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
const ProfileFeatureList = () => {
const t = useTranslations('profile');
const items = [
{
icon: '/images/profile/membership.png',
label: 'VIP',
desc: <span className="txt-body-s text-white/60">Not Unlocked</span>,
label: t('vip'),
desc: <span className="txt-body-s text-white/60">{t('notUnlocked')}</span>,
href: '/vip',
},
{
icon: '/images/profile/wallet.png',
label: 'Wallet',
label: t('wallet'),
desc: (
<div className="flex gap-2">
<Image src="/icons/diamond.svg" alt="Diamond" width={16} height={16} />

View File

@ -28,36 +28,35 @@ import * as z from 'zod';
import { calculateAge } from '@/lib/utils';
import dayjs from 'dayjs';
import ProfileLayout from '@/layout/ProfileLayout';
const schema = z
.object({
nickname: z
.string()
.trim()
.min(1, 'Nickname is required')
.min(2, 'Nickname must be between 2 and 20 characters'),
gender: z.enum(Gender, { message: 'Please select a gender' }),
year: z.string().min(1, 'Please select birth year'),
month: z.string().min(1, 'Please select birth month'),
day: z.string().min(1, 'Please select birth day'),
})
.refine(
(data) => {
const age = calculateAge(data.year, data.month, data.day);
return age >= 18;
},
{
message: 'Character age must be at least 18 years old',
path: ['year'],
}
);
type EditProfileFormData = z.infer<typeof schema>;
import { useTranslations } from 'next-intl';
const EditPage = () => {
const t = useTranslations('profile.edit');
const router = useRouter();
const { data: user, isLoading } = useCurrentUser();
const { mutateAsync: updateUser } = useUpdateUser();
const schema = z
.object({
nickname: z.string().trim().min(1, t('nicknameRequired')).min(2, t('nicknameLength')),
gender: z.enum(Gender, { message: t('selectGender') }),
year: z.string().min(1, t('selectBirthYear')),
month: z.string().min(1, t('selectBirthMonth')),
day: z.string().min(1, t('selectBirthDay')),
})
.refine(
(data) => {
const age = calculateAge(data.year, data.month, data.day);
return age >= 18;
},
{
message: t('ageLimit'),
path: ['year'],
}
);
type EditProfileFormData = z.infer<typeof schema>;
const { mutateAsync: checkNickname } = useCheckNickname({
onError: (error) => {
form.setError('nickname', {
@ -119,7 +118,7 @@ const EditPage = () => {
});
if (isExist) {
form.setError('nickname', {
message: 'This nickname is already taken',
message: t('nicknameTaken'),
});
return;
}
@ -197,7 +196,7 @@ const EditPage = () => {
}, [selectedYear, selectedMonth, selectedDay, form]);
return (
<ProfileLayout title="Edit Profile">
<ProfileLayout title={t('editProfile')}>
{/* 表单容器 */}
<div className="bg-surface-base-normal rounded-2xl p-6">
<Form {...form}>
@ -208,10 +207,12 @@ const EditPage = () => {
name="nickname"
render={({ field }) => (
<FormItem>
<FormLabel className="txt-label-m text-txt-primary-normal">Nickname</FormLabel>
<FormLabel className="txt-label-m text-txt-primary-normal">
{t('nickname')}
</FormLabel>
<FormControl>
<Input
placeholder="Enter nickname"
placeholder={t('enterNickname')}
maxLength={20}
showCount
error={!!form.formState.errors.nickname}
@ -230,7 +231,9 @@ const EditPage = () => {
disabled={true}
render={({ field }) => (
<FormItem>
<FormLabel className="txt-label-m text-txt-primary-normal">Gender</FormLabel>
<FormLabel className="txt-label-m text-txt-primary-normal">
{t('gender')}
</FormLabel>
<FormControl>
<GenderInput
value={field.value as Gender}
@ -238,9 +241,7 @@ const EditPage = () => {
disabled={true}
/>
</FormControl>
<p className="txt-body-s text-txt-secondary-normal">
Please note: gender cannot be changed after setting
</p>
<p className="txt-body-s text-txt-secondary-normal">{t('genderNote')}</p>
<FormMessage />
</FormItem>
)}
@ -248,7 +249,7 @@ const EditPage = () => {
{/* 年龄字段 */}
<FormItem>
<FormLabel className="txt-label-m text-txt-primary-normal">Age</FormLabel>
<FormLabel className="txt-label-m text-txt-primary-normal">{t('age')}</FormLabel>
<div className="grid grid-cols-3 gap-2">
<FormField
control={form.control}
@ -257,7 +258,7 @@ const EditPage = () => {
<FormControl>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger block error={!!form.formState.errors.year}>
<SelectValue placeholder="Year" />
<SelectValue placeholder={t('year')} />
</SelectTrigger>
<SelectContent>
{years.map((year) => (
@ -278,7 +279,7 @@ const EditPage = () => {
<FormControl>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger block error={!!form.formState.errors.year}>
<SelectValue placeholder="Month" />
<SelectValue placeholder={t('month')} />
</SelectTrigger>
<SelectContent>
{months.map((month) => (
@ -299,7 +300,7 @@ const EditPage = () => {
<FormControl>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger block error={!!form.formState.errors.year}>
<SelectValue placeholder="Day" />
<SelectValue placeholder={t('day')} />
</SelectTrigger>
<SelectContent>
{days.map((day) => (
@ -323,7 +324,7 @@ const EditPage = () => {
{/* 保存按钮 */}
<div className="flex justify-end">
<Button type="submit" size="large" disabled={!isValid || !isDirty} loading={loading}>
Save
{t('save')}
</Button>
</div>
</form>

View File

@ -18,24 +18,23 @@ import GenderInput from '@/components/features/genderInput';
import { Button } from '@/components/ui/button';
import { InputNumber } from '@/components/ui/inputnumber';
import { Textarea } from '@/components/ui/textarea';
const schema = z.object({
nickname: z
.string()
.trim()
.min(1, 'Nickname is required')
.min(2, 'Nickname must be between 2 and 20 characters'),
gender: z.enum(Gender, { message: 'Please select a gender' }),
age: z.number().min(1, 'Age is required'),
whoAmI: z.string(),
});
import { useTranslations } from 'next-intl';
type MaskFormProps = {
onSubmitSuccess?: (data: any) => void;
};
export const MaskForm = ({ onSubmitSuccess }: MaskFormProps) => {
const t = useTranslations('profile.mask');
const { data: user } = useCurrentUser();
const schema = z.object({
nickname: z.string().trim().min(1, t('nicknameRequired')).min(2, t('nicknameLength')),
gender: z.enum(Gender, { message: t('selectGender') }),
age: z.number().min(1, t('ageRequired')),
whoAmI: z.string(),
});
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
@ -65,10 +64,12 @@ export const MaskForm = ({ onSubmitSuccess }: MaskFormProps) => {
name="nickname"
render={({ field }) => (
<FormItem>
<FormLabel className="txt-label-m text-txt-primary-normal">Nickname</FormLabel>
<FormLabel className="txt-label-m text-txt-primary-normal">
{t('nickname')}
</FormLabel>
<FormControl>
<Input
placeholder="Enter nickname"
placeholder={t('enterNickname')}
maxLength={100}
showCount
error={!!form.formState.errors.nickname}
@ -85,13 +86,11 @@ export const MaskForm = ({ onSubmitSuccess }: MaskFormProps) => {
name="gender"
render={({ field }) => (
<FormItem>
<FormLabel className="txt-label-m text-txt-primary-normal">Gender</FormLabel>
<FormLabel className="txt-label-m text-txt-primary-normal">{t('gender')}</FormLabel>
<FormControl>
<GenderInput value={field.value as Gender} onChange={field.onChange} />
</FormControl>
<p className="txt-body-s text-txt-secondary-normal">
Please note: gender cannot be changed after setting
</p>
<p className="txt-body-s text-txt-secondary-normal">{t('genderNote')}</p>
<FormMessage />
</FormItem>
)}
@ -102,11 +101,11 @@ export const MaskForm = ({ onSubmitSuccess }: MaskFormProps) => {
name="age"
render={({ field }) => (
<FormItem>
<FormLabel className="txt-label-m text-txt-primary-normal">Age</FormLabel>
<FormLabel className="txt-label-m text-txt-primary-normal">{t('age')}</FormLabel>
<FormControl>
<InputNumber
min={18}
placeholder="Enter age"
placeholder={t('enterAge')}
error={!!form.formState.errors.age}
{...field}
/>
@ -122,12 +121,10 @@ export const MaskForm = ({ onSubmitSuccess }: MaskFormProps) => {
name="whoAmI"
render={({ field }) => (
<FormItem>
<FormLabel className="txt-label-m text-txt-primary-normal">
Who am Ioptional
</FormLabel>
<FormLabel className="txt-label-m text-txt-primary-normal">{t('whoAmI')}</FormLabel>
<FormControl>
<Textarea
placeholder="Describe the character characteristics and scene settings of your role"
placeholder={t('whoAmIPlaceholder')}
error={!!form.formState.errors.whoAmI}
{...field}
/>
@ -145,7 +142,7 @@ export const MaskForm = ({ onSubmitSuccess }: MaskFormProps) => {
disabled={!isValid || !isDirty}
loading={loading}
>
Save
{t('save')}
</Button>
</div>
</form>

View File

@ -5,6 +5,8 @@ import { useCurrentUser } from '@/hooks/auth';
import { useRouter } from 'next/navigation';
import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import { useTranslations } from 'next-intl';
type MaskListProps = {
selectAble?: boolean;
value?: null;
@ -13,6 +15,7 @@ type MaskListProps = {
};
export default function MaskList(props: MaskListProps) {
const t = useTranslations('profile.mask');
const { selectAble, value, onChange, onAdd } = props;
const { data: user } = useCurrentUser();
const router = useRouter();
@ -51,7 +54,7 @@ export default function MaskList(props: MaskListProps) {
className="flex bg-white/10 px-4 py-3 items-center justify-between"
>
<span>
{mask.nickname}{mask.gender === Gender.MALE ? 'Male' : 'Female'}{mask.age}
{mask.nickname}{mask.gender === Gender.MALE ? t('male') : t('female')}{mask.age}
</span>
{iconRender(mask)}
</div>
@ -61,7 +64,7 @@ export default function MaskList(props: MaskListProps) {
{onAdd && (
<div className="pt-2">
<Button variant="primary" className="w-full" onClick={onAdd}>
+ Add new Mask
{t('addNewMask')}
</Button>
</div>
)}

View File

@ -4,13 +4,15 @@ import { MaskForm } from '../MaskForm';
import { IconButton } from '@/components/ui/button';
import { useRouter } from 'next/navigation';
import ProfileLayout from '@/layout/ProfileLayout';
import { useTranslations } from 'next-intl';
export default function MaskPage() {
const t = useTranslations('common');
const { data: user } = useCurrentUser();
const router = useRouter();
return (
<ProfileLayout title="Edit">
<ProfileLayout title={t('edit')}>
<div className="bg-surface-base-normal rounded-2xl p-6">
<MaskForm onSubmitSuccess={() => router.push(`/profile/mask`)} />
</div>

View File

@ -6,8 +6,10 @@ import Link from 'next/link';
import IconFont from '@/components/ui/iconFont';
import { useLayoutStore } from '@/stores';
import MaskList from './MaskList';
import { useTranslations } from 'next-intl';
export default function MaskPage() {
const t = useTranslations('profile.mask');
const response = useLayoutStore((s) => s.response);
const router = useRouter();
@ -17,7 +19,7 @@ export default function MaskPage() {
bottomButton={
<Link href={'/profile/mask/new'} className="w-full">
<Button className="w-full" variant="primary">
+ Add new Mask
{t('addNewMask')}
</Button>
</Link>
}
@ -31,7 +33,7 @@ export default function MaskPage() {
/>
)
}
title="Masked Identity Mode"
title={t('maskedIdentityMode')}
>
<div className="flex flex-col gap-6 bg-surface-base-normal p-6 rounded-2xl">
<MaskList />

View File

@ -10,6 +10,7 @@ import ProfileDropdown from './components/ProfileDropdown';
import AvatarSetting from './components/AvatarSetting';
// import CharacterList from './components/CharacterList'
import Image from 'next/image';
import { useTranslations } from 'next-intl';
const genderMap = {
0: '/icons/male.svg',
@ -18,6 +19,7 @@ const genderMap = {
};
export default function ProfilePage() {
const t = useTranslations('profile');
const { data: user } = useCurrentUser();
const [isAvatarSettingOpen, setIsAvatarSettingOpen] = useState(false);
@ -70,7 +72,7 @@ export default function ProfilePage() {
variant="ghost"
onClick={() => {
navigator.clipboard.writeText(user?.idCard?.toString() || '');
toast.success('Copied to clipboard');
toast.success(t('copiedToClipboard'));
}}
>
<i className="iconfont icon-copy text-txt-secondary-normal"></i>

View File

@ -1,22 +1,22 @@
'use client'
'use client';
import * as React from 'react'
import * as SelectPrimitive from '@radix-ui/react-select'
import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
import { cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'
import Image from 'next/image'
import * as React from 'react';
import * as SelectPrimitive from '@radix-ui/react-select';
import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import Image from 'next/image';
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
const selectTriggerVariants = cva(
@ -40,7 +40,7 @@ const selectTriggerVariants = cva(
error: false,
},
}
)
);
function SelectTrigger({
className,
@ -50,9 +50,9 @@ function SelectTrigger({
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: 'default'
error?: boolean
block?: boolean
size?: 'default';
error?: boolean;
block?: boolean;
}) {
return (
<SelectPrimitive.Trigger
@ -77,7 +77,7 @@ function SelectTrigger({
</div>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
);
}
function SelectContent({
@ -85,7 +85,7 @@ function SelectContent({
children,
position = 'popper',
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
}: React.ComponentProps<typeof SelectPrimitive.Content> & {}) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
@ -114,7 +114,7 @@ function SelectContent({
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
);
}
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
@ -124,7 +124,7 @@ function SelectLabel({ className, ...props }: React.ComponentProps<typeof Select
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
)
);
}
function SelectItem({
@ -152,7 +152,7 @@ function SelectItem({
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
);
}
function SelectSeparator({
@ -165,7 +165,7 @@ function SelectSeparator({
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...props}
/>
)
);
}
function SelectScrollUpButton({
@ -180,7 +180,7 @@ function SelectScrollUpButton({
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
);
}
function SelectScrollDownButton({
@ -195,7 +195,7 @@ function SelectScrollDownButton({
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
);
}
export {
@ -209,4 +209,4 @@ export {
SelectSeparator,
SelectTrigger,
SelectValue,
}
};

View File

@ -5,28 +5,29 @@ import Link from 'next/link';
import Image from 'next/image';
import { cn } from '@/lib/utils';
import { useMedia } from '@/hooks/tools';
import { useTranslations } from 'next-intl';
export const items = [
{
label: 'Explore',
labelKey: 'explore',
path: '/home',
icon: '/images/layout/explore.svg',
selectedIcon: '/images/layout/explore_active.svg',
},
{
label: 'Search',
labelKey: 'search',
path: '/search',
icon: '/images/layout/search.svg',
selectedIcon: '/images/layout/search_active.svg',
},
{
label: 'Chat',
labelKey: 'chat',
path: '/chat-history',
icon: '/images/layout/chat.svg',
selectedIcon: '/images/layout/chat_active.svg',
},
{
label: 'Me',
labelKey: 'me',
path: '/profile',
icon: '/images/layout/me.svg',
selectedIcon: '/images/layout/me_active.svg',
@ -34,6 +35,7 @@ export const items = [
];
export default function BottomBar() {
const t = useTranslations('bottomBar');
const pathname = usePathname();
const response = useMedia({ hide: 500 });
@ -45,6 +47,7 @@ export default function BottomBar() {
<div className="h-20 border-outline-normal bg-[rgba(10,14,43,1)] z-100 flex border-t items-center justify-between">
{items.map((item) => {
const isSelected = pathname === item.path;
const label = t(item.labelKey as 'explore' | 'search' | 'chat' | 'me');
return (
<Link
className={cn(
@ -56,11 +59,11 @@ export default function BottomBar() {
>
<Image
src={isSelected ? item.selectedIcon : item.icon}
alt={item.label}
alt={label}
width={48}
height={48}
/>
{response?.hide && <span>{item.label}</span>}
{response?.hide && <span>{label}</span>}
</Link>
);
})}

View File

@ -8,6 +8,7 @@ import ChatSidebar from './components/ChatSidebar';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useLayoutStore } from '@/stores';
import { useTranslations } from 'next-intl';
// 菜单项接口
interface IMenuItem {
@ -22,6 +23,7 @@ interface IMenuItem {
// 主侧边栏组件
function Sidebar() {
const pathname = usePathname();
const t = useTranslations('home');
const isSidebarExpanded = useLayoutStore((s) => s.isSidebarExpanded);
const setSidebarExpanded = useLayoutStore((s) => s.setSidebarExpanded);
@ -30,7 +32,7 @@ function Sidebar() {
id: MenuItem.FOR_YOU,
icon: '/icons/explore.svg',
selectedIcon: '/icons/explore_selected.svg',
label: 'Home',
label: t('home'),
link: '/home',
isSelected: pathname === '/home',
},

View File

@ -77,41 +77,42 @@ function Topbar() {
const rightDomRender = () => {
if (!response || isLoading) return null;
if (!user)
return (
<Link href={loginHref} prefetch>
<Button size="small">Login in</Button>
</Link>
);
const items = [
{
dom: <LocaleSwitch key="locale-switch" />,
},
{
hide: !user,
dom: <Notice key="notice" />,
},
{
hide: !user,
dom: (
<Link href="/profile" prefetch key="profile">
<Avatar className="size-8 cursor-pointer">
<AvatarImage
className="object-cover"
src={user!.headImage}
alt={user!.nickname}
width={32}
height={32}
/>
<AvatarFallback>{user!.nickname?.slice(0, 1)}</AvatarFallback>
</Avatar>
</Link>
),
},
{
hide: user,
dom: (
<Link href={loginHref} prefetch key="login">
<Button size="small">Login in</Button>
</Link>
),
},
].filter((item) => !item.hide);
const userDom = user ? (
<>
<Notice />
<Link href="/profile" prefetch>
<Avatar className="size-8 cursor-pointer">
<AvatarImage
className="object-cover"
src={user.headImage}
alt={user.nickname}
width={32}
height={32}
/>
<AvatarFallback>{user.nickname?.slice(0, 1)}</AvatarFallback>
</Avatar>
</Link>
</>
) : (
<Link href={loginHref} prefetch>
<Button size="small">Login in</Button>
</Link>
);
return (
<div className="flex items-center">
<LocaleSwitch />
{userDom}
</div>
);
return <div className="flex items-center">{items.map((item) => item.dom)}</div>;
};
// 移动端根据配置决定是否隐藏 Topbar

View File

@ -7,6 +7,7 @@ import { useState, useEffect, useCallback } from 'react';
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
import { useLayoutStore } from '@/stores';
import { useParams } from 'next/navigation';
import { useTranslations } from 'next-intl';
const ChatSidebar = ({
expand,
@ -17,6 +18,7 @@ const ChatSidebar = ({
}) => {
const isSidebarExpanded = useLayoutStore((s) => s.isSidebarExpanded);
const { id } = useParams<{ id: string }>();
const t = useTranslations('chat');
const channels = useStreamChatStore((state) => state.channels);
const [search, setSearch] = useState('');
const [inSearching, setIsSearching] = useState(false);
@ -54,7 +56,7 @@ const ChatSidebar = ({
<div className="flex min-h-0 flex-1 flex-col px-4">
{/* 聊天标题 */}
<div className="mb-2 flex h-10 items-center justify-between px-2 py-1">
<span className="txt-label-s text-txt-secondary-normal">Chats</span>
<span className="txt-label-s text-txt-secondary-normal">{t('chats')}</span>
{finalExpand && (
<ChatSidebarAction
onSearchClick={() => setIsSearching(true)}

View File

@ -20,6 +20,7 @@ import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useStreamChatStore } from '@/app/(main)/chat/[id]/stream-chat';
import { useAsyncFn } from '@/hooks/tools';
import { useTranslations } from 'next-intl';
interface ChatSidebarActionProps {
onSearchClick?: () => void;
@ -37,7 +38,8 @@ const ChatSidebarAction = ({
const channels = useStreamChatStore((state) => state.channels);
const [isDeleteMessageDialogOpen, setIsDeleteMessageDialogOpen] = useState(false);
const router = useRouter();
const t = useTranslations('home');
const ct = useTranslations('common');
const { run: handleClearChannels, loading } = useAsyncFn(async () => {
const { result } = await clearChannels(channels.map((ch) => ch.id!));
if (result === 'ok') {
@ -57,18 +59,18 @@ const ChatSidebarAction = ({
<DropdownMenuContent className="w-56" align="end">
<DropdownMenuItem onClick={clearNotifications}>
<i className="iconfont icon-clear" />
<span>Mark All</span>
<span>{t('mark_all')}</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={isSearchActive ? onCancelSearch : onSearchClick}>
<i className={`iconfont icon-Search`} />
<span>{isSearchActive ? 'Cancel' : 'Search'}</span>
<span>{isSearchActive ? ct('cancel') : ct('search')}</span>
</DropdownMenuItem>
<div className="my-3 px-2">
<Separator className="bg-outline-normal" />
</div>
<DropdownMenuItem onClick={() => setIsDeleteMessageDialogOpen(true)}>
<i className="iconfont icon-trashcan" />
<span>Clear Chat List</span>
<span>{t('clear')}</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@ -14,16 +14,12 @@ export default function LocaleSwitch() {
return (
<Select value={locale} onValueChange={(value) => setLocale(value as 'zh' | 'en')}>
<SelectTrigger>
<SelectValue placeholder="选择语言" />
<SelectTrigger className="rounded-full h-8 w-16 px-3">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="zh">
<span></span>
</SelectItem>
<SelectItem value="en">
<span>English</span>
</SelectItem>
<SelectContent className="min-w-0 w-22">
<SelectItem value="zh"></SelectItem>
<SelectItem value="en">En</SelectItem>
</SelectContent>
</Select>
);

View File

@ -1,10 +1,162 @@
export default {
hello: 'Hello',
common: {
search: 'Search',
cancel: 'Cancel',
edit: 'Edit',
select: 'Select',
default: 'Default',
},
bottomBar: {
explore: 'Explore',
search: 'Search',
chat: 'Chat',
me: 'Me',
},
home: {
home: 'Home',
character: 'Character',
story: 'Story',
mark_all: 'Mark all as read',
clear: 'Clear Chat List',
check_in: 'Check-in',
check_in_desc: 'Daily Free crush coinsh',
},
chat: {
chats: 'Chats',
drawer: {
maskedIdentityMode: 'Masked Identity Mode',
createMask: 'Create Mask',
history: 'History',
voiceActorTitle: 'Voice Actor',
fontTitle: 'Font',
maxToken: 'Max Token',
background: 'Background',
model: 'Chat Model',
profile: {
nickname: 'Nickname',
gender: 'Gender',
age: 'Age',
whoAmI: 'Who am I',
unfilled: 'Unfilled',
chatSetting: 'Chat Setting',
voiceSetting: 'Voice Setting',
chatModel: 'Chat Model',
longText: 'Long text',
maximumReplies: 'Maximum Replies',
font: 'Font',
chatBackground: 'Chat Background',
voiceArtist: 'Voice Artist',
delete: 'Delete',
newChat: 'New Chat',
},
chatModel: {
textMessagePrice: 'Text Message Price',
textMessagePriceDesc:
'Refers to the cost of chatting with the character via text messages, including sending text, images, or gifts. Charged per message.',
voiceMessagePrice: 'Voice Message Price',
voiceMessagePriceDesc:
"Refers to the cost of sending a voice message to the character or playing the character's voice. Charged per use.",
voiceCallPrice: 'Voice Call Price',
voiceCallPriceDesc:
'Refers to the cost of having a voice call with the character. Charged per minute.',
rolePlayDesc: 'Role-play a conversation with AI',
textMessage: 'Text Message',
sendOrPlayVoice: 'Send or play voice',
voiceCallPerMin: 'Voice call/min',
stayTuned: 'Stay tuned for more models',
},
voiceActor: {
all: 'All',
male: 'Male',
female: 'Female',
description: 'Have a role-playing conversation with AI',
},
font: {
standard: 'standard',
},
},
},
profile: {
copiedToClipboard: 'Copied to clipboard',
vip: 'VIP',
notUnlocked: 'Not Unlocked',
wallet: 'Wallet',
editProfile: 'Edit Profile',
maskedIdentityMode: 'Masked Identity Mode',
account: 'Account',
aboutUs: 'About Us',
termsOfServices: 'Terms of Services',
privacyPolicy: 'Privacy Policy',
logOut: 'Log out',
logOutConfirm: 'Are you sure you want to log out?',
avatarSetting: {
selectImageFile: 'Please select an image file',
gifNotSupported: 'GIF format is not supported, please select JPG, JPEG or PNG format images',
imageSizeLimit: 'Image files cannot exceed 10MB.',
uploadPrompt: 'Please upload a JPG, JPEG, or PNG image under 10MB.',
upload: 'Upload',
},
account_page: {
account: 'Account',
disableAccount: 'Disable the Account',
disableAccountConfirm: 'Are you sure you want to disable your account?',
cancel: 'Cancel',
disable: 'Disable',
accountDisabledSuccess: 'Account disabled successfully',
},
edit: {
editProfile: 'Edit Profile',
nickname: 'Nickname',
enterNickname: 'Enter nickname',
nicknameRequired: 'Nickname is required',
nicknameLength: 'Nickname must be between 2 and 20 characters',
gender: 'Gender',
selectGender: 'Please select a gender',
genderNote: 'Please note: gender cannot be changed after setting',
age: 'Age',
year: 'Year',
month: 'Month',
day: 'Day',
selectBirthYear: 'Please select birth year',
selectBirthMonth: 'Please select birth month',
selectBirthDay: 'Please select birth day',
ageLimit: 'Character age must be at least 18 years old',
nicknameTaken: 'This nickname is already taken',
save: 'Save',
},
mask: {
maskedIdentityMode: 'Masked Identity Mode',
addNewMask: '+ Add new Mask',
nickname: 'Nickname',
enterNickname: 'Enter nickname',
nicknameRequired: 'Nickname is required',
nicknameLength: 'Nickname must be between 2 and 20 characters',
gender: 'Gender',
selectGender: 'Please select a gender',
genderNote: 'Please note: gender cannot be changed after setting',
age: 'Age',
enterAge: 'Enter age',
ageRequired: 'Age is required',
whoAmI: 'Who am I (optional)',
whoAmIPlaceholder: 'Describe the character characteristics and scene settings of your role',
save: 'Save',
male: 'Male',
female: 'Female',
},
},
footer: {
slogan: "Grow your love story with Spicyxx.AI AI—From 'Hi' to 'I Do', sparked by every chat",
features: 'Features',
recharge: 'Recharge',
crushLevelVip: 'Spicyxx.AI VIP',
explore: 'Explore',
dailyFreeCrushCoins: 'Daily Free CrushCoins',
aboutUs: 'About Us',
contactUs: 'Contact Us',
legal: 'Legal',
userAgreement: 'User Agreement',
privacyPolicy: 'Privacy Policy',
copyright: 'Copyright © {year} Spicyxx.AI. All rights reserved',
},
};

View File

@ -1,10 +1,160 @@
export default {
hello: '你好',
common: {
search: '搜索',
cancel: '取消',
edit: '编辑',
select: '选择',
default: '默认',
},
bottomBar: {
explore: '首页',
search: '搜索',
chat: '聊天',
me: '我的',
},
home: {
home: '首页',
character: '角色',
story: '故事',
mark_all: '已读全部',
clear: '清空聊天记录',
check_in: '签到',
check_in_desc: '每日签到,免费获取金币',
},
chat: {
chats: '聊天',
drawer: {
maskedIdentityMode: '面具设置',
createMask: '创建面具',
history: '历史记录',
voiceActorTitle: '声优',
fontTitle: '字体',
maxToken: '最大回复',
background: '背景',
model: '聊天模型',
profile: {
nickname: '昵称',
gender: '性别',
age: '年龄',
whoAmI: '我是谁',
unfilled: '未填写',
chatSetting: '聊天设置',
voiceSetting: '语音设置',
chatModel: '聊天模型',
longText: '长文本',
maximumReplies: '最大回复数',
font: '字体',
chatBackground: '聊天背景',
voiceArtist: '声优',
delete: '删除',
newChat: '新建聊天',
},
chatModel: {
textMessagePrice: '文本消息价格',
textMessagePriceDesc:
'指通过文本消息与角色聊天的费用,包括发送文字、图片或礼物。按消息计费。',
voiceMessagePrice: '语音消息价格',
voiceMessagePriceDesc: '指向角色发送语音消息或播放角色语音的费用。按次计费。',
voiceCallPrice: '语音通话价格',
voiceCallPriceDesc: '指与角色进行语音通话的费用。按分钟计费。',
rolePlayDesc: '与 AI 进行角色扮演对话',
textMessage: '文本消息',
sendOrPlayVoice: '发送或播放语音',
voiceCallPerMin: '语音通话/分钟',
stayTuned: '敬请期待更多模型',
},
voiceActor: {
all: '全部',
male: '男性',
female: '女性',
description: '与 AI 进行角色扮演对话',
},
font: {
standard: '标准',
},
},
},
profile: {
copiedToClipboard: '已复制到剪贴板',
vip: 'VIP',
notUnlocked: '未解锁',
wallet: '钱包',
editProfile: '编辑资料',
maskedIdentityMode: '面具身份模式',
account: '账户',
aboutUs: '关于我们',
termsOfServices: '服务条款',
privacyPolicy: '隐私政策',
logOut: '退出登录',
logOutConfirm: '确定要退出登录吗?',
avatarSetting: {
selectImageFile: '请选择图片文件',
gifNotSupported: '不支持 GIF 格式,请选择 JPG、JPEG 或 PNG 格式的图片',
imageSizeLimit: '图片文件不能超过 10MB',
uploadPrompt: '请上传 10MB 以内的 JPG、JPEG 或 PNG 图片。',
upload: '上传',
},
account_page: {
account: '账户',
disableAccount: '停用账户',
disableAccountConfirm: '确定要停用您的账户吗?',
cancel: '取消',
disable: '停用',
accountDisabledSuccess: '账户已成功停用',
},
edit: {
editProfile: '编辑资料',
nickname: '昵称',
enterNickname: '输入昵称',
nicknameRequired: '昵称不能为空',
nicknameLength: '昵称长度必须在 2 到 20 个字符之间',
gender: '性别',
selectGender: '请选择性别',
genderNote: '请注意:性别设置后不可更改',
age: '年龄',
year: '年',
month: '月',
day: '日',
selectBirthYear: '请选择出生年份',
selectBirthMonth: '请选择出生月份',
selectBirthDay: '请选择出生日期',
ageLimit: '角色年龄必须至少为 18 岁',
nicknameTaken: '此昵称已被使用',
save: '保存',
},
mask: {
maskedIdentityMode: '面具身份模式',
addNewMask: '+ 添加新面具',
nickname: '昵称',
enterNickname: '输入昵称',
nicknameRequired: '昵称不能为空',
nicknameLength: '昵称长度必须在 2 到 20 个字符之间',
gender: '性别',
selectGender: '请选择性别',
genderNote: '请注意:性别设置后不可更改',
age: '年龄',
enterAge: '输入年龄',
ageRequired: '年龄不能为空',
whoAmI: '我是谁(可选)',
whoAmIPlaceholder: '描述你的角色特征和场景设定',
save: '保存',
male: '男性',
female: '女性',
},
},
footer: {
slogan: '用 Spicyxx.AI 成长你的爱情故事——从"你好"到"我愿意",每一次对话都点燃火花',
features: '功能',
recharge: '充值',
crushLevelVip: 'Spicyxx.AI VIP',
explore: '探索',
dailyFreeCrushCoins: '每日免费金币',
aboutUs: '关于我们',
contactUs: '联系我们',
legal: '法律',
userAgreement: '用户协议',
privacyPolicy: '隐私政策',
copyright: '版权所有 © {year} Spicyxx.AI. 保留所有权利',
},
};