diff --git a/dashboard/.eslintrc.js b/dashboard/.eslintrc.cjs similarity index 100% rename from dashboard/.eslintrc.js rename to dashboard/.eslintrc.cjs diff --git a/dashboard/README.md b/dashboard/README.md index 8713190..782e1f8 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -1,6 +1,6 @@ # Dashboard -Dashboard is a VueJS application that is the face of 今果 Jingrow. This is what the end users (tenants) see and manage their FC stuff in. The tenants does not have access to the desk, so, this is their dashboard for managing sites, apps, updates etc. +Dashboard is a VueJS application that is the face of Jingrow. This is what the end users (tenants) see and manage their FC stuff in. The tenants does not have access to the desk, so, this is their dashboard for managing sites, apps, updates etc. Technologies at the heart of dashboard: diff --git a/dashboard/index.html b/dashboard/index.html index a95bde5..d1ab8e5 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -1,17 +1,17 @@ - + - 今果 Jingrow + Jingrow @@ -29,6 +29,6 @@ {% endfor %} - + diff --git a/dashboard/jsconfig.json b/dashboard/jsconfig.json index 8ab59b4..c28a1c8 100644 --- a/dashboard/jsconfig.json +++ b/dashboard/jsconfig.json @@ -1,5 +1,5 @@ { - "include": ["./src/**/*", "src2/components/AddressableErrorDialog.vue"], + "include": ["./src/**/*"], "compilerOptions": { "baseUrl": ".", "paths": { diff --git a/dashboard/package.json b/dashboard/package.json index 58a8359..c18f144 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -36,6 +36,7 @@ "lodash": "^4.17.19", "luxon": "^1.22.0", "markdown-it": "^12.3.2", + "naive-ui": "^2.38.1", "papaparse": "^5.4.1", "qrcode": "^1.5.4", "register-service-worker": "^1.6.2", diff --git a/dashboard/src/App.vue b/dashboard/src/App.vue index 9b01c49..e9b4759 100644 --- a/dashboard/src/App.vue +++ b/dashboard/src/App.vue @@ -1,74 +1,451 @@ - + + + diff --git a/dashboard/src2/components/ActionButton.vue b/dashboard/src/components/ActionButton.vue similarity index 100% rename from dashboard/src2/components/ActionButton.vue rename to dashboard/src/components/ActionButton.vue diff --git a/dashboard/src2/components/ActiveServersDialog.vue b/dashboard/src/components/ActiveServersDialog.vue similarity index 100% rename from dashboard/src2/components/ActiveServersDialog.vue rename to dashboard/src/components/ActiveServersDialog.vue diff --git a/dashboard/src2/components/AddDomainDialog.vue b/dashboard/src/components/AddDomainDialog.vue similarity index 100% rename from dashboard/src2/components/AddDomainDialog.vue rename to dashboard/src/components/AddDomainDialog.vue diff --git a/dashboard/src2/components/AddTagDialog.vue b/dashboard/src/components/AddTagDialog.vue similarity index 100% rename from dashboard/src2/components/AddTagDialog.vue rename to dashboard/src/components/AddTagDialog.vue diff --git a/dashboard/src2/components/AddressForm.vue b/dashboard/src/components/AddressForm.vue similarity index 100% rename from dashboard/src2/components/AddressForm.vue rename to dashboard/src/components/AddressForm.vue diff --git a/dashboard/src2/components/AddressableErrorDialog.vue b/dashboard/src/components/AddressableErrorDialog.vue similarity index 100% rename from dashboard/src2/components/AddressableErrorDialog.vue rename to dashboard/src/components/AddressableErrorDialog.vue diff --git a/dashboard/src2/components/AlertAddPaymentMode.vue b/dashboard/src/components/AlertAddPaymentMode.vue similarity index 100% rename from dashboard/src2/components/AlertAddPaymentMode.vue rename to dashboard/src/components/AlertAddPaymentMode.vue diff --git a/dashboard/src2/components/AlertAddressDetails.vue b/dashboard/src/components/AlertAddressDetails.vue similarity index 84% rename from dashboard/src2/components/AlertAddressDetails.vue rename to dashboard/src/components/AlertAddressDetails.vue index ff039d9..119f493 100644 --- a/dashboard/src2/components/AlertAddressDetails.vue +++ b/dashboard/src/components/AlertAddressDetails.vue @@ -1,7 +1,7 @@ + + + + diff --git a/dashboard/src2/components/AppSidebarItem.vue b/dashboard/src/components/AppSidebarItem.vue similarity index 100% rename from dashboard/src2/components/AppSidebarItem.vue rename to dashboard/src/components/AppSidebarItem.vue diff --git a/dashboard/src2/components/AppSidebarItemGroup.vue b/dashboard/src/components/AppSidebarItemGroup.vue similarity index 100% rename from dashboard/src2/components/AppSidebarItemGroup.vue rename to dashboard/src/components/AppSidebarItemGroup.vue diff --git a/dashboard/src2/components/AppTrialSubscriptionDialog.vue b/dashboard/src/components/AppTrialSubscriptionDialog.vue similarity index 83% rename from dashboard/src2/components/AppTrialSubscriptionDialog.vue rename to dashboard/src/components/AppTrialSubscriptionDialog.vue index 3d8cbe9..c7645ef 100644 --- a/dashboard/src2/components/AppTrialSubscriptionDialog.vue +++ b/dashboard/src/components/AppTrialSubscriptionDialog.vue @@ -1,7 +1,7 @@ @@ -72,36 +72,32 @@ export default { icon: '', type: 'database', ext: 'application/x-gzip,application/sql,.sql', - title: '数据库备份', - description: - '上传数据库备份文件。通常文件名以 .sql.gz 或 .sql 结尾', + title: this.$t('Database Backup'), + description: this.$t('Upload database backup file. Usually the filename ends with .sql.gz or .sql'), file: null }, { icon: '', type: 'public', ext: 'application/x-tar', - title: '公共文件', - description: - '上传公共文件备份。通常文件名以 -files.tar 结尾', + title: this.$t('Public Files'), + description: this.$t('Upload public files backup. Usually the filename ends with -files.tar'), file: null }, { icon: '', type: 'private', ext: 'application/x-tar', - title: '私有文件', - description: - '上传私有文件备份。通常文件名以 -private-files.tar 结尾', + title: this.$t('Private Files'), + description: this.$t('Upload private files backup. Usually the filename ends with -private-files.tar'), file: null }, { icon: '', type: 'config', ext: 'application/json', - title: '站点配置(如备份已加密则必需)', - description: - '上传站点配置文件。通常文件名以 -site_config_backup.json 结尾', + title: this.$t('Site Config (required if backup is encrypted)'), + description: this.$t('Upload site config file. Usually the filename ends with -site_config_backup.json'), file: null } ] @@ -118,7 +114,7 @@ export default { // valid strings are "database.sql.gz", "database.sql", "database.sql (1).gz", "database.sql (2).gz" if (!/\.sql( \(\d\))?\.gz$|\.sql$/.test(file.name)) { throw new Error( - '数据库备份文件应以"database.sql.gz"或"database.sql"结尾' + this.$t('Database backup file should end with "database.sql.gz" or "database.sql"') ); } if ( @@ -128,17 +124,18 @@ export default { 'application/sql' ].includes(file.type) ) { - throw new Error('无效的数据库备份文件'); + throw new Error(this.$t('Invalid database backup file')); } } if (['public', 'private'].includes(type)) { if (file.type != 'application/x-tar') { - throw new Error(`无效的${type === 'public' ? '公共' : '私有'}文件备份文件`); + const fileType = type === 'public' ? this.$t('public') : this.$t('private'); + throw new Error(this.$t('Invalid {fileType} files backup file', { fileType })); } } if (type === 'config') { if (file.type != 'application/json') { - throw new Error(`无效的站点配置文件`); + throw new Error(this.$t('Invalid site config file')); } } } diff --git a/dashboard/src2/components/BuyPrepaidCreditsAlipay.vue b/dashboard/src/components/BuyPrepaidCreditsAlipay.vue similarity index 79% rename from dashboard/src2/components/BuyPrepaidCreditsAlipay.vue rename to dashboard/src/components/BuyPrepaidCreditsAlipay.vue index 37ea8e0..8ad43ed 100644 --- a/dashboard/src2/components/BuyPrepaidCreditsAlipay.vue +++ b/dashboard/src/components/BuyPrepaidCreditsAlipay.vue @@ -11,7 +11,7 @@ :loading="$resources.createAlipayOrder.loading || loading" @click="processAlipayPayment" > - 使用支付宝支付 + {{ $t('Pay with Alipay') }} @@ -49,18 +49,18 @@ export default { }, validate() { if (this.amount <= 0) { - throw new DashboardError('支付金额必须大于0'); + throw new DashboardError(this.$t('Payment amount must be greater than 0')); } if (this.amount < this.minimumAmount) { - throw new DashboardError('金额低于最低要求金额'); + throw new DashboardError(this.$t('Amount is below the minimum required amount')); } }, onSuccess(response) { window.open(response.payment_url, '_blank'); - toast.success('支付页面已在新窗口打开'); + toast.success(this.$t('Payment page opened in new window')); - // 如果需要检查支付状态 + // Optional: check payment status // this.checkPaymentStatus(response.order_id); } }; @@ -71,7 +71,7 @@ export default { this.$resources.createAlipayOrder.submit(); }, - // 可选:检查支付状态的方法 + // Optional: method to check payment status checkPaymentStatus(orderId) { const checkInterval = setInterval(() => { this.$call('jcloud.api.billing.check_payment_status', { @@ -80,15 +80,15 @@ export default { }).then(result => { if (result && result.status === 'Success') { clearInterval(checkInterval); - toast.success('支付成功!账户已充值'); + toast.success(this.$t('Payment successful! Account has been recharged')); this.$emit('payment-success'); } }).catch(err => { - console.error('检查支付状态出错:', err); + console.error('Error checking payment status:', err); }); }, 3000); - // 5分钟后停止检查 + // Stop checking after 5 minutes setTimeout(() => { clearInterval(checkInterval); }, 300000); diff --git a/dashboard/src/components/BuyPrepaidCreditsForm.vue b/dashboard/src/components/BuyPrepaidCreditsForm.vue new file mode 100644 index 0000000..6a19eac --- /dev/null +++ b/dashboard/src/components/BuyPrepaidCreditsForm.vue @@ -0,0 +1,388 @@ + + + \ No newline at end of file diff --git a/dashboard/src/components/BuyPrepaidCreditsPayPal.vue b/dashboard/src/components/BuyPrepaidCreditsPayPal.vue new file mode 100644 index 0000000..4041ac0 --- /dev/null +++ b/dashboard/src/components/BuyPrepaidCreditsPayPal.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src2/components/BuyPrepaidCreditsRazorpay.vue b/dashboard/src/components/BuyPrepaidCreditsRazorpay.vue similarity index 95% rename from dashboard/src2/components/BuyPrepaidCreditsRazorpay.vue rename to dashboard/src/components/BuyPrepaidCreditsRazorpay.vue index b1b3f10..1796119 100644 --- a/dashboard/src2/components/BuyPrepaidCreditsRazorpay.vue +++ b/dashboard/src/components/BuyPrepaidCreditsRazorpay.vue @@ -91,7 +91,7 @@ export default { const options = { key: data.key_id, order_id: data.order_id, - name: '今果 Jingrow', + name: 'Jingrow', image: '/assets/jcloud/images/jingrow-cloud-logo.png', prefill: { email: this.$team.pg.user diff --git a/dashboard/src2/components/BuyPrepaidCreditsStripe.vue b/dashboard/src/components/BuyPrepaidCreditsStripe.vue similarity index 100% rename from dashboard/src2/components/BuyPrepaidCreditsStripe.vue rename to dashboard/src/components/BuyPrepaidCreditsStripe.vue diff --git a/dashboard/src2/components/BuyPrepaidCreditsWeChatPay.vue b/dashboard/src/components/BuyPrepaidCreditsWeChatPay.vue similarity index 83% rename from dashboard/src2/components/BuyPrepaidCreditsWeChatPay.vue rename to dashboard/src/components/BuyPrepaidCreditsWeChatPay.vue index 517b442..ca4d575 100644 --- a/dashboard/src2/components/BuyPrepaidCreditsWeChatPay.vue +++ b/dashboard/src/components/BuyPrepaidCreditsWeChatPay.vue @@ -11,11 +11,11 @@ :loading="$resources.createWeChatPayOrder.loading || loading" @click="processWeChatPayment" > - 使用微信支付 + {{ $t('Pay with WeChat Pay') }} - +
@@ -31,20 +31,20 @@
-
扫一扫付款(元)
-
{{ amount }} 元
+
{{ $t('Scan to Pay (CNY)') }}
+
{{ amount }} {{ $t('CNY') }}
- 微信支付二维码 +
-
请使用微信扫描二维码完成支付
-
二维码有效期 15 分钟
+
{{ $t('Please scan the QR code with WeChat to complete payment') }}
+
{{ $t('QR code valid for 15 minutes') }}
@@ -55,7 +55,7 @@ + \ No newline at end of file diff --git a/dashboard/src2/components/ConfigEditorDialog.vue b/dashboard/src/components/ConfigEditorDialog.vue similarity index 100% rename from dashboard/src2/components/ConfigEditorDialog.vue rename to dashboard/src/components/ConfigEditorDialog.vue diff --git a/dashboard/src2/components/ConfigPreviewDialog.vue b/dashboard/src/components/ConfigPreviewDialog.vue similarity index 100% rename from dashboard/src2/components/ConfigPreviewDialog.vue rename to dashboard/src/components/ConfigPreviewDialog.vue diff --git a/dashboard/src2/components/DateTimeControl.vue b/dashboard/src/components/DateTimeControl.vue similarity index 100% rename from dashboard/src2/components/DateTimeControl.vue rename to dashboard/src/components/DateTimeControl.vue diff --git a/dashboard/src2/components/DialogWrapper.vue b/dashboard/src/components/DialogWrapper.vue similarity index 100% rename from dashboard/src2/components/DialogWrapper.vue rename to dashboard/src/components/DialogWrapper.vue diff --git a/dashboard/src2/components/DismissableBanner.vue b/dashboard/src/components/DismissableBanner.vue similarity index 100% rename from dashboard/src2/components/DismissableBanner.vue rename to dashboard/src/components/DismissableBanner.vue diff --git a/dashboard/src2/components/DomainOwner.vue b/dashboard/src/components/DomainOwner.vue similarity index 84% rename from dashboard/src2/components/DomainOwner.vue rename to dashboard/src/components/DomainOwner.vue index 337ad53..ec561c2 100644 --- a/dashboard/src2/components/DomainOwner.vue +++ b/dashboard/src/components/DomainOwner.vue @@ -6,7 +6,7 @@
@@ -53,18 +53,18 @@
- 链接 + {{ $t('Links') }}
- 描述 + {{ $t('Description') }}
- 描述 + {{ $t('Description') }} { this.editing = false; - return '更新成功'; + return this.$t('Listing updated successfully'); }, - loading: '正在更新列表...', + loading: this.$t('Updating listing...'), error: (err) => { return err.messages?.length ? err.messages.join('\n') - : err.message || '更新列表失败'; + : err.message || this.$t('Failed to update listing'); }, }); }, dropdownOptions(image) { return [ - { label: '查看', onClick: () => window.open(image) }, + { label: this.$t('View'), onClick: () => window.open(image) }, { - label: '删除', + label: this.$t('Delete'), onClick: () => { toast.promise( this.$resources.removeScreenshot.submit({ @@ -280,15 +280,15 @@ onClick: () => { file: image, }), { - loading: '正在删除截图...', + loading: this.$t('Deleting screenshot...'), success: () => { this.$resources.listingData.reload(); - return '截图删除成功'; + return this.$t('Screenshot deleted successfully'); }, error: (err) => { return err.messages?.length ? err.messages.join('\n') - : err.message || '删除截图失败'; + : err.message || this.$t('Failed to delete screenshot'); }, }, ); diff --git a/dashboard/src/components/MarketplaceAppProfile.vue b/dashboard/src/components/MarketplaceAppProfile.vue index 4fc23aa..f0ff9e3 100644 --- a/dashboard/src/components/MarketplaceAppProfile.vue +++ b/dashboard/src/components/MarketplaceAppProfile.vue @@ -1,5 +1,5 @@ @@ -45,12 +45,12 @@
-

Published Versions

+

{{ $t('Published Versions') }}

@@ -83,10 +83,10 @@ @@ -206,7 +206,7 @@ export default { dropdownItems(source) { return [ { - label: 'Change Branch', + label: this.$t('Change Branch'), onClick: () => { this.selectedSource = source.source; this.selectedVersion = source.version; @@ -215,7 +215,7 @@ export default { } }, { - label: 'Remove', + label: this.$t('Remove'), onClick: () => { this.$resources.removeVersion.submit({ name: this.app.name, @@ -227,7 +227,7 @@ export default { }, notifySuccess() { notify({ - title: 'App Profile Updated!', + title: this.$t('App Profile Updated!'), icon: 'check', color: 'green' }); diff --git a/dashboard/src2/components/MobileNav.vue b/dashboard/src/components/MobileNav.vue similarity index 91% rename from dashboard/src2/components/MobileNav.vue rename to dashboard/src/components/MobileNav.vue index 7f1e52b..6914340 100644 --- a/dashboard/src2/components/MobileNav.vue +++ b/dashboard/src/components/MobileNav.vue @@ -14,12 +14,12 @@ class="ml-auto" :options="[ { - label: '切换团队', + label: $t('Switch Team'), icon: 'command', onClick: () => (showTeamSwitcher = true) }, { - label: '注销', + label: $t('Logout'), icon: 'log-out', onClick: $session.logout.submit } @@ -30,7 +30,7 @@ - {{ $team?.get.loading ? '加载中...' : $team?.pg?.user }} + {{ $team?.get.loading ? $t('Loading...') : $team?.pg?.user }} diff --git a/dashboard/src2/components/MobileNavItem.vue b/dashboard/src/components/MobileNavItem.vue similarity index 100% rename from dashboard/src2/components/MobileNavItem.vue rename to dashboard/src/components/MobileNavItem.vue diff --git a/dashboard/src2/components/MobileNavItemGroup.vue b/dashboard/src/components/MobileNavItemGroup.vue similarity index 100% rename from dashboard/src2/components/MobileNavItemGroup.vue rename to dashboard/src/components/MobileNavItemGroup.vue diff --git a/dashboard/src2/components/NavigationItems.vue b/dashboard/src/components/NavigationItems.vue similarity index 87% rename from dashboard/src2/components/NavigationItems.vue rename to dashboard/src/components/NavigationItems.vue index 5fe6b57..91947c3 100644 --- a/dashboard/src2/components/NavigationItems.vue +++ b/dashboard/src/components/NavigationItems.vue @@ -39,14 +39,14 @@ export default { return [ { - name: '欢迎', + name: this.$t('Welcome'), icon: () => h(DoorOpen), route: '/welcome', isActive: routeName === 'Welcome', condition: !onboardingComplete, }, { - name: '通知', + name: this.$t('Notifications'), icon: () => h(Notification), route: '/notifications', isActive: routeName === 'Jcloud Notification List', @@ -67,7 +67,7 @@ export default { disabled: enforce2FA, }, { - name: '站点', + name: this.$t('Sites'), icon: () => h(PanelTopInactive), route: '/sites', isActive: @@ -76,7 +76,7 @@ export default { disabled: enforce2FA, }, { - name: '工作台', + name: this.$t('Benches'), icon: () => h(Package), route: '/benches', isActive: routeName.startsWith('Bench'), @@ -84,7 +84,7 @@ export default { disabled: !onboardingComplete || enforce2FA, }, { - name: '站点分组', + name: this.$t('Release Group'), icon: () => h(Boxes), route: '/groups', isActive: @@ -100,7 +100,7 @@ export default { disabled: enforce2FA, }, { - name: 'Jingrow服务器', + name: this.$t('Jingrow Servers'), icon: () => h(Server), route: '/servers', isActive: @@ -110,7 +110,7 @@ export default { disabled: enforce2FA, }, { - name: '域名', + name: this.$t('Domain'), icon: () => h(Globe), route: '/domains', isActive: @@ -119,7 +119,7 @@ export default { disabled: enforce2FA, }, { - name: '服务器', + name: this.$t('Servers'), icon: () => h(Server), route: '/jsite-servers', isActive: @@ -128,7 +128,7 @@ export default { disabled: enforce2FA, }, { - name: '应用市场', + name: this.$t('Marketplace'), icon: () => h(App), route: '/apps', isActive: routeName.startsWith('Marketplace'), @@ -138,26 +138,26 @@ export default { disabled: enforce2FA, }, { - name: '开发工具', + name: this.$t('Developer Tools'), icon: () => h(Code), route: '/devtools', condition: onboardingComplete && !isSaasUser && this.$team.pg.is_developer && this.$team.pg.is_pro, disabled: enforce2FA, children: [ { - name: 'SQL 实验室', + name: this.$t('SQL Playground'), icon: () => h(DatabaseZap), route: '/sql-playground', isActive: routeName === 'SQL Playground', }, { - name: '日志浏览器', + name: this.$t('Log Browser'), icon: () => h(Logs), route: '/log-browser', isActive: routeName === 'Log Browser', }, { - name: '数据库分析器', + name: this.$t('Database Analyzer'), icon: () => h(Activity), route: '/database-analyzer', isActive: routeName === 'DB Analyzer', @@ -169,7 +169,7 @@ export default { disabled: enforce2FA, }, { - name: '充值', + name: this.$t('Recharge'), icon: () => h(CreditCard), route: '/recharge', isActive: routeName.startsWith('RechargeCredits'), @@ -178,7 +178,7 @@ export default { disabled: enforce2FA, }, { - name: '账单', + name: this.$t('Billing'), icon: () => h(WalletCards), route: '/billing', isActive: routeName.startsWith('Billing'), @@ -187,7 +187,7 @@ export default { disabled: enforce2FA, }, { - name: '合作伙伴门户', + name: this.$t('Partner Portal'), icon: () => h(Globe), route: '/partners', isActive: routeName.startsWith('Partner'), @@ -195,7 +195,7 @@ export default { disabled: enforce2FA, }, { - name: '设置', + name: this.$t('Settings'), icon: () => h(Settings), route: '/settings', isActive: routeName.startsWith('Settings'), diff --git a/dashboard/src2/components/NewAppDialog.vue b/dashboard/src/components/NewAppDialog.vue similarity index 100% rename from dashboard/src2/components/NewAppDialog.vue rename to dashboard/src/components/NewAppDialog.vue diff --git a/dashboard/src2/components/ObjectList.vue b/dashboard/src/components/ObjectList.vue similarity index 95% rename from dashboard/src2/components/ObjectList.vue rename to dashboard/src/components/ObjectList.vue index 8273b33..4342d40 100644 --- a/dashboard/src2/components/ObjectList.vue +++ b/dashboard/src/components/ObjectList.vue @@ -14,7 +14,7 @@
-

欢迎来到 今果 Jingrow

+

{{ $t('Welcome to Jingrow') }}

- Jingrow是一站式通用数字化平台,以"一切皆页面"的革新理念,帮助您快速构建从个人工作到团队协作的一站式数字化解决方案。通过AI Agent智能体、可视化工作流编排、零代码可视化数据建模、自动化任务、团队协作、角色与权限管理、文件库、知识库、笔记,会议,待办事项,通知、智能报表等模块,引领数字化协作新趋势。 + {{ $t('Jingrow is a one-stop universal digital platform that helps you quickly build comprehensive digital solutions from personal work to team collaboration through innovative concepts like "Everything is a Page". Through modules such as AI Agent, visual workflow orchestration, zero-code visual data modeling, automated tasks, team collaboration, role and permission management, file library, knowledge base, notes, meetings, to-do items, notifications, and intelligent reports, leading the new trend of digital collaboration.') }}

- 从这里开始,让一切数字化、系统化、智能化、自动化。 + {{ $t('Start here to make everything digital, systematic, intelligent, and automated.') }}

@@ -16,7 +16,7 @@
1 - 账户已创建 + {{ $t('Account Created') }}
2 - 余额充值 + {{ $t('Account Recharge') }}
@@ -55,13 +55,13 @@ class="text-base font-medium" v-if="$team.pg.payment_mode === 'Card'" > - 自动扣费设置已完成 + {{ $t('Automated Billing Setup Completed') }} - 余额支付已开通 + {{ $t('Prepaid Credits Payment Enabled') }}
- 账户余额: {{ $format.userCurrency($team.pg.balance) }} + {{ $t('Account Balance') }}: {{ $format.userCurrency($team.pg.balance) }}
@@ -88,22 +88,22 @@ class="text-base font-medium" v-if="pendingSiteRequest.status == 'Error'" > - 创建您的 {{ pendingSiteRequest.title }} 试用站点时出错 + {{ $t('Error creating your {title} trial site', { title: pendingSiteRequest.title }) }} - 创建您的 {{ pendingSiteRequest.title }} 试用站点 + {{ $t('Create your {title} trial site', { title: pendingSiteRequest.title }) }}

- 请点击下方按钮联系 今果 Jingrow支持团队。 + {{ $t('Please click the button below to contact Jingrow support team.') }}

- +

- 您可以点击下方按钮免费试用 {{ pendingSiteRequest.title }} 应用程序。 + {{ $t('You can click the button below to try the {title} application for free.', { title: pendingSiteRequest.title }) }}

@@ -124,7 +124,7 @@
3 - 您的试用站点已准备就绪 + {{ $t('Your trial site is ready') }}

- 您的试用将于 + {{ $t('Your trial will expire on') }} {{ $format.date(trialSite.trial_end_date, 'LL') }} 到期。立即设置账单以确保您的站点访问不受影响。 + >. {{ $t('Set up billing now to ensure your site access is not affected.') }}

3 -
创建您的第一个数字化平台
+
{{ $t('Create your first digital platform') }}
diff --git a/dashboard/src2/components/OnboardingAppSelector.vue b/dashboard/src/components/OnboardingAppSelector.vue similarity index 100% rename from dashboard/src2/components/OnboardingAppSelector.vue rename to dashboard/src/components/OnboardingAppSelector.vue diff --git a/dashboard/src2/components/OnboardingWithoutPayment.vue b/dashboard/src/components/OnboardingWithoutPayment.vue similarity index 100% rename from dashboard/src2/components/OnboardingWithoutPayment.vue rename to dashboard/src/components/OnboardingWithoutPayment.vue diff --git a/dashboard/src2/components/OrderCheckout.vue b/dashboard/src/components/OrderCheckout.vue similarity index 100% rename from dashboard/src2/components/OrderCheckout.vue rename to dashboard/src/components/OrderCheckout.vue diff --git a/dashboard/src2/components/PayoutTable.vue b/dashboard/src/components/PayoutTable.vue similarity index 100% rename from dashboard/src2/components/PayoutTable.vue rename to dashboard/src/components/PayoutTable.vue diff --git a/dashboard/src2/components/PlansCards.vue b/dashboard/src/components/PlansCards.vue similarity index 100% rename from dashboard/src2/components/PlansCards.vue rename to dashboard/src/components/PlansCards.vue diff --git a/dashboard/src/components/PrepaidCreditsDialog.vue b/dashboard/src/components/PrepaidCreditsDialog.vue index 8e9f225..58e1f5f 100644 --- a/dashboard/src/components/PrepaidCreditsDialog.vue +++ b/dashboard/src/components/PrepaidCreditsDialog.vue @@ -195,7 +195,7 @@ export default { const options = { key: data.key_id, order_id: data.order_id, - name: '今果 Jingrow', + name: 'Jingrow', image: '/assets/jcloud/images/jingrow-cloud-logo.png', prefill: { email: this.$account.team.user diff --git a/dashboard/src2/components/SaaSSignupFields.vue b/dashboard/src/components/SaaSSignupFields.vue similarity index 100% rename from dashboard/src2/components/SaaSSignupFields.vue rename to dashboard/src/components/SaaSSignupFields.vue diff --git a/dashboard/src/components/Sidebar.vue b/dashboard/src/components/Sidebar.vue index b8bcce4..644c842 100644 --- a/dashboard/src/components/Sidebar.vue +++ b/dashboard/src/components/Sidebar.vue @@ -10,7 +10,7 @@
- 今果 Jingrow + Jingrow
- Search + {{ $t('Search') }} @@ -58,7 +58,7 @@ - Notifications + {{ $t('Notifications') }} (this.showTeamSwitcher = true) }, { - label: 'Support & Docs', + label: this.$t('Support & Docs'), icon: 'help-circle', onClick: () => (window.location.href = '/support') }, { - label: 'Logout', + label: this.$t('Logout'), icon: 'log-out', onClick: () => this.$auth.logout() } - ] - }; - }, + ]; + }, mounted() { window.addEventListener('keydown', e => { if (e.key === 'k' && (e.ctrlKey || e.metaKey)) { diff --git a/dashboard/src2/components/SiteActionCell.vue b/dashboard/src/components/SiteActionCell.vue similarity index 100% rename from dashboard/src2/components/SiteActionCell.vue rename to dashboard/src/components/SiteActionCell.vue diff --git a/dashboard/src2/components/SiteActions.vue b/dashboard/src/components/SiteActions.vue similarity index 100% rename from dashboard/src2/components/SiteActions.vue rename to dashboard/src/components/SiteActions.vue diff --git a/dashboard/src2/components/SiteDailyUsage.vue b/dashboard/src/components/SiteDailyUsage.vue similarity index 100% rename from dashboard/src2/components/SiteDailyUsage.vue rename to dashboard/src/components/SiteDailyUsage.vue diff --git a/dashboard/src2/components/SiteDatabaseAccessDialog.vue b/dashboard/src/components/SiteDatabaseAccessDialog.vue similarity index 100% rename from dashboard/src2/components/SiteDatabaseAccessDialog.vue rename to dashboard/src/components/SiteDatabaseAccessDialog.vue diff --git a/dashboard/src2/components/SiteDatabaseRestoreDialog.vue b/dashboard/src/components/SiteDatabaseRestoreDialog.vue similarity index 78% rename from dashboard/src2/components/SiteDatabaseRestoreDialog.vue rename to dashboard/src/components/SiteDatabaseRestoreDialog.vue index 11a8772..958ddc0 100644 --- a/dashboard/src2/components/SiteDatabaseRestoreDialog.vue +++ b/dashboard/src/components/SiteDatabaseRestoreDialog.vue @@ -1,10 +1,10 @@ + \ No newline at end of file diff --git a/dashboard/src2/components/StripeCardDialog.vue b/dashboard/src/components/StripeCardDialog.vue similarity index 100% rename from dashboard/src2/components/StripeCardDialog.vue rename to dashboard/src/components/StripeCardDialog.vue diff --git a/dashboard/src/components/StripeLogo.vue b/dashboard/src/components/StripeLogo.vue index 377ddfc..33726a4 100644 --- a/dashboard/src/components/StripeLogo.vue +++ b/dashboard/src/components/StripeLogo.vue @@ -2,8 +2,8 @@ - - Current Team: {{ $account.team.name }} -
- + - + \ No newline at end of file diff --git a/dashboard/src2/components/TabsWithRouter.vue b/dashboard/src/components/TabsWithRouter.vue similarity index 100% rename from dashboard/src2/components/TabsWithRouter.vue rename to dashboard/src/components/TabsWithRouter.vue diff --git a/dashboard/src2/components/TextInsideCircle.vue b/dashboard/src/components/TextInsideCircle.vue similarity index 100% rename from dashboard/src2/components/TextInsideCircle.vue rename to dashboard/src/components/TextInsideCircle.vue diff --git a/dashboard/src2/components/ToggleContent.vue b/dashboard/src/components/ToggleContent.vue similarity index 100% rename from dashboard/src2/components/ToggleContent.vue rename to dashboard/src/components/ToggleContent.vue diff --git a/dashboard/src2/components/UpdateBillingDetails.vue b/dashboard/src/components/UpdateBillingDetails.vue similarity index 100% rename from dashboard/src2/components/UpdateBillingDetails.vue rename to dashboard/src/components/UpdateBillingDetails.vue diff --git a/dashboard/src2/components/UpdateBillingDetailsForm.vue b/dashboard/src/components/UpdateBillingDetailsForm.vue similarity index 100% rename from dashboard/src2/components/UpdateBillingDetailsForm.vue rename to dashboard/src/components/UpdateBillingDetailsForm.vue diff --git a/dashboard/src2/components/UserWithAvatarCell.vue b/dashboard/src/components/UserWithAvatarCell.vue similarity index 100% rename from dashboard/src2/components/UserWithAvatarCell.vue rename to dashboard/src/components/UserWithAvatarCell.vue diff --git a/dashboard/src/components/auth/Configure2FA.vue b/dashboard/src/components/auth/Configure2FA.vue new file mode 100644 index 0000000..b5bb148 --- /dev/null +++ b/dashboard/src/components/auth/Configure2FA.vue @@ -0,0 +1,238 @@ + + + \ No newline at end of file diff --git a/dashboard/src2/components/auth/LoginBox.vue b/dashboard/src/components/auth/LoginBox.vue similarity index 94% rename from dashboard/src2/components/auth/LoginBox.vue rename to dashboard/src/components/auth/LoginBox.vue index 584ab28..246f48b 100644 --- a/dashboard/src2/components/auth/LoginBox.vue +++ b/dashboard/src/components/auth/LoginBox.vue @@ -11,7 +11,7 @@ - 今果 Jingrow + Jingrow
diff --git a/dashboard/src2/components/auth/SaaSLoginBox.vue b/dashboard/src/components/auth/SaaSLoginBox.vue similarity index 100% rename from dashboard/src2/components/auth/SaaSLoginBox.vue rename to dashboard/src/components/auth/SaaSLoginBox.vue diff --git a/dashboard/src2/components/billing/AddCardDialog.vue b/dashboard/src/components/billing/AddCardDialog.vue similarity index 100% rename from dashboard/src2/components/billing/AddCardDialog.vue rename to dashboard/src/components/billing/AddCardDialog.vue diff --git a/dashboard/src2/components/billing/AddExchangeRate.vue b/dashboard/src/components/billing/AddExchangeRate.vue similarity index 100% rename from dashboard/src2/components/billing/AddExchangeRate.vue rename to dashboard/src/components/billing/AddExchangeRate.vue diff --git a/dashboard/src/components/billing/AddPrepaidCreditsDialog.vue b/dashboard/src/components/billing/AddPrepaidCreditsDialog.vue new file mode 100644 index 0000000..a76eddc --- /dev/null +++ b/dashboard/src/components/billing/AddPrepaidCreditsDialog.vue @@ -0,0 +1,97 @@ + + + + \ No newline at end of file diff --git a/dashboard/src2/components/billing/BillingDetails.vue b/dashboard/src/components/billing/BillingDetails.vue similarity index 75% rename from dashboard/src2/components/billing/BillingDetails.vue rename to dashboard/src/components/billing/BillingDetails.vue index a042257..b6ae43c 100644 --- a/dashboard/src2/components/billing/BillingDetails.vue +++ b/dashboard/src/components/billing/BillingDetails.vue @@ -4,7 +4,7 @@ v-model="billingInformation.billing_name" type="text" name="billing_name" - label="账单名称" + :label="$t('Billing Name')" :required="true" />
-
+ + \ No newline at end of file diff --git a/dashboard/src2/components/billing/BillingSummary.vue b/dashboard/src/components/billing/BillingSummary.vue similarity index 79% rename from dashboard/src2/components/billing/BillingSummary.vue rename to dashboard/src/components/billing/BillingSummary.vue index c1b7a4a..8809cec 100644 --- a/dashboard/src2/components/billing/BillingSummary.vue +++ b/dashboard/src/components/billing/BillingSummary.vue @@ -4,9 +4,9 @@
-
账单费用
+
{{ t('Billing Charges') }}
- 下次扣款日期 — + {{ t('Next billing date') }} — {{ currentMonthEnd() }}
@@ -15,14 +15,14 @@
- 当前账单金额为 + {{ t('Current billing amount is') }} {{ currency }} {{ currentBillingAmount?.toFixed(2) || '0.00' }}
-
@@ -33,12 +33,12 @@
- 未支付金额为 + {{ t('Unpaid amount is') }} {{ currency }} {{ unpaidAmount.data?.toFixed(2) }}
-
@@ -56,8 +56,10 @@ import UpcomingInvoiceDialog from './UpcomingInvoiceDialog.vue'; import { Button, createResource } from 'jingrow-ui'; import { ref, computed, inject } from 'vue'; import { confirmDialog } from '../../utils/components'; +import { useI18n } from '../../composables/useI18n'; import router from '../../router'; +const { t } = useI18n(); const team = inject('team'); const { currentBillingAmount, upcomingInvoice, unpaidInvoices } = inject('billing'); @@ -76,7 +78,7 @@ const unpaidAmount = createResource({ const currentMonthEnd = () => { const date = new Date(); const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0); - return lastDay.toLocaleDateString('zh-CN', { + return lastDay.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' @@ -96,11 +98,10 @@ function payUnpaidInvoices() { showAddPrepaidCreditsDialog.value = true; } else { confirmDialog({ - title: '多张未支付发票', - message: - '您有多张未支付发票。请从发票页面支付它们', + title: t('Multiple Unpaid Invoices'), + message: t('You have multiple unpaid invoices. Please pay them from the invoices page.'), primaryAction: { - label: '前往发票', + label: t('Go to Invoices'), variant: 'solid', onClick: ({ hide }) => { router.push({ name: 'BillingInvoices' }); diff --git a/dashboard/src2/components/billing/BuyCreditsRazorpay.vue b/dashboard/src/components/billing/BuyCreditsRazorpay.vue similarity index 95% rename from dashboard/src2/components/billing/BuyCreditsRazorpay.vue rename to dashboard/src/components/billing/BuyCreditsRazorpay.vue index cb24897..0c41bb1 100644 --- a/dashboard/src2/components/billing/BuyCreditsRazorpay.vue +++ b/dashboard/src/components/billing/BuyCreditsRazorpay.vue @@ -94,7 +94,7 @@ function processOrder(data) { const options = { key: data.key_id, order_id: data.order_id, - name: '今果 Jingrow', + name: 'Jingrow', image: 'https://jingrow.com/files/cloud.png', prefill: { email: team.pg?.user }, handler: handlePaymentSuccess, diff --git a/dashboard/src2/components/billing/BuyCreditsStripe.vue b/dashboard/src/components/billing/BuyCreditsStripe.vue similarity index 100% rename from dashboard/src2/components/billing/BuyCreditsStripe.vue rename to dashboard/src/components/billing/BuyCreditsStripe.vue diff --git a/dashboard/src2/components/billing/CardForm.vue b/dashboard/src/components/billing/CardForm.vue similarity index 100% rename from dashboard/src2/components/billing/CardForm.vue rename to dashboard/src/components/billing/CardForm.vue diff --git a/dashboard/src2/components/billing/ChangeCardDialog.vue b/dashboard/src/components/billing/ChangeCardDialog.vue similarity index 100% rename from dashboard/src2/components/billing/ChangeCardDialog.vue rename to dashboard/src/components/billing/ChangeCardDialog.vue diff --git a/dashboard/src2/components/billing/DropdownItem.vue b/dashboard/src/components/billing/DropdownItem.vue similarity index 100% rename from dashboard/src2/components/billing/DropdownItem.vue rename to dashboard/src/components/billing/DropdownItem.vue diff --git a/dashboard/src2/components/billing/FinalizeInvoicesDialog.vue b/dashboard/src/components/billing/FinalizeInvoicesDialog.vue similarity index 100% rename from dashboard/src2/components/billing/FinalizeInvoicesDialog.vue rename to dashboard/src/components/billing/FinalizeInvoicesDialog.vue diff --git a/dashboard/src2/components/billing/NewAddressForm.vue b/dashboard/src/components/billing/NewAddressForm.vue similarity index 79% rename from dashboard/src2/components/billing/NewAddressForm.vue rename to dashboard/src/components/billing/NewAddressForm.vue index f405012..bb02fbd 100644 --- a/dashboard/src2/components/billing/NewAddressForm.vue +++ b/dashboard/src/components/billing/NewAddressForm.vue @@ -25,7 +25,7 @@ \ No newline at end of file + + + \ No newline at end of file diff --git a/dashboard/src2/components/billing/PrepaidCreditsForm.vue b/dashboard/src/components/billing/PrepaidCreditsForm.vue similarity index 68% rename from dashboard/src2/components/billing/PrepaidCreditsForm.vue rename to dashboard/src/components/billing/PrepaidCreditsForm.vue index 1ba4082..30f6dca 100644 --- a/dashboard/src2/components/billing/PrepaidCreditsForm.vue +++ b/dashboard/src/components/billing/PrepaidCreditsForm.vue @@ -1,7 +1,7 @@ + \ No newline at end of file diff --git a/dashboard/src2/components/marketplace/CodeReview.vue b/dashboard/src/components/marketplace/CodeReview.vue similarity index 100% rename from dashboard/src2/components/marketplace/CodeReview.vue rename to dashboard/src/components/marketplace/CodeReview.vue diff --git a/dashboard/src2/components/marketplace/MarketplaceAppAnalytics.vue b/dashboard/src/components/marketplace/MarketplaceAppAnalytics.vue similarity index 87% rename from dashboard/src2/components/marketplace/MarketplaceAppAnalytics.vue rename to dashboard/src/components/marketplace/MarketplaceAppAnalytics.vue index 65a2507..b1fdcec 100644 --- a/dashboard/src2/components/marketplace/MarketplaceAppAnalytics.vue +++ b/dashboard/src/components/marketplace/MarketplaceAppAnalytics.vue @@ -3,7 +3,7 @@
-
总安装量
+
{{ $t('Total Installs') }}
@@ -16,7 +16,7 @@
-
激活站点
+
{{ $t('Active Sites') }}
@@ -29,7 +29,7 @@
-
激活站点分组
+
{{ $t('Active Benches') }}
@@ -42,7 +42,7 @@
-
每周安装量
+
{{ $t('Weekly Installs') }}
@@ -55,7 +55,7 @@
-
总收入
+
{{ $t('Total Earnings') }}
@@ -74,7 +74,7 @@ class="grid grid-cols-1 gap-5 lg:grid-cols-2" > @@ -39,7 +39,7 @@ class="flex text-base text-gray-700" > - 正在验证应用... + {{ $t('Validating app...') }}
@@ -48,7 +48,7 @@ name="check" :stroke-width="3" /> - 找到 {{ this.app.title }} ({{ this.app.name }}) + {{ $t('Found {title} ({name})', { title: this.app.title, name: this.app.name }) }}
@@ -56,12 +56,12 @@ class="w-4 p-0.5 text-white rounded bg-red-500" name="x" /> - Github 仓库是私有的。 + {{ $t('Github repository is private.') }} - 条款和政策 + {{ $t('Terms and Policies') }}
@@ -140,14 +140,14 @@ export default { methods: { addApp() { toast.promise(this.$resources.addApp.submit(), { - loading: '正在添加新应用...', + loading: this.$t('Adding new app...'), success: () => { this.show = false; this.$router.push({ name: 'Marketplace App Detail Listing', params: { name: this.app.name } }); - return '新应用已添加'; + return this.$t('New app added'); }, error: e => getToastErrorMessage(e) }); diff --git a/dashboard/src2/components/marketplace/PlansDialog.vue b/dashboard/src/components/marketplace/PlansDialog.vue similarity index 100% rename from dashboard/src2/components/marketplace/PlansDialog.vue rename to dashboard/src/components/marketplace/PlansDialog.vue diff --git a/dashboard/src/components/marketplace/PublisherPayoutInfoCard.vue b/dashboard/src/components/marketplace/PublisherPayoutInfoCard.vue index 20d3d78..4e1e052 100644 --- a/dashboard/src/components/marketplace/PublisherPayoutInfoCard.vue +++ b/dashboard/src/components/marketplace/PublisherPayoutInfoCard.vue @@ -56,7 +56,7 @@ @@ -68,7 +68,7 @@ diff --git a/dashboard/src2/components/marketplace/ReplyMarketplaceApp.vue b/dashboard/src/components/marketplace/ReplyMarketplaceApp.vue similarity index 100% rename from dashboard/src2/components/marketplace/ReplyMarketplaceApp.vue rename to dashboard/src/components/marketplace/ReplyMarketplaceApp.vue diff --git a/dashboard/src2/components/partners/BuyPartnerCreditsRazorpay.vue b/dashboard/src/components/partners/BuyPartnerCreditsRazorpay.vue similarity index 95% rename from dashboard/src2/components/partners/BuyPartnerCreditsRazorpay.vue rename to dashboard/src/components/partners/BuyPartnerCreditsRazorpay.vue index e355977..7548d2d 100644 --- a/dashboard/src2/components/partners/BuyPartnerCreditsRazorpay.vue +++ b/dashboard/src/components/partners/BuyPartnerCreditsRazorpay.vue @@ -102,7 +102,7 @@ function processOrder(data) { const options = { key: data.key_id, order_id: data.order_id, - name: '今果 Jingrow', + name: 'Jingrow', image: 'https://jingrow.com/files/cloud.png', prefill: { email: team.pg?.user }, handler: handlePaymentSuccess, diff --git a/dashboard/src2/components/partners/BuyPartnerCreditsStripe.vue b/dashboard/src/components/partners/BuyPartnerCreditsStripe.vue similarity index 100% rename from dashboard/src2/components/partners/BuyPartnerCreditsStripe.vue rename to dashboard/src/components/partners/BuyPartnerCreditsStripe.vue diff --git a/dashboard/src2/components/partners/PartnerApprovalRequests.vue b/dashboard/src/components/partners/PartnerApprovalRequests.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerApprovalRequests.vue rename to dashboard/src/components/partners/PartnerApprovalRequests.vue diff --git a/dashboard/src2/components/partners/PartnerContribution.vue b/dashboard/src/components/partners/PartnerContribution.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerContribution.vue rename to dashboard/src/components/partners/PartnerContribution.vue diff --git a/dashboard/src2/components/partners/PartnerCreditsForm.vue b/dashboard/src/components/partners/PartnerCreditsForm.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerCreditsForm.vue rename to dashboard/src/components/partners/PartnerCreditsForm.vue diff --git a/dashboard/src2/components/partners/PartnerCustomerInvoices.vue b/dashboard/src/components/partners/PartnerCustomerInvoices.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerCustomerInvoices.vue rename to dashboard/src/components/partners/PartnerCustomerInvoices.vue diff --git a/dashboard/src2/components/partners/PartnerCustomers.vue b/dashboard/src/components/partners/PartnerCustomers.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerCustomers.vue rename to dashboard/src/components/partners/PartnerCustomers.vue diff --git a/dashboard/src2/components/partners/PartnerLocalPaymentSetup.vue b/dashboard/src/components/partners/PartnerLocalPaymentSetup.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerLocalPaymentSetup.vue rename to dashboard/src/components/partners/PartnerLocalPaymentSetup.vue diff --git a/dashboard/src2/components/partners/PartnerMembers.vue b/dashboard/src/components/partners/PartnerMembers.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerMembers.vue rename to dashboard/src/components/partners/PartnerMembers.vue diff --git a/dashboard/src2/components/partners/PartnerOverview.vue b/dashboard/src/components/partners/PartnerOverview.vue similarity index 100% rename from dashboard/src2/components/partners/PartnerOverview.vue rename to dashboard/src/components/partners/PartnerOverview.vue diff --git a/dashboard/src2/components/server/ServerActionCell.vue b/dashboard/src/components/server/ServerActionCell.vue similarity index 100% rename from dashboard/src2/components/server/ServerActionCell.vue rename to dashboard/src/components/server/ServerActionCell.vue diff --git a/dashboard/src2/components/server/ServerActions.vue b/dashboard/src/components/server/ServerActions.vue similarity index 100% rename from dashboard/src2/components/server/ServerActions.vue rename to dashboard/src/components/server/ServerActions.vue diff --git a/dashboard/src2/components/server/ServerCharts.vue b/dashboard/src/components/server/ServerCharts.vue similarity index 100% rename from dashboard/src2/components/server/ServerCharts.vue rename to dashboard/src/components/server/ServerCharts.vue diff --git a/dashboard/src2/components/server/ServerLoadAverage.vue b/dashboard/src/components/server/ServerLoadAverage.vue similarity index 100% rename from dashboard/src2/components/server/ServerLoadAverage.vue rename to dashboard/src/components/server/ServerLoadAverage.vue diff --git a/dashboard/src2/components/server/ServerOverview.vue b/dashboard/src/components/server/ServerOverview.vue similarity index 100% rename from dashboard/src2/components/server/ServerOverview.vue rename to dashboard/src/components/server/ServerOverview.vue diff --git a/dashboard/src2/components/server/ServerPlansCards.vue b/dashboard/src/components/server/ServerPlansCards.vue similarity index 100% rename from dashboard/src2/components/server/ServerPlansCards.vue rename to dashboard/src/components/server/ServerPlansCards.vue diff --git a/dashboard/src2/components/server/ServerPlansDialog.vue b/dashboard/src/components/server/ServerPlansDialog.vue similarity index 100% rename from dashboard/src2/components/server/ServerPlansDialog.vue rename to dashboard/src/components/server/ServerPlansDialog.vue diff --git a/dashboard/src/components/settings/ActivateWebhookDialog.vue b/dashboard/src/components/settings/ActivateWebhookDialog.vue new file mode 100644 index 0000000..eb9a0cb --- /dev/null +++ b/dashboard/src/components/settings/ActivateWebhookDialog.vue @@ -0,0 +1,277 @@ + + + + + diff --git a/dashboard/src/components/settings/AddNewWebhookDialog.vue b/dashboard/src/components/settings/AddNewWebhookDialog.vue new file mode 100644 index 0000000..cb77099 --- /dev/null +++ b/dashboard/src/components/settings/AddNewWebhookDialog.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/dashboard/src/components/settings/DeveloperSettings.vue b/dashboard/src/components/settings/DeveloperSettings.vue new file mode 100644 index 0000000..cf5c865 --- /dev/null +++ b/dashboard/src/components/settings/DeveloperSettings.vue @@ -0,0 +1,675 @@ + + + + + diff --git a/dashboard/src/components/settings/EditWebhookDialog.vue b/dashboard/src/components/settings/EditWebhookDialog.vue new file mode 100644 index 0000000..fd0b566 --- /dev/null +++ b/dashboard/src/components/settings/EditWebhookDialog.vue @@ -0,0 +1,348 @@ + + + + + diff --git a/dashboard/src/components/settings/InviteTeamMemberDialog.vue b/dashboard/src/components/settings/InviteTeamMemberDialog.vue new file mode 100644 index 0000000..f222be5 --- /dev/null +++ b/dashboard/src/components/settings/InviteTeamMemberDialog.vue @@ -0,0 +1,339 @@ + + + + + diff --git a/dashboard/src/components/settings/RoleConfigureDialog.vue b/dashboard/src/components/settings/RoleConfigureDialog.vue new file mode 100644 index 0000000..986862d --- /dev/null +++ b/dashboard/src/components/settings/RoleConfigureDialog.vue @@ -0,0 +1,470 @@ + + + + + diff --git a/dashboard/src/components/settings/RoleList.vue b/dashboard/src/components/settings/RoleList.vue new file mode 100644 index 0000000..ba5766e --- /dev/null +++ b/dashboard/src/components/settings/RoleList.vue @@ -0,0 +1,307 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/settings/RolePermissions.vue b/dashboard/src/components/settings/RolePermissions.vue new file mode 100644 index 0000000..34f6329 --- /dev/null +++ b/dashboard/src/components/settings/RolePermissions.vue @@ -0,0 +1,410 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/settings/SettingsPermissions.vue b/dashboard/src/components/settings/SettingsPermissions.vue new file mode 100644 index 0000000..c3edb59 --- /dev/null +++ b/dashboard/src/components/settings/SettingsPermissions.vue @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/settings/TeamSettings.vue b/dashboard/src/components/settings/TeamSettings.vue new file mode 100644 index 0000000..b134dde --- /dev/null +++ b/dashboard/src/components/settings/TeamSettings.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/dashboard/src2/components/settings/TeamSettingsDialog.vue b/dashboard/src/components/settings/TeamSettingsDialog.vue similarity index 72% rename from dashboard/src2/components/settings/TeamSettingsDialog.vue rename to dashboard/src/components/settings/TeamSettingsDialog.vue index d5359d5..0cadc02 100644 --- a/dashboard/src2/components/settings/TeamSettingsDialog.vue +++ b/dashboard/src/components/settings/TeamSettingsDialog.vue @@ -1,7 +1,7 @@ diff --git a/dashboard/src2/components/settings/WebhookAttemptDetails.vue b/dashboard/src/components/settings/WebhookAttemptDetails.vue similarity index 100% rename from dashboard/src2/components/settings/WebhookAttemptDetails.vue rename to dashboard/src/components/settings/WebhookAttemptDetails.vue diff --git a/dashboard/src/components/settings/WebhookAttemptsDialog.vue b/dashboard/src/components/settings/WebhookAttemptsDialog.vue new file mode 100644 index 0000000..c30b8e1 --- /dev/null +++ b/dashboard/src/components/settings/WebhookAttemptsDialog.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/dashboard/src2/components/settings/profile/AccountEmails.vue b/dashboard/src/components/settings/profile/AccountEmails.vue similarity index 100% rename from dashboard/src2/components/settings/profile/AccountEmails.vue rename to dashboard/src/components/settings/profile/AccountEmails.vue diff --git a/dashboard/src/components/settings/profile/AccountPartner.vue b/dashboard/src/components/settings/profile/AccountPartner.vue new file mode 100644 index 0000000..c5cde3d --- /dev/null +++ b/dashboard/src/components/settings/profile/AccountPartner.vue @@ -0,0 +1,294 @@ + + + + \ No newline at end of file diff --git a/dashboard/src/components/settings/profile/AccountProfile.vue b/dashboard/src/components/settings/profile/AccountProfile.vue new file mode 100644 index 0000000..67250c8 --- /dev/null +++ b/dashboard/src/components/settings/profile/AccountProfile.vue @@ -0,0 +1,964 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src/components/settings/profile/AccountReferral.vue b/dashboard/src/components/settings/profile/AccountReferral.vue new file mode 100644 index 0000000..2c9944b --- /dev/null +++ b/dashboard/src/components/settings/profile/AccountReferral.vue @@ -0,0 +1,56 @@ + + + + \ No newline at end of file diff --git a/dashboard/src/components/settings/profile/ProfileSettings.vue b/dashboard/src/components/settings/profile/ProfileSettings.vue new file mode 100644 index 0000000..3c67a82 --- /dev/null +++ b/dashboard/src/components/settings/profile/ProfileSettings.vue @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/dashboard/src2/components/settings/profile/ResetPasswordDialog.vue b/dashboard/src/components/settings/profile/ResetPasswordDialog.vue similarity index 50% rename from dashboard/src2/components/settings/profile/ResetPasswordDialog.vue rename to dashboard/src/components/settings/profile/ResetPasswordDialog.vue index c95d700..549c937 100644 --- a/dashboard/src2/components/settings/profile/ResetPasswordDialog.vue +++ b/dashboard/src/components/settings/profile/ResetPasswordDialog.vue @@ -1,76 +1,106 @@ @@ -433,13 +433,7 @@ export default { password: null, otpResendCountdown: 0, resetPasswordEmailSent: false, - useEmail: false, confirmPassword: '', - passwordMismatch: false, - passwordStrength: 0, - passwordStrengthClass: '', - passwordStrengthText: '', - passwordStrengthTextClass: '', phoneNumberFormatError: false, }; }, @@ -469,7 +463,7 @@ export default { this.account_request = account_request; this.otpRequested = true; this.otpResendCountdown = 30; - toast.success('验证码已发送至您的邮箱'); + toast.success(this.$t('Verification code has been sent to your email')); }, onError: (error) => { if (error?.exc_type !== 'ValidationError') { @@ -520,11 +514,11 @@ export default { onSuccess() { this.otp = ''; this.otpResendCountdown = 30; - toast.success('验证码已发送至您的邮箱'); + toast.success(this.$t('Verification code has been sent to your email')); }, onError(err) { toast.error( - getToastErrorMessage(err, '验证码重发失败'), + getToastErrorMessage(err, this.$t('Failed to resend verification code')), ); }, }; @@ -538,11 +532,11 @@ export default { onSuccess() { this.otpSent = true; this.otpResendCountdown = 30; - toast.success('验证码已发送至您的邮箱'); + toast.success(this.$t('Verification code has been sent to your email')); }, onError(err) { toast.error( - getToastErrorMessage(err, '验证码发送失败'), + getToastErrorMessage(err, this.$t('Failed to send verification code')), ); }, }; @@ -621,7 +615,7 @@ export default { }, onSuccess(res) { if (res && res.success === false) { - toast.error(res.message || '注册失败,请检查您的信息'); + toast.error(res.message || this.$t('Sign up failed, please check your information')); return; } @@ -636,7 +630,7 @@ export default { onError(err) { const errorMessage = err?.messages?.length ? err.messages.join('\n') - : (err?.message || '注册失败,请检查您的信息'); + : (err?.message || this.$t('Sign up failed, please check your information')); toast.error(errorMessage); }, }; @@ -667,27 +661,27 @@ export default { this.checkTwoFactorAndResetPassword(); } else { if (!this.username) { - toast.error('用户名不能为空'); + toast.error(this.$t('Username cannot be empty')); return; } if (!this.phoneNumber) { - toast.error('手机号不能为空'); + toast.error(this.$t('Phone number cannot be empty')); return; } if (!this.isPhoneNumberValid) { - toast.error('请输入正确的手机号码格式'); + toast.error(this.$t('Please enter a valid phone number format')); return; } if (!this.signupPassword) { - toast.error('密码不能为空'); + toast.error(this.$t('Password cannot be empty')); return; } if (this.signupPassword !== this.confirmPassword) { - toast.error('两次输入的密码不一致'); + toast.error(this.$t('Passwords do not match')); return; } if (!this.isPasswordValid) { - toast.error('密码必须至少8个字符,并包含大小写字母和数字'); + toast.error(this.$t('Password must be at least 8 characters and contain uppercase and lowercase letters and numbers')); return; } this.$resources.signupWithUsername.submit(); @@ -885,17 +879,17 @@ export default { }, title() { if (this.hasForgotPassword) { - return '重置密码'; + return this.$t('Reset Password'); } else if (this.isLogin) { if (this.saasProduct) { - return `登录您的账户以开始使用 ${this.saasProduct.title}`; + return this.$t('Log in to your account to start using {product}', { product: this.saasProduct.title }); } - return '登录您的账户'; + return this.$t('Log in to your account'); } else { if (this.saasProduct) { - return `注册以创建您的 ${this.saasProduct.title} 站点`; + return this.$t('Sign up to create your {product} site', { product: this.saasProduct.title }); } - return '创建新账户'; + return this.$t('Create New Account'); } }, useEmail() { @@ -931,10 +925,10 @@ export default { return 'bg-green-500'; }, passwordStrengthText() { - if (this.passwordStrength < 40) return '弱'; - if (this.passwordStrength < 60) return '一般'; - if (this.passwordStrength < 80) return '强'; - return '非常强'; + if (this.passwordStrength < 40) return this.$t('Weak'); + if (this.passwordStrength < 60) return this.$t('Fair'); + if (this.passwordStrength < 80) return this.$t('Strong'); + return this.$t('Very Strong'); }, passwordStrengthTextClass() { if (this.passwordStrength < 40) return 'text-red-500'; @@ -950,7 +944,6 @@ export default { /[0-9]/.test(this.signupPassword); }, isPhoneNumberValid() { - // 中国大陆手机号验证:11位数字,以1开头,第二位为3-9 const phoneRegex = /^1[3-9]\d{9}$/; return phoneRegex.test(this.phoneNumber); }, diff --git a/dashboard/src2/pages/NewJsiteDomain.vue b/dashboard/src/pages/NewJsiteDomain.vue similarity index 100% rename from dashboard/src2/pages/NewJsiteDomain.vue rename to dashboard/src/pages/NewJsiteDomain.vue diff --git a/dashboard/src2/pages/NewJsiteServer.vue b/dashboard/src/pages/NewJsiteServer.vue similarity index 80% rename from dashboard/src2/pages/NewJsiteServer.vue rename to dashboard/src/pages/NewJsiteServer.vue index 8f7ff43..1459b21 100644 --- a/dashboard/src2/pages/NewJsiteServer.vue +++ b/dashboard/src/pages/NewJsiteServer.vue @@ -5,8 +5,8 @@
@@ -17,17 +17,17 @@
-

新建服务器

+

{{ $t('New Server') }}

- 请选择服务器配置和支付方式,点击"创建"后将自动为您开通。 + {{ $t('Please select server configuration and payment method. Click "Create" to automatically provision the server.') }}

- + - + @@ -45,9 +45,9 @@
- + @@ -64,18 +64,18 @@
-
月度费用
+
{{ $t('Monthly Fee') }}
¥ {{ getSelectedPlanPrice() }} - (月付) + ({{ $t('Monthly') }})
-
购买时长
-
{{ period }} 个月
+
{{ $t('Purchase Duration') }}
+
{{ period }} {{ $t('months') }}
-
总计
+
{{ $t('Total') }}
¥ {{ getTotalAmount() }}
@@ -83,7 +83,7 @@
@@ -145,7 +145,7 @@
-

正在处理支付,请稍候...

+

{{ $t('Processing payment, please wait...') }}

@@ -157,12 +157,12 @@
-

支付成功!

+

{{ $t('Payment Successful!') }}

- 您的订单已支付成功,服务器记录已创建。服务器正在后台创建中,创建并启动需要 3-5 分钟,请耐心等待。 + {{ $t('Your order has been paid successfully and the server record has been created. The server is being created in the background. Creation and startup will take 3-5 minutes, please be patient.') }}

- 您可以在服务器列表中查看服务器状态,创建完成后状态将更新为"运行中"。 + {{ $t('You can check the server status in the server list. After creation is complete, the status will be updated to "Running".') }}

@@ -184,8 +184,8 @@
-
扫一扫付款(元)
-
{{ order?.total_amount }} 元
+
{{ $t('Scan to Pay (CNY)') }}
+
{{ order?.total_amount }} {{ $t('CNY') }}
@@ -200,9 +200,9 @@

- 请使用微信扫描二维码完成支付 + {{ $t('Please scan the QR code with WeChat to complete payment') }}

-

二维码有效期 15 分钟

+

{{ $t('QR code valid for 15 minutes') }}

@@ -212,19 +212,19 @@
-

请在新页面完成支付宝支付

+

{{ $t('Please complete Alipay payment in the new page') }}

- 如果没有自动跳转,请点击下方按钮打开支付页面 + {{ $t('If it does not automatically redirect, please click the button below to open the payment page') }}

- 支付完成后,请稍等片刻,系统会自动刷新页面 + {{ $t('After payment is completed, please wait a moment, the system will automatically refresh the page') }}

@@ -260,14 +260,7 @@ export default { regions: [], plans: [], images: [], - periods: [ - { value: 1, label: '1个月' }, - { value: 3, label: '3个月' }, - { value: 6, label: '6个月' }, - { value: 12, label: '1年' }, - { value: 24, label: '2年' }, - { value: 36, label: '3年' } - ], + periods: [], isLoading: false, error: null, success: false, @@ -314,11 +307,11 @@ export default { if (Array.isArray(regionsData) && regionsData.length > 0) { this.regions = regionsData; } else { - this.error = '区域数据格式不正确或为空'; + this.error = this.$t('Region data format is incorrect or empty'); } }, onError(error) { - this.error = error.message || '获取区域失败'; + this.error = error.message || this.$t('Failed to get regions'); } }; }, @@ -330,7 +323,7 @@ export default { this.plans = data.plans || []; }, onError(error) { - this.error = error.message || '获取套餐失败'; + this.error = error.message || this.$t('Failed to get plans'); } }; }, @@ -363,11 +356,11 @@ export default { if (Array.isArray(imagesData) && imagesData.length > 0) { this.images = imagesData; } else { - this.error = '镜像数据格式不正确或为空'; + this.error = this.$t('Image data format is incorrect or empty'); } }, onError(error) { - this.error = error.message || '获取镜像失败'; + this.error = error.message || this.$t('Failed to get images'); } }; }, @@ -377,15 +370,15 @@ export default { url: 'jcloud.api.aliyun_server_light.create_server_order', validate() { if (!this.selectedRegionId || !this.selectedPlanId || !this.selectedImageId) { - throw new DashboardError('请选择完整配置'); + throw new DashboardError(this.$t('Please select complete configuration')); } if (!this.selectedPaymentMethod) { - throw new DashboardError('请选择支付方式'); + throw new DashboardError(this.$t('Please select payment method')); } }, onSuccess(data) { if (!data.success) { - this.error = data.message || '创建服务器订单失败'; + this.error = data.message || this.$t('Failed to create server order'); return; } @@ -398,7 +391,7 @@ export default { this.processPayment(); }, onError(error) { - this.error = error.message || '创建服务器订单失败'; + this.error = error.message || this.$t('Failed to create server order'); } }; }, @@ -408,13 +401,13 @@ export default { url: 'jcloud.api.billing.process_balance_payment_for_server_order', validate() { if (!this.order || !this.order.order_id) { - throw new DashboardError('缺少订单信息'); + throw new DashboardError(this.$t('Missing order information')); } }, onSuccess(response) { if (response.status === "Error" || response.success === false) { - toast.error(response.message || '支付失败,请确保余额充足'); - this.error = response.message || '余额不足'; + toast.error(response.message || this.$t('Payment failed, please ensure sufficient balance')); + this.error = response.message || this.$t('Insufficient balance'); this.isProcessingPayment = false; return; } @@ -439,7 +432,7 @@ export default { }, 30000); }, onError(error) { - this.error = error.message || '余额支付处理失败'; + this.error = error.message || this.$t('Balance payment processing failed'); this.isProcessingPayment = false; } }; @@ -450,20 +443,20 @@ export default { url: 'jcloud.api.billing.process_alipay_order', validate() { if (!this.order || !this.order.order_id) { - throw new DashboardError('缺少订单信息'); + throw new DashboardError(this.$t('Missing order information')); } }, onSuccess(response) { this.paymentUrl = response.payment_url; window.open(response.payment_url, '_blank'); - toast.success('支付页面已在新窗口打开'); + toast.success(this.$t('Payment page opened in new window')); // 开始轮询支付状态 this.startPaymentCheck(); }, onError(error) { - this.error = error.message || '支付宝支付处理失败'; + this.error = error.message || this.$t('Alipay payment processing failed'); this.isProcessingPayment = false; } }; @@ -474,7 +467,7 @@ export default { url: 'jcloud.api.billing.process_wechatpay_order', validate() { if (!this.order || !this.order.order_id) { - throw new DashboardError('缺少订单信息'); + throw new DashboardError(this.$t('Missing order information')); } }, onSuccess(response) { @@ -485,7 +478,7 @@ export default { this.startPaymentCheck(); }, onError(error) { - this.error = error.message || '微信支付处理失败'; + this.error = error.message || this.$t('WeChat Pay payment processing failed'); this.isProcessingPayment = false; } }; @@ -499,7 +492,7 @@ export default { }, validate() { if (!this.order || !this.order.order_id) { - throw new DashboardError('缺少订单信息'); + throw new DashboardError(this.$t('Missing order information')); } }, onSuccess(data) { @@ -565,6 +558,15 @@ export default { }, mounted() { this.$resources.aliyunRegions.submit(); + // Initialize periods with translations + this.periods = [ + { value: 1, label: this.$t('1 month') }, + { value: 3, label: this.$t('3 months') }, + { value: 6, label: this.$t('6 months') }, + { value: 12, label: this.$t('1 year') }, + { value: 24, label: this.$t('2 years') }, + { value: 36, label: this.$t('3 years') } + ]; }, methods: { async onRegionChange() { @@ -592,11 +594,11 @@ export default { }, async createInstance() { if (!this.selectedRegionId || !this.selectedPlanId || !this.selectedImageId) { - this.error = '请选择完整配置'; + this.error = this.$t('Please select complete configuration'); return; } if (!this.selectedPaymentMethod) { - this.error = '请选择支付方式'; + this.error = this.$t('Please select payment method'); return; } @@ -637,7 +639,7 @@ export default { this.error = null; if (!this.order || !this.order.order_id) { - this.error = '订单信息不完整,请重试'; + this.error = this.$t('Order information is incomplete, please try again'); this.isProcessingPayment = false; return; } @@ -694,22 +696,22 @@ export default { let typePrefix = ''; switch (planType) { case 'NORMAL': - typePrefix = '通用型 - '; + typePrefix = this.$t('General Type - '); break; case 'MULTI_IP': - typePrefix = '多公网IP型 - '; + typePrefix = this.$t('Multi Public IP Type - '); break; case 'INTERNATIONAL': - typePrefix = '国际型 - '; + typePrefix = this.$t('International Type - '); break; case 'CAPACITY': - typePrefix = '容量型 - '; + typePrefix = this.$t('Capacity Type - '); break; default: typePrefix = ''; } - return `${typePrefix}${cpu}核/${memoryDisplay}/${disk}GB/${bandwidthDisplay}/${publicIpNum}个公网IP - ¥${price}/月`; + return `${typePrefix}${cpu}${this.$t('cores')}/${memoryDisplay}/${disk}GB/${bandwidthDisplay}/${publicIpNum}${this.$t('public IPs')} - ¥${price}/${this.$t('month')}`; }, getSelectedPlanPrice() { const selectedPlan = this.filteredPlans.find(plan => plan.plan_id === this.selectedPlanId); @@ -728,7 +730,7 @@ export default { }, getImageDisplayName(image) { // 根据阿里云API返回的数据结构显示镜像信息 - const name = image.image_name || image.name || image.ImageName || image.Name || '未知镜像'; + const name = image.image_name || image.name || image.ImageName || image.Name || this.$t('Unknown image'); const platform = image.platform || ''; const description = image.description || ''; @@ -740,11 +742,11 @@ export default { }, getPaymentMethodName(method) { const methodNames = { - 'balance': '余额支付', - 'alipay': '支付宝', - 'wechatpay': '微信支付' + 'balance': this.$t('Balance Payment'), + 'alipay': this.$t('Alipay'), + 'wechatpay': this.$t('WeChat Pay') }; - return methodNames[method] || '未知方式'; + return methodNames[method] || this.$t('Unknown method'); } }, diff --git a/dashboard/src2/pages/NewReleaseGroup.vue b/dashboard/src/pages/NewReleaseGroup.vue similarity index 100% rename from dashboard/src2/pages/NewReleaseGroup.vue rename to dashboard/src/pages/NewReleaseGroup.vue diff --git a/dashboard/src2/pages/NewServer.vue b/dashboard/src/pages/NewServer.vue similarity index 100% rename from dashboard/src2/pages/NewServer.vue rename to dashboard/src/pages/NewServer.vue diff --git a/dashboard/src2/pages/NewSite.vue b/dashboard/src/pages/NewSite.vue similarity index 100% rename from dashboard/src2/pages/NewSite.vue rename to dashboard/src/pages/NewSite.vue diff --git a/dashboard/src2/pages/Partners.vue b/dashboard/src/pages/Partners.vue similarity index 100% rename from dashboard/src2/pages/Partners.vue rename to dashboard/src/pages/Partners.vue diff --git a/dashboard/src2/pages/PlayPage.vue b/dashboard/src/pages/PlayPage.vue similarity index 100% rename from dashboard/src2/pages/PlayPage.vue rename to dashboard/src/pages/PlayPage.vue diff --git a/dashboard/src2/pages/RechargeCredits.vue b/dashboard/src/pages/RechargeCredits.vue similarity index 72% rename from dashboard/src2/pages/RechargeCredits.vue rename to dashboard/src/pages/RechargeCredits.vue index 0baf4fc..5667bdd 100644 --- a/dashboard/src2/pages/RechargeCredits.vue +++ b/dashboard/src/pages/RechargeCredits.vue @@ -2,11 +2,11 @@
-

账户充值

-

为您的账户充值余额,用于支付您的服务费用。

+

{{ $t('Account Recharge') }}

+

{{ $t('Recharge your account balance to pay for your services.') }}

- +
- 在线充值 + {{ $t('Online Recharge') }}
- 对公汇款 + {{ $t('Bank Transfer') }}
- +
- +
-

汇款账户信息

+

{{ $t('Bank Account Information') }}

- 公司名称 + {{ $t('Company Name') }} 广州市向日葵网络信息科技有限公司
- 开户银行 + {{ $t('Bank Name') }} 中国农业银行股份有限公司广州花都金狮支行
- 银行账号 + {{ $t('Account Number') }} 4408 6601 0400 20960
- +
-

重要提示

+

{{ $t('Important Notice') }}

    -
  • 汇款时请备注用户所属团队ID: +
  • {{ $t('Please include your team ID in the transfer note:') }} {{ $team.pg.name }} - 已复制! + {{ $t('Copied!') }}
  • -
  • 汇款成功后请联系客服进行确认,财务人员会在1个工作日内处理
  • -
  • 如有疑问,请联系客服。
  • +
  • {{ $t('After successful transfer, please contact customer service for confirmation. Our finance team will process within 1 business day.') }}
  • +
  • {{ $t('If you have any questions, please contact customer service.') }}
- +
-

充值流程

+

{{ $t('Recharge Process') }}

- 完成对公汇款后,我们的财务人员会在确认收款后,将资金添加到您的账户余额中。 - 通常这个过程需要1个工作日。如需加急处理,请联系客服。 + {{ $t('After completing the bank transfer, our finance team will add the funds to your account balance after confirming receipt. This process usually takes 1 business day. For urgent processing, please contact customer service.') }}

@@ -113,31 +112,31 @@ export default { }, data() { return { - paymentMethod: 'online', // 默认在线充值 + paymentMethod: 'online', // Default to online recharge minimumAmount: 0, showCopiedMessage: false }; }, created() { - // 从URL参数中获取最小充值金额(如果有) + // Get minimum recharge amount from URL parameter if available if (this.$route.query.amount) { this.minimumAmount = parseFloat(this.$route.query.amount) || 0; } }, methods: { onRechargeSuccess() { - this.$toast.success('充值成功'); + this.$toast.success(this.$t('Recharge successful')); - // 如果是从其他页面跳转过来的,可以返回上一页 + // If redirected from another page, go back if (this.$route.query.redirect) { this.$router.push(this.$route.query.redirect); } else { - // 否则跳转到账单页面 + // Otherwise navigate to billing page this.$router.push('/billing'); } }, onRechargeCancel() { - // 取消充值,返回上一页 + // Cancel recharge, go back this.$router.back(); }, copyTransferNote() { @@ -190,7 +189,7 @@ export default { if (successful) { this.showCopiedSuccessMessage(); } else { - this.$toast.error('复制失败,请手动复制'); + this.$toast.error(this.$t('Copy failed, please copy manually')); } }, diff --git a/dashboard/src2/pages/ReleaseGroupBenchSites.vue b/dashboard/src/pages/ReleaseGroupBenchSites.vue similarity index 100% rename from dashboard/src2/pages/ReleaseGroupBenchSites.vue rename to dashboard/src/pages/ReleaseGroupBenchSites.vue diff --git a/dashboard/src2/pages/ResetPassword.vue b/dashboard/src/pages/ResetPassword.vue similarity index 100% rename from dashboard/src2/pages/ResetPassword.vue rename to dashboard/src/pages/ResetPassword.vue diff --git a/dashboard/src2/pages/Settings.vue b/dashboard/src/pages/Settings.vue similarity index 75% rename from dashboard/src2/pages/Settings.vue rename to dashboard/src/pages/Settings.vue index 58d5cb0..5e48c4c 100644 --- a/dashboard/src2/pages/Settings.vue +++ b/dashboard/src/pages/Settings.vue @@ -1,7 +1,7 @@ diff --git a/dashboard/src2/pages/SiteLogin.vue b/dashboard/src/pages/SiteLogin.vue similarity index 94% rename from dashboard/src2/pages/SiteLogin.vue rename to dashboard/src/pages/SiteLogin.vue index a6f6771..fec3ab2 100644 --- a/dashboard/src2/pages/SiteLogin.vue +++ b/dashboard/src/pages/SiteLogin.vue @@ -1,6 +1,6 @@ @@ -93,7 +93,7 @@
- 由 今果 Jingrow 提供支持 + 由 Jingrow 提供支持
@@ -132,7 +132,7 @@
- 由 今果 Jingrow 提供支持 + 由 Jingrow 提供支持
diff --git a/dashboard/src2/pages/saas/OAuthSetupAccount.vue b/dashboard/src/pages/saas/OAuthSetupAccount.vue similarity index 100% rename from dashboard/src2/pages/saas/OAuthSetupAccount.vue rename to dashboard/src/pages/saas/OAuthSetupAccount.vue diff --git a/dashboard/src2/pages/saas/SetupSite.vue b/dashboard/src/pages/saas/SetupSite.vue similarity index 95% rename from dashboard/src2/pages/saas/SetupSite.vue rename to dashboard/src/pages/saas/SetupSite.vue index 2144883..1dd2ffb 100644 --- a/dashboard/src2/pages/saas/SetupSite.vue +++ b/dashboard/src/pages/saas/SetupSite.vue @@ -101,7 +101,7 @@
- 由 今果 Jingrow 提供支持 + 由 Jingrow 提供支持
diff --git a/dashboard/src2/pages/saas/Signup.vue b/dashboard/src/pages/saas/Signup.vue similarity index 100% rename from dashboard/src2/pages/saas/Signup.vue rename to dashboard/src/pages/saas/Signup.vue diff --git a/dashboard/src2/pages/saas/VerifyEmail.vue b/dashboard/src/pages/saas/VerifyEmail.vue similarity index 100% rename from dashboard/src2/pages/saas/VerifyEmail.vue rename to dashboard/src/pages/saas/VerifyEmail.vue diff --git a/dashboard/src2/router.js b/dashboard/src/router.js similarity index 94% rename from dashboard/src2/router.js rename to dashboard/src/router.js index 03fce2d..034b2e2 100644 --- a/dashboard/src2/router.js +++ b/dashboard/src/router.js @@ -59,7 +59,7 @@ let router = createRouter({ { path: '/checkout/:secretKey', name: 'Checkout', - component: () => import('../src/views/checkout/Checkout.vue'), + component: () => import('./views/checkout/Checkout.vue'), props: true, meta: { isLoginPage: true, @@ -68,7 +68,7 @@ let router = createRouter({ { path: '/subscription/:site?', name: 'Subscription', - component: () => import('../src/views/checkout/Subscription.vue'), + component: () => import('./views/checkout/Subscription.vue'), props: true, meta: { hideSidebar: true, @@ -351,7 +351,7 @@ let router = createRouter({ { path: '/:pathMatch(.*)*', name: '404', - component: () => import('../src/views/general/404.vue'), + component: () => import('./views/general/404.vue'), }, ], }); diff --git a/dashboard/src2/socket.js b/dashboard/src/socket.js similarity index 83% rename from dashboard/src2/socket.js rename to dashboard/src/socket.js index 27fb4f7..b272307 100644 --- a/dashboard/src2/socket.js +++ b/dashboard/src/socket.js @@ -5,13 +5,14 @@ import { getCachedResource, getCachedListResource } from 'jingrow-ui'; export function initSocket() { let host = window.location.hostname; let siteName = window.site_name; + let protocol = window.location.protocol.slice(0, -1); let port = window.location.port ? `:${socketio_port}` : ''; - let protocol = port ? 'http' : 'https'; let url = `${protocol}://${host}${port}/${siteName}`; let socket = io(url, { withCredentials: true, - reconnectionAttempts: 5 + reconnectionAttempts: 5, + secure: protocol === 'https' }); socket.on('refetch_resource', data => { diff --git a/dashboard/src2/types.ts b/dashboard/src/types.ts similarity index 100% rename from dashboard/src2/types.ts rename to dashboard/src/types.ts diff --git a/dashboard/src2/utils/agentJob.js b/dashboard/src/utils/agentJob.js similarity index 100% rename from dashboard/src2/utils/agentJob.js rename to dashboard/src/utils/agentJob.js diff --git a/dashboard/src2/utils/components.jsx b/dashboard/src/utils/components.jsx similarity index 100% rename from dashboard/src2/utils/components.jsx rename to dashboard/src/utils/components.jsx diff --git a/dashboard/src2/utils/country.ts b/dashboard/src/utils/country.ts similarity index 100% rename from dashboard/src2/utils/country.ts rename to dashboard/src/utils/country.ts diff --git a/dashboard/src/utils/dayjs.js b/dashboard/src/utils/dayjs.js index 8b2ab04..556cfce 100644 --- a/dashboard/src/utils/dayjs.js +++ b/dashboard/src/utils/dayjs.js @@ -59,4 +59,14 @@ dayjs.shortFormating = (s, ago = false) => { }`; }; +export function dayjsLocal(dateTimeString) { + let localTimezone = dayjs.tz.guess(); + // dates are stored in Asia/Calcutta timezone on the server + return dayjs.tz(dateTimeString, 'Asia/Calcutta').tz(localTimezone); +} + +export function dayjsIST(dateTimeString) { + return dayjs(dateTimeString).tz('Asia/Calcutta'); +} + export default dayjs; diff --git a/dashboard/src2/utils/device.ts b/dashboard/src/utils/device.ts similarity index 100% rename from dashboard/src2/utils/device.ts rename to dashboard/src/utils/device.ts diff --git a/dashboard/src2/utils/error.ts b/dashboard/src/utils/error.ts similarity index 100% rename from dashboard/src2/utils/error.ts rename to dashboard/src/utils/error.ts diff --git a/dashboard/src2/utils/format.js b/dashboard/src/utils/format.js similarity index 83% rename from dashboard/src2/utils/format.js rename to dashboard/src/utils/format.js index 4dac7f2..c879f2b 100644 --- a/dashboard/src2/utils/format.js +++ b/dashboard/src/utils/format.js @@ -1,6 +1,7 @@ -import dayjs, { dayjsLocal } from '../utils/dayjs'; +import dayjs, { dayjsLocal } from './dayjs'; import { getTeam } from '../data/team'; import { format } from 'sql-formatter'; +import { t } from './i18n'; export function bytes(bytes, decimals = 2, current = 0) { if (bytes === 0) return '0 Bytes'; @@ -224,36 +225,36 @@ export function formatValue(value, type) { } } -export const statusMap = { - 'Active': '激活', - 'Inactive': '未激活', - 'Suspended': '已暂停', - 'Broken': '损坏', - 'Archived': '已归档', - 'Pending': '待处理', - 'Installing': '安装中', - 'Running': '运行中', - 'Success': '成功', - 'Failure': '失败' -}; - -export const deployTypeMap = { - 'Migrate': '迁移', - 'Pull': '拉取', - 'Update': '更新', - 'Install': '安装', - 'Uninstall': '卸载', - 'Backup': '备份', - 'Restore': '恢复', - 'Upgrade': '升级', - 'Downgrade': '降级', - 'Rollback': '回滚', -}; - +// Status labels - use translation function export function getStatusLabel(status) { - return statusMap[status] || status; + const statusLabels = { + 'Active': t('Active'), + 'Inactive': t('Inactive'), + 'Suspended': t('Suspended'), + 'Broken': t('Broken'), + 'Archived': t('Archived'), + 'Pending': t('Pending'), + 'Installing': t('Installing'), + 'Running': t('Running'), + 'Success': t('Success'), + 'Failure': t('Failure') + }; + return statusLabels[status] || status; } +// Deploy type labels - use translation function export function getDeployTypeLabel(type) { - return deployTypeMap[type] || type; + const deployTypeLabels = { + 'Migrate': t('Migrate'), + 'Pull': t('Pull'), + 'Update': t('Update'), + 'Install': t('Install'), + 'Uninstall': t('Uninstall'), + 'Backup': t('Backup'), + 'Restore': t('Restore'), + 'Upgrade': t('Upgrade'), + 'Downgrade': t('Downgrade'), + 'Rollback': t('Rollback'), + }; + return deployTypeLabels[type] || type; } diff --git a/dashboard/src/utils/i18n.js b/dashboard/src/utils/i18n.js new file mode 100644 index 0000000..ca6c16b --- /dev/null +++ b/dashboard/src/utils/i18n.js @@ -0,0 +1,53 @@ +import { ref } from 'vue'; + +// 从构建时配置获取语言,默认为英文 +const buildLocale = import.meta.env.DASHBOARD_LOCALE || 'en'; + +// 当前语言(从构建配置获取,不可切换) +const currentLocale = ref(buildLocale); +// 翻译数据:构建时 Vite 插件已直接替换静态文本,这里只处理动态翻译 +const translations = ref({}); + +/** + * 翻译函数 + * @param {string} key - 翻译键(通常是英文原文) + * @param {object} params - 参数对象,用于替换占位符 + * @returns {string} 翻译后的文本 + */ +export function t(key, params = {}) { + if (!key || typeof key !== 'string') return key; + + // 从翻译字典中获取翻译,如果没有则返回原文 + let translated = translations.value[key] || key; + + // 支持参数替换,如 t('Hello {name}', { name: 'John' }) + if (params && typeof params === 'object' && Object.keys(params).length > 0) { + Object.keys(params).forEach((paramKey) => { + const regex = new RegExp(`\\{${paramKey}\\}`, 'g'); + translated = translated.replace(regex, String(params[paramKey])); + }); + } + + return translated; +} + +/** + * 获取当前语言(构建时配置的语言) + * @returns {string} 当前语言代码 + */ +export function getLocale() { + return currentLocale.value; +} + +/** + * 初始化 i18n + * 使用构建时配置的语言 + */ +export async function initI18n() { + const locale = buildLocale.split('-')[0] || 'en'; // 处理 'en-US' -> 'en' + document.documentElement.lang = locale; +} + +// 导出响应式状态(供 composable 使用) +export { currentLocale }; + diff --git a/dashboard/src2/utils/regions.ts b/dashboard/src/utils/regions.ts similarity index 100% rename from dashboard/src2/utils/regions.ts rename to dashboard/src/utils/regions.ts diff --git a/dashboard/src2/utils/resource.js b/dashboard/src/utils/resource.js similarity index 100% rename from dashboard/src2/utils/resource.js rename to dashboard/src/utils/resource.js diff --git a/dashboard/src2/utils/site.js b/dashboard/src/utils/site.js similarity index 100% rename from dashboard/src2/utils/site.js rename to dashboard/src/utils/site.js diff --git a/dashboard/src2/utils/throttle.ts b/dashboard/src/utils/throttle.ts similarity index 100% rename from dashboard/src2/utils/throttle.ts rename to dashboard/src/utils/throttle.ts diff --git a/dashboard/src/utils/toast.js b/dashboard/src/utils/toast.js index a2a4871..9a3493e 100644 --- a/dashboard/src/utils/toast.js +++ b/dashboard/src/utils/toast.js @@ -1,4 +1,5 @@ -import { ref } from 'vue'; +import { ref, h } from 'vue'; +import { toast } from 'vue-sonner'; export const notifications = ref([]); @@ -13,3 +14,19 @@ export const notify = props => { notifications.value.push(props); setTimeout(() => hideNotification(props.id), props.timeout || 5000); }; + +export function getToastErrorMessage(error, fallbackMessage = 'An error occurred') { + if (!error) return fallbackMessage; + + try { + const errorMessage = error.messages?.length + ? error.messages.join('
') + : (error.message || fallbackMessage); + + return h('div', { + innerHTML: errorMessage, + }); + } catch (e) { + return fallbackMessage; + } +} diff --git a/dashboard/src2/vendor/posthog.js b/dashboard/src/vendor/posthog.js similarity index 100% rename from dashboard/src2/vendor/posthog.js rename to dashboard/src/vendor/posthog.js diff --git a/dashboard/src/views/auth/Auth.vue b/dashboard/src/views/auth/Auth.vue index 830ce52..1a45ec6 100644 --- a/dashboard/src/views/auth/Auth.vue +++ b/dashboard/src/views/auth/Auth.vue @@ -8,7 +8,7 @@ >
Reset password
- Sign in to 今果 Jingrow to start using + Sign in to Jingrow to start using {{ saasProduct.title }}
diff --git a/dashboard/src/views/bench/Bench.vue b/dashboard/src/views/bench/Bench.vue index a987fb1..d3696f3 100644 --- a/dashboard/src/views/bench/Bench.vue +++ b/dashboard/src/views/bench/Bench.vue @@ -70,7 +70,7 @@ export default { name: 'Bench', pageMeta() { return { - title: `Bench - ${this.bench?.title || 'Private'} - 今果 Jingrow` + title: `Bench - ${this.bench?.title || 'Private'} - Jingrow` }; }, props: ['benchName'], diff --git a/dashboard/src/views/bench/Benches.vue b/dashboard/src/views/bench/Benches.vue index 2402d84..e34bb4c 100644 --- a/dashboard/src/views/bench/Benches.vue +++ b/dashboard/src/views/bench/Benches.vue @@ -174,7 +174,7 @@ export default { }, pageMeta() { return { - title: 'Benches - 今果 Jingrow' + title: 'Benches - Jingrow' }; }, components: { diff --git a/dashboard/src/views/billing/AccountBilling.vue b/dashboard/src/views/billing/AccountBilling.vue index 6972da0..030bd8d 100644 --- a/dashboard/src/views/billing/AccountBilling.vue +++ b/dashboard/src/views/billing/AccountBilling.vue @@ -28,7 +28,7 @@ export default { name: 'BillingScreen', pageMeta() { return { - title: 'Billing - 今果 Jingrow' + title: 'Billing - Jingrow' }; }, props: ['invoiceName'], diff --git a/dashboard/src/views/billing/AccountBillingCards.vue b/dashboard/src/views/billing/AccountBillingCards.vue index ae798ce..116f1ab 100644 --- a/dashboard/src/views/billing/AccountBillingCards.vue +++ b/dashboard/src/views/billing/AccountBillingCards.vue @@ -85,7 +85,7 @@ href="https://jingrow.com/docs/billing/disable-account" >cancelling subscription. - 今果 Jingrow is not responsible for any refund if the account is not + Jingrow is not responsible for any refund if the account is not closed properly. diff --git a/dashboard/src/views/billing/AccountBillingDetails.vue b/dashboard/src/views/billing/AccountBillingDetails.vue index 219ae4b..ad53e01 100644 --- a/dashboard/src/views/billing/AccountBillingDetails.vue +++ b/dashboard/src/views/billing/AccountBillingDetails.vue @@ -40,7 +40,7 @@ export default { emits: ['updated'], components: { UpdateBillingDetails: defineAsyncComponent(() => - import('../../../src2/components/UpdateBillingDetails.vue') + import('@/components/UpdateBillingDetails.vue') ) }, resources: { diff --git a/dashboard/src/views/billing/BillingOverview.vue b/dashboard/src/views/billing/BillingOverview.vue index e31c551..a5053b8 100644 --- a/dashboard/src/views/billing/BillingOverview.vue +++ b/dashboard/src/views/billing/BillingOverview.vue @@ -13,7 +13,7 @@ export default { name: 'BillingOverview', pageMeta() { return { - title: 'Billing - 今果 Jingrow' + title: 'Billing - Jingrow' }; }, props: ['invoiceName'], diff --git a/dashboard/src/views/billing/BillingSummary.vue b/dashboard/src/views/billing/BillingSummary.vue index ab28884..aec3778 100644 --- a/dashboard/src/views/billing/BillingSummary.vue +++ b/dashboard/src/views/billing/BillingSummary.vue @@ -129,7 +129,7 @@ export default { import('@/components/ChangePaymentModeDialog.vue') ), UpdateBillingDetails: defineAsyncComponent(() => - import('../../../src2/components/UpdateBillingDetails.vue') + import('@/components/UpdateBillingDetails.vue') ) }, resources: { diff --git a/dashboard/src/views/billing/PaymentMethods.vue b/dashboard/src/views/billing/PaymentMethods.vue index e9830f6..d348a07 100644 --- a/dashboard/src/views/billing/PaymentMethods.vue +++ b/dashboard/src/views/billing/PaymentMethods.vue @@ -13,7 +13,7 @@ export default { name: 'BillingScreen', pageMeta() { return { - title: 'Billing - 今果 Jingrow' + title: 'Billing - Jingrow' }; }, props: ['invoiceName'], diff --git a/dashboard/src/views/checkout/CheckoutPayment.vue b/dashboard/src/views/checkout/CheckoutPayment.vue index 9f14c34..51554f4 100644 --- a/dashboard/src/views/checkout/CheckoutPayment.vue +++ b/dashboard/src/views/checkout/CheckoutPayment.vue @@ -37,7 +37,7 @@ + + diff --git a/dashboard/src/views/site/AppSiteSetup.vue b/dashboard/src/views/site/AppSiteSetup.vue index ee6afe8..393b73c 100644 --- a/dashboard/src/views/site/AppSiteSetup.vue +++ b/dashboard/src/views/site/AppSiteSetup.vue @@ -12,7 +12,7 @@
{{ saasProduct.data.title }}
-
Powered by 今果 Jingrow
+
Powered by Jingrow
diff --git a/dashboard/src/views/site/Site.vue b/dashboard/src/views/site/Site.vue index b4c41e0..45c24d2 100644 --- a/dashboard/src/views/site/Site.vue +++ b/dashboard/src/views/site/Site.vue @@ -163,7 +163,7 @@ export default { name: 'Site', pageMeta() { return { - title: `Site - ${this.siteName} - 今果 Jingrow` + title: `Site - ${this.siteName} - Jingrow` }; }, props: ['siteName'], diff --git a/dashboard/src/views/site/SiteAlerts.vue b/dashboard/src/views/site/SiteAlerts.vue index 9b5d348..d129425 100644 --- a/dashboard/src/views/site/SiteAlerts.vue +++ b/dashboard/src/views/site/SiteAlerts.vue @@ -192,7 +192,7 @@ const marketplacePromotionalBanners = createResource({ v-model="showPromotionalDialog" @close="e => (clickedPromotion = null)" :options="{ - title: '今果 Jingrow Marketplace', + title: 'Jingrow Marketplace', actions: [ { variant: 'solid', diff --git a/dashboard/src/views/site/SiteChangeRegionDialog.vue b/dashboard/src/views/site/SiteChangeRegionDialog.vue index 13703b7..021673c 100644 --- a/dashboard/src/views/site/SiteChangeRegionDialog.vue +++ b/dashboard/src/views/site/SiteChangeRegionDialog.vue @@ -73,7 +73,7 @@ - - diff --git a/dashboard/src2/components/AppSidebar.vue b/dashboard/src2/components/AppSidebar.vue deleted file mode 100644 index d0098d6..0000000 --- a/dashboard/src2/components/AppSidebar.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/BuyPrepaidCreditsForm.vue b/dashboard/src2/components/BuyPrepaidCreditsForm.vue deleted file mode 100644 index 503aec6..0000000 --- a/dashboard/src2/components/BuyPrepaidCreditsForm.vue +++ /dev/null @@ -1,243 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/ClickToCopyField.vue b/dashboard/src2/components/ClickToCopyField.vue deleted file mode 100644 index cdd8809..0000000 --- a/dashboard/src2/components/ClickToCopyField.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/StripeCard.vue b/dashboard/src2/components/StripeCard.vue deleted file mode 100644 index 9bf38e9..0000000 --- a/dashboard/src2/components/StripeCard.vue +++ /dev/null @@ -1,346 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/SwitchTeamDialog.vue b/dashboard/src2/components/SwitchTeamDialog.vue deleted file mode 100644 index 986f05b..0000000 --- a/dashboard/src2/components/SwitchTeamDialog.vue +++ /dev/null @@ -1,93 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/components/auth/Configure2FA.vue b/dashboard/src2/components/auth/Configure2FA.vue deleted file mode 100644 index 31c0d18..0000000 --- a/dashboard/src2/components/auth/Configure2FA.vue +++ /dev/null @@ -1,212 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/billing/AddPrepaidCreditsDialog.vue b/dashboard/src2/components/billing/AddPrepaidCreditsDialog.vue deleted file mode 100644 index 67ee979..0000000 --- a/dashboard/src2/components/billing/AddPrepaidCreditsDialog.vue +++ /dev/null @@ -1,37 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/components/billing/BillingDetailsDialog.vue b/dashboard/src2/components/billing/BillingDetailsDialog.vue deleted file mode 100644 index d0bf9ba..0000000 --- a/dashboard/src2/components/billing/BillingDetailsDialog.vue +++ /dev/null @@ -1,37 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/components/marketplace/ChangeAppBranchDialog.vue b/dashboard/src2/components/marketplace/ChangeAppBranchDialog.vue deleted file mode 100644 index bcb1081..0000000 --- a/dashboard/src2/components/marketplace/ChangeAppBranchDialog.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/ActivateWebhookDialog.vue b/dashboard/src2/components/settings/ActivateWebhookDialog.vue deleted file mode 100644 index 806f729..0000000 --- a/dashboard/src2/components/settings/ActivateWebhookDialog.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/AddNewWebhookDialog.vue b/dashboard/src2/components/settings/AddNewWebhookDialog.vue deleted file mode 100644 index a0b56fb..0000000 --- a/dashboard/src2/components/settings/AddNewWebhookDialog.vue +++ /dev/null @@ -1,166 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/DeveloperSettings.vue b/dashboard/src2/components/settings/DeveloperSettings.vue deleted file mode 100644 index 71d8aef..0000000 --- a/dashboard/src2/components/settings/DeveloperSettings.vue +++ /dev/null @@ -1,474 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/EditWebhookDialog.vue b/dashboard/src2/components/settings/EditWebhookDialog.vue deleted file mode 100644 index f8b9142..0000000 --- a/dashboard/src2/components/settings/EditWebhookDialog.vue +++ /dev/null @@ -1,189 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/InviteTeamMemberDialog.vue b/dashboard/src2/components/settings/InviteTeamMemberDialog.vue deleted file mode 100644 index 33250c1..0000000 --- a/dashboard/src2/components/settings/InviteTeamMemberDialog.vue +++ /dev/null @@ -1,133 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/RoleConfigureDialog.vue b/dashboard/src2/components/settings/RoleConfigureDialog.vue deleted file mode 100644 index 2944477..0000000 --- a/dashboard/src2/components/settings/RoleConfigureDialog.vue +++ /dev/null @@ -1,318 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/RoleList.vue b/dashboard/src2/components/settings/RoleList.vue deleted file mode 100644 index f5c5797..0000000 --- a/dashboard/src2/components/settings/RoleList.vue +++ /dev/null @@ -1,136 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/RolePermissions.vue b/dashboard/src2/components/settings/RolePermissions.vue deleted file mode 100644 index 716bcb0..0000000 --- a/dashboard/src2/components/settings/RolePermissions.vue +++ /dev/null @@ -1,233 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/SettingsPermissions.vue b/dashboard/src2/components/settings/SettingsPermissions.vue deleted file mode 100644 index 0c7b422..0000000 --- a/dashboard/src2/components/settings/SettingsPermissions.vue +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/TeamSettings.vue b/dashboard/src2/components/settings/TeamSettings.vue deleted file mode 100644 index 29b2de4..0000000 --- a/dashboard/src2/components/settings/TeamSettings.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/WebhookAttemptsDialog.vue b/dashboard/src2/components/settings/WebhookAttemptsDialog.vue deleted file mode 100644 index 715a475..0000000 --- a/dashboard/src2/components/settings/WebhookAttemptsDialog.vue +++ /dev/null @@ -1,122 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/profile/AccountPartner.vue b/dashboard/src2/components/settings/profile/AccountPartner.vue deleted file mode 100644 index 2ebd59e..0000000 --- a/dashboard/src2/components/settings/profile/AccountPartner.vue +++ /dev/null @@ -1,190 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/profile/AccountProfile.vue b/dashboard/src2/components/settings/profile/AccountProfile.vue deleted file mode 100644 index 5b40115..0000000 --- a/dashboard/src2/components/settings/profile/AccountProfile.vue +++ /dev/null @@ -1,444 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/profile/AccountReferral.vue b/dashboard/src2/components/settings/profile/AccountReferral.vue deleted file mode 100644 index d6d8c92..0000000 --- a/dashboard/src2/components/settings/profile/AccountReferral.vue +++ /dev/null @@ -1,37 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/profile/ProfileSettings.vue b/dashboard/src2/components/settings/profile/ProfileSettings.vue deleted file mode 100644 index aa0d575..0000000 --- a/dashboard/src2/components/settings/profile/ProfileSettings.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/components/settings/profile/TFADialog.vue b/dashboard/src2/components/settings/profile/TFADialog.vue deleted file mode 100644 index c60ec9b..0000000 --- a/dashboard/src2/components/settings/profile/TFADialog.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/data/notifications.js b/dashboard/src2/data/notifications.js deleted file mode 100644 index beb0ff0..0000000 --- a/dashboard/src2/data/notifications.js +++ /dev/null @@ -1,7 +0,0 @@ -import { createResource } from 'jingrow-ui'; - -export const unreadNotificationsCount = createResource({ - cache: 'Unread Notifications Count', - url: 'jcloud.api.notifications.get_unread_count', - initialData: 0 -}); diff --git a/dashboard/src2/main.js b/dashboard/src2/main.js deleted file mode 100644 index d1572ca..0000000 --- a/dashboard/src2/main.js +++ /dev/null @@ -1,168 +0,0 @@ -import { createApp } from 'vue'; -import { - setConfig, - jingrowRequest, - pageMetaPlugin, - resourcesPlugin, -} from 'jingrow-ui'; -import App from './App.vue'; -import router from './router'; -import { initSocket } from './socket'; -import { subscribeToJobUpdates } from './utils/agentJob'; -import { fetchPlans } from './data/plans.js'; -import * as Sentry from '@sentry/vue'; -import { session } from './data/session.js'; -import { unreadNotificationsCount } from './data/notifications.js'; -import './vendor/posthog.js'; - -const request = (options) => { - const _options = options || {}; - _options.headers = options.headers || {}; - const currentTeam = - localStorage.getItem('current_team') || window.default_team; - if (currentTeam) { - _options.headers['X-Jcloud-Team'] = currentTeam; - } - return jingrowRequest(_options); -}; -setConfig('resourceFetcher', request); -setConfig('defaultListUrl', 'jcloud.api.client.get_list'); -setConfig('defaultDocGetUrl', 'jcloud.api.client.get'); -setConfig('defaultDocInsertUrl', 'jcloud.api.client.insert'); -setConfig('defaultRunDocMethodUrl', 'jcloud.api.client.run_pg_method'); -setConfig('defaultDocUpdateUrl', 'jcloud.api.client.set_value'); -setConfig('defaultDocDeleteUrl', 'jcloud.api.client.delete'); - -let app; -let socket; - -getInitialData().then(() => { - app = createApp(App); - app.use(router); - app.use(resourcesPlugin); - app.use(pageMetaPlugin); - - socket = initSocket(); - app.config.globalProperties.$socket = socket; - window.$socket = socket; - subscribeToJobUpdates(socket); - if (session.isLoggedIn) { - fetchPlans(); - session.roles.fetch(); - unreadNotificationsCount.fetch(); - } - - if (window.jcloud_dashboard_sentry_dsn.includes('https://')) { - Sentry.init({ - app, - dsn: window.jcloud_dashboard_sentry_dsn, - integrations: [ - Sentry.browserTracingIntegration({ router }), - Sentry.replayIntegration({ - maskAllText: false, - blockAllMedia: false, - }), - Sentry.thirdPartyErrorFilterIntegration({ - // Specify the application keys that you specified in the Sentry bundler plugin - filterKeys: ['jcloud-dashboard'], - - // Defines how to handle errors that contain third party stack frames. - // Possible values are: - // - 'drop-error-if-contains-third-party-frames' - // - 'drop-error-if-exclusively-contains-third-party-frames' - // - 'apply-tag-if-contains-third-party-frames' - // - 'apply-tag-if-exclusively-contains-third-party-frames' - behaviour: 'apply-tag-if-contains-third-party-frames', - }), - ], - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1.0, - beforeSend(event, hint) { - const ignoreErrors = [ - /api\/action\/jcloud.api.client/, - /dynamically imported module/, - /NetworkError when attempting to fetch resource/, - /Failed to fetch/, - /Load failed/, - /jingrow is not defined/, - /Cannot read properties of undefined \(reading 'exc_type'\)/, - /Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing/, - /Importing a module script failed./, - /o is undefined/, - /undefined is not an object \(evaluating 'o.exc_type'\)/, - /e is not defined/, - /Cannot set property ethereum of # which has only a getter/, - /Can't find variable: ResizeObserver/, - /Method not found/, - /Menu caption text is required/, - /Internal error opening backing store for indexedDB.open/, - ]; - const ignoreErrorTypes = [ - 'BuildValidationError', - 'ValidationError', - 'PermissionError', - 'SecurityException', - 'AAAARecordExists', - 'AuthenticationError', - 'RateLimitExceededError', - 'InsufficientSpaceOnServer', - 'ConflictingDNSRecord', - 'MultipleARecords', - ]; - const error = hint.originalException; - - if ( - error?.name === 'DashboardError' || - ignoreErrorTypes.includes(error?.exc_type) || - (error?.message && ignoreErrors.some((re) => re.test(error.message))) - ) { - return null; - } - - return event; - }, - logErrors: true, - }); - - Sentry.setTag('team', localStorage.getItem('current_team')); - } - - if ( - window.jcloud_frontend_posthog_project_id && - window.jcloud_frontend_posthog_host && - window.posthog - ) { - window.posthog.init(window.jcloud_frontend_posthog_project_id, { - api_host: window.jcloud_frontend_posthog_host, - person_profiles: 'identified_only', - autocapture: false, - disable_session_recording: true, - session_recording: { - maskAllInputs: true, - }, - }); - } else { - // unset posthog if not configured - window.posthog = undefined; - } - - importGlobals().then(() => { - app.mount('#app'); - }); -}); - -function getInitialData() { - if (import.meta.env.DEV) { - return jingrowRequest({ - url: '/api/action/jcloud.www.dashboard.get_context_for_dev', - }).then((values) => Object.assign(window, values)); - } else { - return Promise.resolve(); - } -} - -function importGlobals() { - return import('./globals.ts').then((globals) => { - app.use(globals.default); - }); -} diff --git a/dashboard/src2/objects/jsite_server.js b/dashboard/src2/objects/jsite_server.js deleted file mode 100644 index 395e411..0000000 --- a/dashboard/src2/objects/jsite_server.js +++ /dev/null @@ -1,333 +0,0 @@ -import { defineAsyncComponent, h } from 'vue'; -import LucideServer from '~icons/lucide/server'; -import { getTeam } from '../data/team'; -import router from '../router'; -import { icon } from '../utils/components'; -import { duration, planTitle, userCurrency } from '../utils/format'; -import { trialDays } from '../utils/site'; -import { getJobsTab } from './common/jobs'; -import { tagTab } from './common/tags'; - -export default { - pagetype: 'Jsite Server', - whitelistedMethods: { - reboot: 'reboot', - rename: 'rename', - dropServer: 'drop_server', - addTag: 'add_resource_tag', - removeTag: 'remove_resource_tag' - }, - list: { - route: '/jsite-servers', - title: '服务器', - fields: [ - 'name', - 'title', - 'status', - 'region', - 'cpu', - 'memory', - 'disk_size', - 'public_ip', - 'private_ip', - 'end_date', - 'bandwidth', - 'team', - 'instance_id', - 'order_id', - 'planid', - 'image_id', - 'system' - ], - filterControls() { - return [ - { - type: 'select', - label: '状态', - fieldname: 'status', - options: [ - { label: '', value: '' }, - { label: '准备中', value: 'Pending' }, - { label: '启动中', value: 'Starting' }, - { label: '运行中', value: 'Running' }, - { label: '停止中', value: 'Stopping' }, - { label: '已停止', value: 'Stopped' }, - { label: '重置中', value: 'Resetting' }, - { label: '升级中', value: 'Upgrading' }, - { label: '已禁用', value: 'Disabled' } - ] - }, - { - type: 'select', - label: '区域', - fieldname: 'region', - options: [ - { label: '', value: '' }, - { label: '华北1(青岛)', value: 'cn-qingdao' }, - { label: '华北2(北京)', value: 'cn-beijing' }, - { label: '华北3(张家口)', value: 'cn-zhangjiakou' }, - { label: '华北5(呼和浩特)', value: 'cn-huhehaote' }, - { label: '华东1(杭州)', value: 'cn-hangzhou' }, - { label: '华东2(上海)', value: 'cn-shanghai' }, - { label: '华南1(深圳)', value: 'cn-shenzhen' }, - { label: '华南2(河源)', value: 'cn-heyuan' }, - { label: '西南1(成都)', value: 'cn-chengdu' }, - { label: '华南3(广州)', value: 'cn-guangzhou' }, - { label: '华北6(乌兰察布)', value: 'cn-wulanchabu' }, - { label: '华东5(南京)', value: 'cn-nanjing' }, - { label: '华东6(福州)', value: 'cn-fuzhou' }, - { label: '华中1(武汉)', value: 'cn-wuhan-lr' }, - { label: '中国香港', value: 'cn-hongkong' }, - { label: '新加坡', value: 'ap-southeast-1' }, - { label: '马来西亚(吉隆坡)', value: 'ap-southeast-3' }, - { label: '印度尼西亚(雅加达)', value: 'ap-southeast-5' }, - { label: '日本(东京)', value: 'ap-northeast-1' }, - { label: '美国(硅谷)', value: 'us-west-1' }, - { label: '美国(弗吉尼亚)', value: 'us-east-1' }, - { label: '德国(法兰克福)', value: 'eu-central-1' }, - { label: '英国(伦敦)', value: 'eu-west-1' }, - { label: '菲律宾(马尼拉)', value: 'ap-southeast-6' }, - { label: '泰国(曼谷)', value: 'ap-southeast-7' }, - { label: '韩国(首尔)', value: 'ap-northeast-2' } - ] - } - ]; - }, - orderBy: 'creation desc', - searchField: 'title', - columns: [ - { - label: '服务器', - fieldname: 'name', - width: 1.5, - class: 'font-medium', - format(value, row) { - return row.title || value; - } - }, - { - label: '状态', - fieldname: 'status', - type: 'Badge', - width: 0.8, - format(value) { - const statusMap = { - 'Pending': '准备中', - 'Starting': '启动中', - 'Running': '运行中', - 'Stopping': '停止中', - 'Stopped': '已停止', - 'Resetting': '重置中', - 'Upgrading': '升级中', - 'Disabled': '已禁用' - }; - return statusMap[value] || value; - } - }, - { - label: '配置', - fieldname: 'cpu', - format(value, row) { - const cpu = row.cpu || '未知'; - const memory = row.memory || '未知'; - const disk = row.disk_size || '未知'; - return `${cpu}核/${memory}GB/${disk}GB`; - } - }, - { - label: '公网IP', - fieldname: 'public_ip', - format(value) { - return value || '-'; - } - }, - { - label: '区域', - fieldname: 'region', - format(value) { - if (!value) return '-'; - - // 区域ID到中文名称的映射表 - const regionMap = { - 'cn-qingdao': '华北1(青岛)', - 'cn-beijing': '华北2(北京)', - 'cn-zhangjiakou': '华北3(张家口)', - 'cn-huhehaote': '华北5(呼和浩特)', - 'cn-hangzhou': '华东1(杭州)', - 'cn-shanghai': '华东2(上海)', - 'cn-shenzhen': '华南1(深圳)', - 'cn-heyuan': '华南2(河源)', - 'cn-chengdu': '西南1(成都)', - 'cn-guangzhou': '华南3(广州)', - 'cn-wulanchabu': '华北6(乌兰察布)', - 'cn-nanjing': '华东5(南京)', - 'cn-fuzhou': '华东6(福州)', - 'cn-wuhan-lr': '华中1(武汉)', - 'cn-hongkong': '中国香港', - 'ap-southeast-1': '新加坡', - 'ap-southeast-3': '马来西亚(吉隆坡)', - 'ap-southeast-5': '印度尼西亚(雅加达)', - 'ap-northeast-1': '日本(东京)', - 'us-west-1': '美国(硅谷)', - 'us-east-1': '美国(弗吉尼亚)', - 'eu-central-1': '德国(法兰克福)', - 'eu-west-1': '英国(伦敦)', - 'ap-southeast-6': '菲律宾(马尼拉)', - 'ap-southeast-7': '泰国(曼谷)', - 'ap-northeast-2': '韩国(首尔)' - }; - - // 如果是RegionId,返回对应的中文名称;如果已经是中文名称,直接返回 - return regionMap[value] || value; - } - }, - { - label: '到期时间', - fieldname: 'end_date', - format(value) { - if (!value) return '-'; - return value; - } - } - ], - primaryAction({ listResource: jsiteServers }) { - return { - label: '新建服务器', - variant: 'solid', - slots: { - prefix: icon('plus') - }, - onClick() { - router.push('/jsite-servers/new'); - } - }; - }, - statusBadge({ documentResource: jsiteServer }) { - const status = jsiteServer.pg?.status; - const statusMap = { - 'Pending': '准备中', - 'Starting': '启动中', - 'Running': '运行中', - 'Stopping': '停止中', - 'Stopped': '已停止', - 'Resetting': '重置中', - 'Upgrading': '升级中', - 'Disabled': '已禁用' - }; - return { - label: statusMap[status] || status - }; - }, - breadcrumbs({ documentResource: jsiteServer }) { - return [ - { - label: '服务器', - route: '/jsite-servers' - }, - { - label: jsiteServer.pg?.title || jsiteServer.pg?.name, - route: `/jsite-servers/${jsiteServer.pg?.name}` - } - ]; - }, - actions({ documentResource: jsiteServer }) { - if (!jsiteServer) return []; - - const actions = [ - { - label: '重启', - icon: 'refresh-cw', - onClick() { - jsiteServer.reboot.submit(); - }, - condition: () => jsiteServer.pg?.status === 'Running' - }, - { - label: '重命名', - icon: 'edit-3', - onClick() { - jsiteServer.rename.submit(); - } - }, - { - label: '删除', - icon: 'trash-2', - onClick() { - jsiteServer.dropServer.submit(); - }, - condition: () => jsiteServer.pg?.status !== 'Running' - } - ]; - return actions.filter(action => !action.condition || action.condition()); - } - }, - detail: { - route: '/jsite-servers/:name', - title: '服务器详细信息', - tabs: [ - { - label: '概览', - route: '', - type: 'Component', - component: defineAsyncComponent(() => import('../components/JsiteServerOverview.vue')), - props: jsiteServer => { - return { server: jsiteServer.pg?.name }; - } - }, - { - label: '防火墙', - route: 'firewall', - type: 'Component', - component: defineAsyncComponent(() => import('../components/JsiteServerFirewallRules.vue')), - props: jsiteServer => { - return { server: jsiteServer.pg?.name }; - } - } - ], - fields: [ - { - label: '基本信息', - fields: [ - 'title', - 'status', - 'region', - 'instance_id', - 'order_id' - ] - }, - { - label: '服务器配置', - fields: [ - 'cpu', - 'memory', - 'disk_size', - 'bandwidth', - 'public_ip', - 'image_id', - 'planid', - 'system' - ] - }, - { - label: 'SSH连接', - fields: [ - 'ssh_user', - 'ssh_port', - 'password', - 'key_pair_name', - 'private_key' - ] - }, - { - label: '其他信息', - fields: [ - 'end_date', - 'period' - ] - } - ], - actions({ documentResource: jsiteServer }) { - return []; - } - } -}; \ No newline at end of file diff --git a/dashboard/src2/pages/Billing.vue b/dashboard/src2/pages/Billing.vue deleted file mode 100644 index 00dcddc..0000000 --- a/dashboard/src2/pages/Billing.vue +++ /dev/null @@ -1,68 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/pages/BillingBalances.vue b/dashboard/src2/pages/BillingBalances.vue deleted file mode 100644 index f321d68..0000000 --- a/dashboard/src2/pages/BillingBalances.vue +++ /dev/null @@ -1,205 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/pages/BillingMarketplacePayouts.vue b/dashboard/src2/pages/BillingMarketplacePayouts.vue deleted file mode 100644 index 43b6e82..0000000 --- a/dashboard/src2/pages/BillingMarketplacePayouts.vue +++ /dev/null @@ -1,105 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/pages/BillingOrders.vue b/dashboard/src2/pages/BillingOrders.vue deleted file mode 100644 index 344008f..0000000 --- a/dashboard/src2/pages/BillingOrders.vue +++ /dev/null @@ -1,340 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/pages/BillingOverview.vue b/dashboard/src2/pages/BillingOverview.vue deleted file mode 100644 index 8d0f431..0000000 --- a/dashboard/src2/pages/BillingOverview.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - \ No newline at end of file diff --git a/dashboard/src2/pages/BillingPaymentMethods.vue b/dashboard/src2/pages/BillingPaymentMethods.vue deleted file mode 100644 index bc23e24..0000000 --- a/dashboard/src2/pages/BillingPaymentMethods.vue +++ /dev/null @@ -1,209 +0,0 @@ - - \ No newline at end of file diff --git a/dashboard/src2/utils/dayjs.js b/dashboard/src2/utils/dayjs.js deleted file mode 100644 index 77ac9fa..0000000 --- a/dashboard/src2/utils/dayjs.js +++ /dev/null @@ -1,30 +0,0 @@ -import dayjs from 'dayjs/esm'; -import relativeTime from 'dayjs/esm/plugin/relativeTime'; -import localizedFormat from 'dayjs/esm/plugin/localizedFormat'; -import updateLocale from 'dayjs/esm/plugin/updateLocale'; -import isToday from 'dayjs/esm/plugin/isToday'; -import duration from 'dayjs/esm/plugin/duration'; -import utc from 'dayjs/esm/plugin/utc'; -import timezone from 'dayjs/esm/plugin/timezone'; -import advancedFormat from 'dayjs/plugin/advancedFormat'; - -dayjs.extend(updateLocale); -dayjs.extend(relativeTime); -dayjs.extend(localizedFormat); -dayjs.extend(isToday); -dayjs.extend(duration); -dayjs.extend(utc); -dayjs.extend(timezone); -dayjs.extend(advancedFormat); - -export function dayjsLocal(dateTimeString) { - let localTimezone = dayjs.tz.guess(); - // dates are stored in Asia/Calcutta timezone on the server - return dayjs.tz(dateTimeString, 'Asia/Calcutta').tz(localTimezone); -} - -export function dayjsIST(dateTimeString) { - return dayjs(dateTimeString).tz('Asia/Calcutta'); -} - -export default dayjs; diff --git a/dashboard/src2/utils/toast.js b/dashboard/src2/utils/toast.js deleted file mode 100644 index ce2efc8..0000000 --- a/dashboard/src2/utils/toast.js +++ /dev/null @@ -1,30 +0,0 @@ -import { toast } from 'vue-sonner'; -import { h } from 'vue'; - -export function showErrorToast(error) { - if (!error) { - toast.error('发生了未知错误'); - return; - } - - let errorMessage = error.messages?.length - ? error.messages.join('\n') - : (error.message || '发生了未知错误'); - toast.error(errorMessage); -} - -export function getToastErrorMessage(error, fallbackMessage = '发生了错误') { - if (!error) return fallbackMessage; - - try { - const errorMessage = error.messages?.length - ? error.messages.join('
') - : (error.message || fallbackMessage); - - return h('div', { - innerHTML: errorMessage, - }); - } catch (e) { - return fallbackMessage; - } -} \ No newline at end of file diff --git a/dashboard/tailwind.config.cjs b/dashboard/tailwind.config.cjs index b05c606..fc5ea6e 100644 --- a/dashboard/tailwind.config.cjs +++ b/dashboard/tailwind.config.cjs @@ -6,7 +6,7 @@ module.exports = { './public/index.html', './src/**/*.html', './src/**/*.vue', - './src2/**/*.vue', + './src/**/*.vue', './src/assets/*.css', './node_modules/jingrow-ui/src/components/**/*.{vue,js,ts}' ], diff --git a/dashboard/vite-plugin-translate.mjs b/dashboard/vite-plugin-translate.mjs new file mode 100644 index 0000000..7c9c08d --- /dev/null +++ b/dashboard/vite-plugin-translate.mjs @@ -0,0 +1,765 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// 常量配置 +const CONSTANTS = { + VUE_TEMPLATE_TAG: '', + VUE_TEMPLATE_TAG_LEN: 9, + VUE_TEMPLATE_CLOSE_TAG_LEN: 11, + VUE_TEMPLATE_MODULE: '?vue&type=template', + VUE_SCRIPT_MODULE: '?vue&type=script', + VUE_STYLE_MODULE: '?vue&type=style', + FILE_EXTENSIONS: /\.(vue|js|ts|jsx|tsx)(\?.*)?$/, + NON_ASCII_REGEX: /[^\x00-\x7F]/, +}; + +/** + * 查找平衡括号的结束位置 + * @param {string} code - 代码字符串 + * @param {number} startPos - 起始位置(开括号之后) + * @param {string} openChar - 开括号字符 + * @param {string} closeChar - 闭括号字符 + * @returns {number} 闭括号的位置,未找到返回 -1 + */ +/** + * 查找平衡括号的结束位置,正确处理字符串字面量中的括号 + * @param {string} code - 代码字符串 + * @param {number} startPos - 起始位置(开括号之后) + * @param {string} openChar - 开括号字符 + * @param {string} closeChar - 闭括号字符 + * @returns {number} 闭括号的位置,未找到返回 -1 + */ +function findBalancedBracket(code, startPos, openChar = '(', closeChar = ')') { + let depth = 1; + let pos = startPos; + let inString = false; + let stringChar = null; + + while (pos < code.length && depth > 0) { + const char = code[pos]; + + // 处理字符串字面量 + if (!inString && (char === '"' || char === "'")) { + inString = true; + stringChar = char; + } else if (inString && char === stringChar) { + // 检查是否是转义的引号:计算连续的反斜杠数量 + let backslashCount = 0; + let checkPos = pos - 1; + while (checkPos >= 0 && code[checkPos] === '\\') { + backslashCount++; + checkPos--; + } + // 如果反斜杠数量是偶数,则引号未转义,字符串结束 + if (backslashCount % 2 === 0) { + inString = false; + stringChar = null; + } + } + + // 只在非字符串中计算括号深度 + if (!inString) { + if (char === openChar) depth++; + else if (char === closeChar) { + depth--; + if (depth === 0) return pos; + } + } + + pos++; + } + + return -1; +} + +/** + * 处理参数对象中的嵌套 $t() 调用 + * @param {string} paramsCode - 参数部分的代码 + * @param {object} translations - 翻译字典 + * @param {RegExp} pattern - 匹配 $t() 调用的正则表达式 + * @returns {string} 处理后的参数代码 + */ +function processNestedTranslations(paramsCode, translations, pattern) { + return paramsCode.replace(pattern, (match, ...args) => { + // String.replace 回调的参数:match, ...captureGroups, index, input + // 需要根据捕获组数量判断格式 + // 格式1: /([a-zA-Z_$][a-zA-Z0-9_$]*\.)?[\$]t\((['"])([^'"]+)\2\)/g -> 3个捕获组 + // 格式2: /\$t\((['"])([^'"]+)\1\)/g -> 2个捕获组 + // 格式3: /\{\{\s*[\$]t\s*\(\s*(['"])([^'"]+)\1\s*\)\s*\}\}/g -> 2个捕获组 + + // 获取捕获组数量(排除最后两个参数:index 和 input) + const captureGroups = args.slice(0, -2); + let ctxPrefix = ''; + let quote; + let key; + + if (captureGroups.length === 3) { + // 格式1:带前缀的 (ctxPrefix, quote, key) + ctxPrefix = captureGroups[0] || ''; + quote = captureGroups[1]; + key = unescapeString(captureGroups[2]); + } else if (captureGroups.length === 2) { + // 格式2或3:不带前缀的 (quote, key) + quote = captureGroups[0]; + key = unescapeString(captureGroups[1]); + } else { + // 无法解析,返回原匹配 + return match; + } + + const translation = translations[key]; + return translation + ? `${ctxPrefix}$t(${quote}${escapeString(translation, quote)}${quote})` + : match; + }); +} + +/** + * Vite 插件:构建时直接读取 CSV,替换代码中的 $t('xxx') 为翻译文本 + * @param {object} options - 插件选项 + * @param {string} options.locale - 目标语言代码 + * @param {string} options.translationsPath - 翻译文件路径(相对于插件目录) + * @param {string} options.defaultLocale - 默认语言代码 + */ +function vitePluginTranslate(options = {}) { + const locale = options.locale || 'en'; + const defaultLocale = options.defaultLocale || 'en'; + const translationsPath = options.translationsPath || '../jcloud/translations'; + + // 在插件初始化时立即加载翻译数据,而不是等到 configResolved + let translations = {}; + if (locale && locale !== defaultLocale) { + const csvPath = path.resolve(__dirname, `${translationsPath}/${locale}.csv`); + if (fs.existsSync(csvPath)) { + try { + translations = parseCSV(csvPath); + } catch (error) { + console.warn(`[vite-plugin-translate] Failed to load translations from ${csvPath}:`, error.message); + } + } + } + + return { + name: 'vite-plugin-translate', + enforce: 'pre', + configResolved(config) { + // 确保翻译数据已加载(双重检查) + if (locale && locale !== defaultLocale && Object.keys(translations).length === 0) { + const csvPath = path.resolve(__dirname, `${translationsPath}/${locale}.csv`); + if (fs.existsSync(csvPath)) { + try { + translations = parseCSV(csvPath); + } catch (error) { + console.warn(`[vite-plugin-translate] Failed to load translations from ${csvPath}:`, error.message); + } + } + } + }, + transform(code, id) { + if (id.endsWith('index.html')) { + const lang = locale; + return { + code: code.replace(/]*)>/i, ``), + map: null + }; + } + + const fileMatches = CONSTANTS.FILE_EXTENSIONS.test(id); + if (!fileMatches || locale === defaultLocale || Object.keys(translations).length === 0) { + return null; + } + + const originalCode = code; + const isVueFile = id.includes('.vue') && !id.includes(CONSTANTS.VUE_STYLE_MODULE); + + if (isVueFile) { + const isTemplateModule = id.includes(CONSTANTS.VUE_TEMPLATE_MODULE); + const isScriptModule = id.includes(CONSTANTS.VUE_SCRIPT_MODULE); + + if (isTemplateModule) { + // Vue 编译后的模板模块 + code = replaceInterpolations(code, translations); + code = replaceCompiledFormat(code, translations); + code = replaceAttributeBindings(code, translations); + } else if (isScriptModule) { + // Vue 编译后的 script 模块 - 处理 this.$t() 和 $t() 调用 + // 注意:t() 函数调用已在原始 Vue SFC 文件中处理,编译后应该已经是翻译后的字符串 + code = code.replace( + /(?:this\.)?\$t\((['"])((?:\\.|(?!\1).)*)\1\)/g, + (match, quote, key) => { + const unescapedKey = unescapeString(key); + const translation = translations[unescapedKey]; + if (translation) { + return quote + escapeString(translation, quote) + quote; + } + return match; + } + ); + } else { + // 原始 Vue SFC 文件 + code = processVueSFC(code, translations); + } + } else { + // 非 Vue 文件 + code = code.replace( + /\bt\((['"])((?:\\.|(?!\1).)*)\1\)/g, + (match, quote, key) => { + const unescapedKey = unescapeString(key); + const translation = translations[unescapedKey]; + return translation ? quote + escapeString(translation, quote) + quote : match; + } + ); + } + + return code !== originalCode ? { code, map: null } : null; + } + }; +} + +/** + * 处理 Vue SFC 文件 + */ +function processVueSFC(code, translations) { + // 处理 ,处理嵌套 + let depth = 1; + let pos = templateRegex.lastIndex; + let templateContent = ''; + + while (depth > 0 && pos < code.length) { + const nextOpen = code.indexOf(CONSTANTS.VUE_TEMPLATE_TAG, pos); + const nextClose = code.indexOf(CONSTANTS.VUE_TEMPLATE_CLOSE_TAG, pos); + + if (nextClose === -1) break; + + if (nextOpen !== -1 && nextOpen < nextClose) { + depth++; + pos = nextOpen + CONSTANTS.VUE_TEMPLATE_TAG_LEN; + } else { + depth--; + if (depth === 0) { + templateContent = code.substring(templateRegex.lastIndex, nextClose); + break; + } + pos = nextClose + CONSTANTS.VUE_TEMPLATE_CLOSE_TAG_LEN; + } + } + + if (depth === 0 && templateContent) { + let replacedContent = templateContent; + // 先处理属性绑定(包括带参数的),避免 replaceInterpolations 误处理 + replacedContent = replaceAttributeBindings(replacedContent, translations); + // 处理属性绑定中的数组字面量,如 :items="[{ label: $t('key'), ... }]" + replacedContent = replaceAttributeArrayLiterals(replacedContent, translations); + // 处理属性绑定中的对象字面量,如 :options="{ title: $t('key'), ... }" + replacedContent = replaceAttributeObjectLiterals(replacedContent, translations); + // 最后处理插值表达式 + replacedContent = replaceInterpolations(replacedContent, translations); + + code = code.substring(0, startIndex) + openTag + replacedContent + CONSTANTS.VUE_TEMPLATE_CLOSE_TAG + + code.substring(startIndex + openTag.length + templateContent.length + CONSTANTS.VUE_TEMPLATE_CLOSE_TAG_LEN); + break; + } + } + + // 处理