设置菜单及页面实现多语言支持

This commit is contained in:
jingrow 2025-12-29 16:03:13 +08:00
parent d911a973eb
commit 369e89fc6d
13 changed files with 459 additions and 289 deletions

View File

@ -195,7 +195,7 @@ export default {
disabled: enforce2FA,
},
{
name: '设置',
name: this.$t('Settings'),
icon: () => h(Settings),
route: '/settings',
isActive: routeName.startsWith('Settings'),

View File

@ -5,11 +5,11 @@
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
>
<div class="flex items-center justify-between">
<div class="text-xl font-semibold">API 访问</div>
<div class="text-xl font-semibold">{{ $t('API Access') }}</div>
<Button @click="showCreateSecretDialog = true">{{
$team.pg?.user_info?.api_key
? '重新生成 API 密钥'
: '创建新的 API 密钥'
? $t('Regenerate API Key')
: $t('Create New API Key')
}}</Button>
</div>
<div v-if="$team.pg?.user_info?.api_key">
@ -19,18 +19,18 @@
/>
</div>
<div v-else class="pb-2 text-base text-gray-700">
您还没有 API 密钥点击上面的按钮创建一个
{{ $t("You don't have an API key yet. Click the button above to create one.") }}
</div>
<Dialog
v-model="showCreateSecretDialog"
:options="{
title: 'API 访问',
title: $t('API Access'),
size: 'xl',
actions: [
{
label: $team.pg.user_info.api_key
? '重新生成 API 密钥'
: '创建新的 API 密钥',
? $t('Regenerate API Key')
: $t('Create New API Key'),
variant: 'solid',
disabled: createSecret.data,
loading: createSecret.loading,
@ -44,7 +44,7 @@
<template #body-content>
<div v-if="createSecret.data">
<p class="text-base">
请立即复制 API 密钥您将无法再次查看它
{{ $t('Please copy the API key immediately. You will not be able to view it again!') }}
</p>
<label class="block pt-2">
<span class="mb-2 block text-sm leading-4 text-gray-700"
@ -60,9 +60,9 @@
</label>
</div>
<div v-else class="text-base text-gray-700">
API key API secret 可用于访问
{{ $t('API key and API secret can be used to access') }}
<a href="/docs/api" class="underline" target="_blank"
>今果 Jingrow API</a
>{{ $t('Jingrow API') }}</a
>.
</div>
</template>
@ -72,7 +72,7 @@
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
>
<div class="flex items-center justify-between">
<div class="text-xl font-semibold">SSH 密钥</div>
<div class="text-xl font-semibold">{{ $t('SSH Keys') }}</div>
</div>
<ObjectList :options="sshKeyListOptions" />
</div>
@ -81,7 +81,7 @@
class="mx-auto min-w-[48rem] max-w-3xl space-y-6 rounded-md border p-4"
>
<div class="flex items-center justify-between">
<div class="text-xl font-semibold">Webhooks</div>
<div class="text-xl font-semibold">{{ $t('Webhooks') }}</div>
</div>
<ObjectList :options="webhookListOptions" />
<AddNewWebhookDialog
@ -114,7 +114,7 @@
<script setup>
import { Badge, createResource } from 'jingrow-ui';
import { toast } from 'vue-sonner';
import { computed, h, onMounted, ref } from 'vue';
import { computed, h, onMounted, ref, getCurrentInstance } from 'vue';
import { confirmDialog, icon } from '../../utils/components';
import ObjectList from '../ObjectList.vue';
import { getTeam } from '../../data/team';
@ -127,6 +127,9 @@ import { useRouter } from 'vue-router';
import WebhookAttemptsDialog from './WebhookAttemptsDialog.vue';
import { session } from '../../data/session';
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
const $team = getTeam();
const router = useRouter();
let showCreateSecretDialog = ref(false);
@ -140,9 +143,9 @@ const createSecret = createResource({
url: 'jcloud.api.account.create_api_secret',
onSuccess() {
if ($team.pg.user_info.api_key) {
toast.success('API 密钥已成功重新生成');
toast.success($t('API key regenerated successfully'));
} else {
toast.success('API 密钥已成功创建');
toast.success($t('API key created successfully'));
}
}
});
@ -150,13 +153,13 @@ const createSecret = createResource({
const addSSHKey = createResource({
url: 'jcloud.api.client.insert',
onSuccess() {
toast.success('SSH 密钥已成功添加');
toast.success($t('SSH key added successfully'));
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'SSH 密钥无法添加'
: $t('Failed to add SSH key')
);
}
});
@ -164,13 +167,13 @@ const addSSHKey = createResource({
const makeKeyDefault = createResource({
url: 'jcloud.api.account.mark_key_as_default',
onSuccess() {
toast.success('SSH 密钥已成功更新');
toast.success($t('SSH key updated successfully'));
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'SSH 密钥无法设置为默认'
: $t('Failed to set SSH key as default')
);
}
});
@ -178,13 +181,13 @@ const makeKeyDefault = createResource({
const deleteSSHKey = createResource({
url: 'jcloud.api.client.delete',
onSuccess() {
toast.success('SSH 密钥已成功删除');
toast.success($t('SSH key deleted successfully'));
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'SSH 密钥无法删除'
: $t('Failed to delete SSH key')
);
}
});
@ -199,14 +202,14 @@ const sshKeyListOptions = computed(() => ({
},
columns: [
{
label: 'SSH 指纹',
label: $t('SSH Fingerprint'),
fieldname: 'ssh_fingerprint',
class: 'font-mono',
format: value => `SHA256:${value}`,
suffix(row) {
return row.is_default
? h(Badge, {
label: '默认',
label: $t('Default'),
theme: 'green',
class: 'ml-2'
})
@ -214,7 +217,7 @@ const sshKeyListOptions = computed(() => ({
}
},
{
label: '添加时间',
label: $t('Added Time'),
fieldname: 'creation',
width: 0.1,
format: value => date(value, 'll')
@ -222,7 +225,7 @@ const sshKeyListOptions = computed(() => ({
],
primaryAction({ listResource }) {
return {
label: '添加 SSH 密钥',
label: $t('Add SSH Key'),
slots: { prefix: icon('plus') },
onClick: () => renderAddNewKeyDialog(listResource)
};
@ -230,7 +233,7 @@ const sshKeyListOptions = computed(() => ({
rowActions({ row }) {
return [
{
label: '设为默认',
label: $t('Set as Default'),
condition: () => !row.is_default,
onClick() {
makeKeyDefault.submit({
@ -239,7 +242,7 @@ const sshKeyListOptions = computed(() => ({
}
},
{
label: '删除',
label: $t('Delete'),
onClick() {
deleteSSHKey.submit({
pagetype: 'User SSH Key',
@ -253,23 +256,22 @@ const sshKeyListOptions = computed(() => ({
function renderAddNewKeyDialog(listResource) {
confirmDialog({
title: '添加新 SSH 密钥',
message: '向您的账户添加新的 SSH 密钥',
title: $t('Add New SSH Key'),
message: $t('Add a new SSH key to your account'),
fields: [
{
label: 'SSH 密钥',
label: $t('SSH Key'),
fieldname: 'sshKey',
type: 'textarea',
placeholder:
"以 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', 或 'sk-ssh-ed25519@openssh.com' 开头",
placeholder: $t("Starts with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'"),
required: true
}
],
primaryAction: {
label: '添加 SSH 密钥',
label: $t('Add SSH Key'),
variant: 'solid',
onClick({ hide, values }) {
if (!values.sshKey) throw new Error('SSH 密钥是必填项');
if (!values.sshKey) throw new Error($t('SSH key is required'));
addSSHKey
.submit({
pg: {
@ -303,14 +305,14 @@ const webhookListResource = createResource({
const deleteWebhook = createResource({
url: 'jcloud.api.client.delete',
onSuccess() {
toast.success('Webhook 删除成功');
toast.success($t('Webhook deleted successfully'));
webhookListResource.reload();
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'Webhook 无法删除'
: $t('Failed to delete webhook')
);
}
});
@ -319,13 +321,13 @@ const webhookListOptions = computed(() => ({
data: () => webhookListResource.data,
columns: [
{
label: '端点',
label: $t('Endpoint'),
fieldname: 'endpoint',
width: 1,
format: value => value.substring(0, 50)
},
{
label: '状态',
label: $t('Status'),
fieldname: 'enabled',
width: 0.1,
type: 'Component',
@ -333,11 +335,11 @@ const webhookListOptions = computed(() => ({
component({ row }) {
return row.enabled
? h(Badge, {
label: '启用',
label: $t('Enabled'),
theme: 'green'
})
: h(Badge, {
label: '禁用',
label: $t('Disabled'),
theme: 'red'
});
}
@ -346,7 +348,7 @@ const webhookListOptions = computed(() => ({
rowActions({ row }) {
return [
{
label: '激活',
label: $t('Activate'),
condition: () => !Boolean(row.enabled),
onClick() {
selectedWebhook.value = row;
@ -354,14 +356,14 @@ const webhookListOptions = computed(() => ({
}
},
{
label: '禁用',
label: $t('Disable'),
condition: () => Boolean(row.enabled),
onClick: () => {
confirmDialog({
title: '禁用 Webhook',
message: `端点 - ${row.endpoint}<br>确定要禁用该 webhook 吗?<br>`,
title: $t('Disable Webhook'),
message: $t('Endpoint - {endpoint}<br>Are you sure you want to disable this webhook?<br>', { endpoint: row.endpoint }),
primaryAction: {
label: '禁用',
label: $t('Disable'),
variant: 'solid',
theme: 'red',
onClick({ hide }) {
@ -379,27 +381,27 @@ const webhookListOptions = computed(() => ({
}
},
{
label: '尝试',
label: $t('Attempts'),
onClick: () => {
selectedWebhook.value = row;
showWebhookAttempts.value = true;
}
},
{
label: '编辑',
label: $t('Edit'),
onClick() {
selectedWebhook.value = row;
showEditWebhookDialog.value = true;
}
},
{
label: '删除',
label: $t('Delete'),
onClick() {
confirmDialog({
title: '删除 Webhook',
message: `端点 - ${row.endpoint}<br>确定要删除该 webhook 吗?<br>`,
title: $t('Delete Webhook'),
message: $t('Endpoint - {endpoint}<br>Are you sure you want to delete this webhook?<br>', { endpoint: row.endpoint }),
primaryAction: {
label: '删除',
label: $t('Delete'),
variant: 'solid',
theme: 'red',
onClick({ hide }) {
@ -419,14 +421,14 @@ const webhookListOptions = computed(() => ({
},
primaryAction() {
return {
label: '添加 Webhook',
label: $t('Add Webhook'),
slots: { prefix: icon('plus') },
onClick: () => (showAddWebhookDialog.value = true)
};
},
secondaryAction() {
return {
label: '刷新',
label: $t('Refresh'),
icon: 'refresh-ccw',
onClick: () => webhookListResource.reload()
};
@ -436,14 +438,14 @@ const webhookListOptions = computed(() => ({
const disableWebhook = createResource({
url: 'jcloud.api.client.run_pg_method',
onSuccess() {
toast.success('Webhook 禁用成功');
toast.success($t('Webhook disabled successfully'));
webhookListResource.reload();
},
onError(err) {
toast.error(
err.messages.length
? err.messages.join('\n')
: 'Webhook 无法禁用'
: $t('Failed to disable webhook')
);
}
});

View File

@ -1,10 +1,10 @@
<template>
<Dialog
:options="{
title: '添加新成员',
title: $t('Add New Member'),
actions: [
{
label: '邀请成员',
label: $t('Invite Member'),
variant: 'solid',
onClick: inviteMember,
},
@ -14,7 +14,7 @@
>
<template #body-content>
<div class="space-y-4">
<FormControl label="用户名" v-model="username" />
<FormControl :label="$t('Username')" v-model="username" />
<div
v-if="$resources.roles.data?.length > 0"
class="flex items-center space-x-2"
@ -22,12 +22,12 @@
<FormControl
class="w-full"
type="autocomplete"
label="选择角色"
:label="$t('Select Role')"
:options="roleOptions"
v-model="selectedRole"
/>
<Button
label="添加"
:label="$t('Add')"
icon-left="plus"
:disabled="!selectedRole"
@click="addRole"
@ -109,12 +109,12 @@ export default {
},
inviteMember() {
if (!this.username) {
toast.error('用户名为必填项');
toast.error(this.$t('Username is required'));
return;
}
if (this.$team.inviteTeamMember.loading) return;
toast.success('已添加成员到团队', { duration: 2000 });
toast.success(this.$t('Member added to team'), { duration: 2000 });
this.show = false;
this.$team.inviteTeamMember.submit({
username: this.username,

View File

@ -8,11 +8,11 @@
<FTabs
:tabs="[
{
label: '成员',
label: $t('Members'),
value: 'members',
},
{
label: '设置',
label: $t('Settings'),
value: 'settings',
},
]"
@ -30,28 +30,28 @@
<div v-if="tab.value === 'members'" class="text-base">
<div class="my-4 flex gap-2">
<div class="flex-1">
<Autocomplete
:options="autoCompleteList"
v-model="member"
placeholder="选择要添加的成员"
/>
</div>
<Button
variant="solid"
label="添加成员"
:disabled="!member?.value"
:loading="$resources.role.addUser?.loading"
@click="() => addUser(member.value)"
<Autocomplete
:options="autoCompleteList"
v-model="member"
:placeholder="$t('Select member to add')"
/>
</div>
<div class="rounded border px-3">
<div class="mt-2 text-gray-600">成员</div>
<div
v-if="roleUsers.length === 0"
class="p-6 text-center text-gray-500"
>
<span>此角色尚未添加任何成员</span>
</div>
<Button
variant="solid"
:label="$t('Add Member')"
:disabled="!member?.value"
:loading="$resources.role.addUser?.loading"
@click="() => addUser(member.value)"
/>
</div>
<div class="rounded border px-3">
<div class="mt-2 text-gray-600">{{ $t('Members') }}</div>
<div
v-if="roleUsers.length === 0"
class="p-6 text-center text-gray-500"
>
<span>{{ $t('No members have been added to this role yet.') }}</span>
</div>
<div v-else class="flex flex-col divide-y">
<div
v-for="user in roleUsers"
@ -78,46 +78,46 @@
<Switch
class="ml-2"
v-model="adminAccess"
label="管理员权限"
description="授予成员类似团队所有者的权限。包括访问所有页面和设置。"
:label="$t('Admin Access')"
:description="$t('Grant members permissions similar to team owner. Includes access to all pages and settings.')"
/>
</div>
<div class="space-y-1 rounded border p-4">
<h2 class="mb-2 ml-2 font-semibold">页面访问权限</h2>
<h2 class="mb-2 ml-2 font-semibold">{{ $t('Page Access Permissions') }}</h2>
<Switch
v-model="allowBilling"
label="允许访问计费"
:label="$t('Allow Billing Access')"
:disabled="adminAccess"
/>
<Switch
v-model="allowApps"
label="允许访问应用"
:label="$t('Allow Apps Access')"
:disabled="adminAccess"
/>
<Switch
v-if="$team.pg.jerp_partner"
v-model="allowPartner"
label="允许访问合作伙伴"
:label="$t('Allow Partner Access')"
:disabled="adminAccess"
/>
<Switch
v-model="allowSiteCreation"
label="允许创建站点"
:label="$t('Allow Site Creation')"
:disabled="adminAccess"
/>
<Switch
v-model="allowBenchCreation"
label="允许创建站点分组"
:label="$t('Allow Release Group Creation')"
:disabled="adminAccess"
/>
<Switch
v-model="allowServerCreation"
label="允许创建服务器"
:label="$t('Allow Server Creation')"
:disabled="adminAccess"
/>
<Switch
v-model="allowWebhookConfiguration"
label="允许配置Webhook"
:label="$t('Allow Webhook Configuration')"
:disabled="adminAccess"
/>
</div>
@ -290,7 +290,7 @@ export default {
if (!user) return;
if (this.$resources.role.addUser.loading) return;
toast.success(`已添加 ${user}${this.role.title}`, { duration: 2000 });
toast.success(this.$t('Added {user} to {role}', { user, role: this.role.title }), { duration: 2000 });
this.member = {};
this.$resources.role.addUser.submit({ user });
//
@ -304,7 +304,7 @@ export default {
if (!user) return;
if (this.$resources.role.removeUser.loading) return;
toast.success(`已从 ${this.role.title} 中移除 ${user}`, { duration: 2000 });
toast.success(this.$t('Removed {user} from {role}', { user, role: this.role.title }), { duration: 2000 });
this.$resources.role.removeUser.submit({ user });
//
setTimeout(() => {

View File

@ -3,7 +3,7 @@
</template>
<script setup lang="jsx">
import { h, ref } from 'vue';
import { h, ref, getCurrentInstance } from 'vue';
import { toast } from 'vue-sonner';
import { FeatherIcon } from 'jingrow-ui';
import { icon, renderDialog, confirmDialog } from '../../utils/components';
@ -13,6 +13,9 @@ import router from '../../router';
import UserAvatarGroup from '../AvatarGroup.vue';
import { getToastErrorMessage } from '../../utils/toast';
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
const listOptions = ref({
pagetype: 'Jcloud Role',
fields: [
@ -22,7 +25,7 @@ const listOptions = ref({
documentation: 'https://jingrow.com/docs/role-permissions',
columns: [
{
label: '角色',
label: $t('Role'),
fieldname: 'title',
width: 1,
suffix: (row) => {
@ -32,7 +35,7 @@ const listOptions = ref({
},
},
{
label: '成员',
label: $t('Members'),
type: 'Component',
component: ({ row }) => {
return (
@ -44,7 +47,7 @@ const listOptions = ref({
class="flex h-6 items-center space-x-2"
>
<UserAvatarGroup users={row.users} />
<Button label="添加成员">
<Button label={$t('Add Member')}>
{{ icon: () => <i-lucide-plus class="h-4 w-4 text-gray-600" /> }}
</Button>
</div>
@ -56,7 +59,7 @@ const listOptions = ref({
rowActions({ row, listResource: roleListResource }) {
return [
{
label: '编辑权限',
label: $t('Edit Permissions'),
onClick() {
router.push({
name: 'SettingsPermissionRolePermissions',
@ -65,24 +68,24 @@ const listOptions = ref({
},
},
{
label: '配置角色',
label: $t('Configure Role'),
onClick: () => configureRole(row),
},
{
label: '删除角色',
label: $t('Delete Role'),
onClick() {
if (roleListResource.delete.loading) return;
confirmDialog({
title: '删除角色',
message: `确定要删除角色 <b>${row.title}</b> 吗?`,
title: $t('Delete Role'),
message: $t('Are you sure you want to delete role <b>{role}</b>?', { role: row.title }),
onSuccess({ hide }) {
if (roleListResource.delete.loading) return;
toast.promise(roleListResource.delete.submit(row.name), {
loading: '正在删除角色...',
loading: $t('Deleting role...'),
success: () => {
roleListResource.reload();
hide();
return `角色 ${row.title} 已删除`;
return $t('Role {role} deleted', { role: row.title });
},
error: (e) => getToastErrorMessage(e),
});
@ -100,18 +103,18 @@ const listOptions = ref({
},
primaryAction({ listResource: groups }) {
return {
label: '新建角色',
label: $t('New Role'),
variant: 'solid',
slots: {
prefix: icon('plus'),
},
onClick() {
confirmDialog({
title: '创建角色',
title: $t('Create Role'),
fields: [
{
fieldname: 'title',
label: '角色',
label: $t('Role'),
autocomplete: 'off',
},
],

View File

@ -1,6 +1,6 @@
<template>
<div class="mb-5 flex items-center gap-2">
<Tooltip text="所有角色">
<Tooltip :text="$t('All Roles')">
<Button :route="{ name: 'SettingsPermissionRoles' }">
<template #icon>
<i-lucide-arrow-left class="h-4 w-4 text-gray-700" />
@ -10,7 +10,7 @@
<h3 class="text-lg font-medium text-gray-900">
{{ role.pg?.title }}
</h3>
<Tooltip text="管理员角色" v-if="role.pg.admin_access">
<Tooltip :text="$t('Admin Role')" v-if="role.pg.admin_access">
<FeatherIcon name="shield" class="h-5 w-5 text-gray-700" />
</Tooltip>
</div>
@ -41,7 +41,7 @@ import {
createDocumentResource,
createResource,
} from 'jingrow-ui';
import { computed, h, ref } from 'vue';
import { computed, h, ref, getCurrentInstance } from 'vue';
import LucideAppWindow from '~icons/lucide/app-window';
import ObjectList from '../ObjectList.vue';
import { toast } from 'vue-sonner';
@ -49,6 +49,9 @@ import { getToastErrorMessage } from '../../utils/toast';
import { confirmDialog, icon, renderDialog } from '../../utils/components';
import RoleConfigureDialog from './RoleConfigureDialog.vue';
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
let selectedItems = ref(new Set());
const props = defineProps({
@ -71,13 +74,13 @@ const docInsert = createResource({
});
const dropdownOptions = [
{ label: '允许的站点', pagetype: 'Site', fieldname: 'site' },
{ label: $t('Allowed Sites'), pagetype: 'Site', fieldname: 'site' },
{
label: '允许的发布组',
label: $t('Allowed Release Groups'),
pagetype: 'Release Group',
fieldname: 'release_group',
},
{ label: '允许的服务器', pagetype: 'Server', fieldname: 'server' },
{ label: $t('Allowed Servers'), pagetype: 'Server', fieldname: 'server' },
];
const currentDropdownOption = ref(dropdownOptions[0]);
function getDropdownOptions(listResource) {
@ -122,12 +125,13 @@ const rolePermissions = ref({
selectable: true,
columns: [
{
label: computed(() =>
currentDropdownOption.value.pagetype.replace(
'Release Group',
'发布组',
),
),
label: computed(() => {
const pagetype = currentDropdownOption.value.pagetype;
if (pagetype === 'Release Group') {
return $t('Release Group');
}
return pagetype;
}),
format(_value, row) {
return (
row.site_host_name ||
@ -141,7 +145,7 @@ const rolePermissions = ref({
actions({ listResource: permissions }) {
return [
{
label: '配置',
label: $t('Configure'),
slots: {
prefix: icon('settings'),
},
@ -150,8 +154,8 @@ const rolePermissions = ref({
},
},
{
label: '删除',
slots: {
label: $t('Delete'),
slots: {
prefix: icon('trash-2'),
},
condition: () => selectedItems.value.size > 0,
@ -162,7 +166,7 @@ slots: {
},
{
onSuccess: () => {
toast.success('项目删除成功');
toast.success($t('Items deleted successfully'));
selectedItems.value.clear();
},
},
@ -170,20 +174,20 @@ slots: {
},
},
{
label: '添加',
label: $t('Add'),
slots: {
prefix: icon('plus'),
},
onClick() {
const pagetypeLabel = currentDropdownOption.value.pagetype === 'Release Group'
? $t('Release Group')
: currentDropdownOption.value.pagetype;
confirmDialog({
title: '添加权限',
title: $t('Add Permission'),
message: '',
fields: [
{
label: `选择 ${currentDropdownOption.value.pagetype.replace(
'Release Group',
'Bench Group',
)}`,
label: $t('Select {type}', { type: pagetypeLabel }),
type: 'link',
fieldname: 'document_name',
options: {
@ -201,7 +205,7 @@ slots: {
},
],
primaryAction: {
label: '添加',
label: $t('Add'),
onClick({ values }) {
let key = currentDropdownOption.value.fieldname;
@ -214,10 +218,10 @@ slots: {
},
}),
{
loading: '正在添加权限...',
loading: $t('Adding permission...'),
success() {
permissions.reload();
return '权限添加成功';
return $t('Permission added successfully');
},
error: (e) => getToastErrorMessage(e),
},

View File

@ -5,13 +5,16 @@
</template>
<script setup>
import { defineAsyncComponent, h, ref } from 'vue';
import { defineAsyncComponent, h, ref, getCurrentInstance } from 'vue';
import { toast } from 'vue-sonner';
import { getTeam } from '../../data/team';
import { confirmDialog, renderDialog } from '../../utils/components';
import ObjectList from '../ObjectList.vue';
import UserWithAvatarCell from '../UserWithAvatarCell.vue';
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
const team = getTeam();
team.getTeamMembers.submit();
const teamMembersListOptions = ref({
@ -20,7 +23,7 @@ const teamMembersListOptions = ref({
list: team.getTeamMembers,
columns: [
{
label: '用户',
label: $t('User'),
type: 'Component',
component: ({ row }) => {
return h(UserWithAvatarCell, {
@ -38,15 +41,15 @@ const teamMembersListOptions = ref({
return [];
return [
{
label: '移除成员',
label: $t('Remove Member'),
condition: () => row.name !== team.pg.user,
onClick() {
if (team.removeTeamMember.loading) return;
confirmDialog({
title: '移除成员',
message: `确定要将 <b>${row.full_name}</b> 从团队中移除吗?`,
title: $t('Remove Member'),
message: $t('Are you sure you want to remove <b>{name}</b> from the team?', { name: row.full_name }),
onSuccess({ hide }) {
toast.success('成员已被删除', { duration: 2000 });
toast.success($t('Member removed'), { duration: 2000 });
hide();
team.removeTeamMember.submit({ member: row.name });
setTimeout(() => {
@ -61,7 +64,7 @@ const teamMembersListOptions = ref({
actions() {
return [
{
label: '设置',
label: $t('Settings'),
iconLeft: 'settings',
onClick() {
const TeamSettingsDialog = defineAsyncComponent(() =>
@ -71,7 +74,7 @@ const teamMembersListOptions = ref({
}
},
{
label: '添加成员',
label: $t('Add Member'),
variant: 'solid',
iconLeft: 'plus',
onClick() {

View File

@ -1,7 +1,7 @@
<template>
<Dialog
:options="{
title: '设置'
title: $t('Settings')
}"
v-model="show"
>
@ -9,8 +9,8 @@
<div class="mt-8 flex flex-col gap-4">
<Switch
v-model="enforce2FA"
label="强制启用双因素认证"
description="要求所有团队成员启用双因素认证"
:label="$t('Enforce Two-Factor Authentication')"
:description="$t('Require all team members to enable two-factor authentication')"
/>
</div>
</template>

View File

@ -1,8 +1,8 @@
<template>
<Card
v-if="!$team.pg?.jerp_partner"
title="Jingrow 合作伙伴"
subtitle="与您的账户关联的 Jingrow 合作伙伴"
:title="$t('Jingrow Partner')"
:subtitle="$t('Jingrow partner associated with your account')"
class="mx-auto max-w-3xl"
>
<template #actions>
@ -11,14 +11,14 @@
icon-left="edit"
@click="showAddPartnerCodeDialog = true"
>
添加合作伙伴代码
{{ $t('Add Partner Code') }}
</Button>
<Button
v-else
icon-left="trash-2"
@click="showRemovePartnerDialog = true"
>
取消关联合作伙伴
{{ $t('Unlink Partner') }}
</Button>
</template>
<div class="py-4">
@ -26,8 +26,9 @@
class="text-base font-medium text-gray-700"
v-if="!$team.pg?.partner_email"
>
Jingrow 合作伙伴推荐代码吗点击
<strong>添加合作伙伴代码</strong> 以与您的合作伙伴团队关联
{{ $t('Have a Jingrow partner referral code? Click') }}
<strong>{{ $t('Add Partner Code') }}</strong>
{{ $t('to associate with your partner team.') }}
</span>
<ListItem
v-else
@ -37,10 +38,10 @@
</div>
<Dialog
:options="{
title: '关联合作伙伴账户',
title: $t('Link Partner Account'),
actions: [
{
label: '提交',
label: $t('Submit'),
variant: 'solid',
onClick: () => $resources.addPartnerCode.submit(),
},
@ -50,24 +51,24 @@
>
<template v-slot:body-content>
<p class="pb-2 text-p-base">
输入您的合作伙伴提供的合作伙伴代码
{{ $t('Enter the partner code provided by your partner') }}
</p>
<div class="rounded border border-gray-200 bg-gray-100 p-2 mb-4">
<span class="text-sm leading-[1.5] text-gray-700">
<strong>注意</strong>: 与合作伙伴关联后以下信息将与您的合作伙伴团队共享
<strong>{{ $t('Note') }}</strong>: {{ $t('After linking with a partner, the following information will be shared with your partner team:') }}
<br />
<li>账单名称</li>
<li>月度账单金额</li>
<li>{{ $t('Billing Name') }}</li>
<li>{{ $t('Monthly Billing Amount') }}</li>
</span>
</div>
<FormControl
placeholder="例如rGjw3hJ81b"
:placeholder="$t('For example: rGjw3hJ81b')"
v-model="code"
@input="referralCodeChange"
/>
<div class="mt-1">
<div v-if="partnerExists" class="text-sm text-green-600" role="alert">
推荐代码 {{ code }} 属于 {{ partner }}
{{ $t('Referral code {code} belongs to {partner}', { code, partner }) }}
</div>
</div>
</template>
@ -76,10 +77,10 @@
<Dialog
v-model="showRemovePartnerDialog"
:options="{
title: '移除合作伙伴',
title: $t('Remove Partner'),
actions: [
{
label: '移除',
label: $t('Remove'),
variant: 'solid',
theme: 'red',
onClick: () => {
@ -91,7 +92,7 @@
>
<template v-slot:body-content>
<p class="text-p-base pb-3">
这将移除与您的账户关联的合作伙伴您确定要移除该合作伙伴吗
{{ $t('This will remove the partner associated with your account. Are you sure you want to remove this partner?') }}
</p>
</template>
</Dialog>
@ -127,13 +128,13 @@ export default {
onSuccess(data) {
this.showAddPartnerCodeDialog = false;
if (data === 'Request already sent') {
toast.error('Approval Request has already been sent to Partner');
toast.error(this.$t('Approval Request has already been sent to Partner'));
} else {
toast.success('Approval Request has been sent to Partner');
toast.success(this.$t('Approval Request has been sent to Partner'));
}
},
onError() {
throw new DashboardError('Failed to add Partner Code');
throw new DashboardError(this.$t('Failed to add Partner Code'));
},
};
},
@ -148,13 +149,13 @@ export default {
},
removePartner() {
return {
url: 'jcloud.api.partner.remove_partner',
url: 'jcloud.api.partner.remove_partner',
onSuccess() {
this.showRemovePartnerDialog = false;
toast.success('合作伙伴已成功移除');
toast.success(this.$t('Partner removed successfully'));
},
onError() {
throw new DashboardError('移除合作伙伴失败');
throw new DashboardError(this.$t('Failed to remove partner'));
},
};
},
@ -177,7 +178,7 @@ url: 'jcloud.api.partner.remove_partner',
this.referralCode = code;
this.partner = partnerName;
} else {
this.errorMessage = `${code} 是无效的推荐码`;
this.errorMessage = this.$t('{code} is an invalid referral code', { code });
}
}, 500),
},

View File

@ -1,5 +1,5 @@
<template>
<Card title="个人资料" v-if="user" class="mx-auto max-w-3xl">
<Card :title="$t('Profile')" v-if="user" class="mx-auto max-w-3xl">
<div class="flex items-center border-b pb-3">
<div class="relative">
<Avatar size="2xl" :label="user.first_name" :image="user.user_image" />
@ -20,7 +20,7 @@
:class="{ 'opacity-50': uploading }"
>
<span v-if="uploading">{{ progress }}%</span>
<span v-else>编辑</span>
<span v-else>{{ $t('Edit') }}</span>
</button>
</div>
</template>
@ -30,58 +30,58 @@
<h3 class="text-base font-semibold">
{{ user.first_name }} {{ user.last_name }}
</h3>
<p class="mt-1 text-base text-gray-600">用户名{{ user.username }}</p>
<p class="mt-1 text-base text-gray-600">手机{{ user.mobile_no }}</p>
<p class="mt-1 text-base text-gray-600">邮箱{{ user.email }}</p>
<p class="mt-1 text-base text-gray-600">{{ $t('Username') }}: {{ user.username }}</p>
<p class="mt-1 text-base text-gray-600">{{ $t('Phone') }}: {{ user.mobile_no }}</p>
<p class="mt-1 text-base text-gray-600">{{ $t('Email') }}: {{ user.email }}</p>
</div>
<div class="ml-auto">
<Button icon-left="edit" @click="showProfileEditDialog = true">
编辑
{{ $t('Edit') }}
</Button>
</div>
</div>
<div>
<ListItem
title="应用市场开发者"
subtitle="开发者可以在应用市场发布自己的应用,供用户付费或免费订阅。"
:title="$t('Marketplace Developer')"
:subtitle="$t('Developers can publish their apps on the marketplace for users to subscribe to, either paid or free.')"
v-if="!$team.pg.is_developer"
>
<template #actions>
<Button @click="confirmPublisherAccount">
<span>成为开发者</span>
<span>{{ $t('Become a Developer') }}</span>
</Button>
</template>
</ListItem>
<ListItem
:title="user.is_2fa_enabled ? '禁用双因素认证' : '启用双因素认证'"
:title="user.is_2fa_enabled ? $t('Disable Two-Factor Authentication') : $t('Enable Two-Factor Authentication')"
:subtitle="
user.is_2fa_enabled
? '为您的账户禁用双因素认证'
: '为您的账户启用双因素认证以增加额外的安全层'
? $t('Disable two-factor authentication for your account')
: $t('Enable two-factor authentication for your account to add an extra layer of security')
"
>
<template #actions>
<Button @click="show2FADialog = true">
{{ user.is_2fa_enabled ? '禁用' : '启用' }}
{{ user.is_2fa_enabled ? $t('Disable') : $t('Enable') }}
</Button>
</template>
</ListItem>
<ListItem
title="重置密码"
subtitle="更改您的账户登录密码"
:title="$t('Reset Password')"
:subtitle="$t('Change your account login password')"
>
<template #actions>
<Button @click="showResetPasswordDialog = true">
重置密码
{{ $t('Reset Password') }}
</Button>
</template>
</ListItem>
<ListItem
:title="teamEnabled ? '禁用账户' : '启用账户'"
:title="teamEnabled ? $t('Disable Account') : $t('Enable Account')"
:subtitle="
teamEnabled
? '禁用您的账户并停止计费'
: '启用您的账户并恢复计费'
? $t('Disable your account and stop billing')
: $t('Enable your account and resume billing')
"
>
<template #actions>
@ -97,7 +97,7 @@
"
>
<span :class="{ 'text-red-600': teamEnabled }">{{
teamEnabled ? '禁用' : '启用'
teamEnabled ? $t('Disable') : $t('Enable')
}}</span>
</Button>
</template>
@ -105,11 +105,11 @@
</div>
<Dialog
:options="{
title: '更新个人资料信息',
title: $t('Update Profile Information'),
actions: [
{
variant: 'solid',
label: '保存更改',
label: $t('Save Changes'),
onClick: () => $resources.updateProfile.submit(),
},
],
@ -118,10 +118,10 @@
>
<template v-slot:body-content>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormControl label="个人名称" v-model="user.first_name" />
<FormControl label="用户名" v-model="user.username" />
<FormControl label="手机" v-model="user.mobile_no" />
<FormControl label="邮箱" v-model="user.email" />
<FormControl :label="$t('First Name')" v-model="user.first_name" />
<FormControl :label="$t('Username')" v-model="user.username" />
<FormControl :label="$t('Phone')" v-model="user.mobile_no" />
<FormControl :label="$t('Email')" v-model="user.email" />
</div>
<ErrorMessage class="mt-4" :message="$resources.updateProfile.error" />
</template>
@ -129,10 +129,10 @@
<Dialog
:options="{
title: '禁用账户',
title: $t('Disable Account'),
actions: [
{
label: '禁用账户',
label: $t('Disable Account'),
variant: 'solid',
theme: 'red',
loading: $resources.disableAccount.loading,
@ -141,23 +141,23 @@
],
}"
v-model="showDisableAccountDialog"
>
>
<template v-slot:body-content>
<div class="prose text-base">
确认此操作后
{{ $t('After confirming this action:') }}
<ul>
<li>您的账户将被禁用</li>
<li>{{ $t('Your account will be disabled') }}</li>
<li>
您激活的站点将立即暂停并在一周后被删除
{{ $t('Your activated sites will be suspended immediately and deleted after one week.') }}
</li>
<li>您的账户计费将停止</li>
<li>{{ $t('Your account billing will stop') }}</li>
</ul>
您可以稍后登录以重新启用您的账户您要继续吗
{{ $t('You can log in later to re-enable your account. Do you want to continue?') }}
</div>
<FormControl
v-if="user.is_2fa_enabled"
class="mt-4"
label="输入您的2FA代码以确认"
:label="$t('Enter your 2FA code to confirm')"
v-model="disableAccount2FACode"
/>
<ErrorMessage class="mt-2" :message="$resources.disableAccount.error" />
@ -166,10 +166,10 @@
<Dialog
:options="{
title: '启用账户',
title: $t('Enable Account'),
actions: [
{
label: '启用账户',
label: $t('Enable Account'),
variant: 'solid',
loading: $resources.enableAccount.loading,
onClick: () => $resources.enableAccount.submit(),
@ -180,13 +180,13 @@
>
<template v-slot:body-content>
<div class="prose text-base">
确认此操作后
{{ $t('After confirming this action:') }}
<ul>
<li>您的账户将被启用</li>
<li>您暂停的站点将重新激活</li>
<li>您的账户计费将恢复</li>
<li>{{ $t('Your account will be enabled') }}</li>
<li>{{ $t('Your suspended sites will be reactivated') }}</li>
<li>{{ $t('Your account billing will resume') }}</li>
</ul>
您要继续吗
{{ $t('Do you want to continue?') }}
</div>
<ErrorMessage class="mt-2" :message="$resources.enableAccount.error" />
</template>
@ -263,6 +263,9 @@ export default {
this.showProfileEditDialog = false;
this.notifySuccess();
},
onError() {
// Error handling
},
};
},
disableAccount: {
@ -274,22 +277,22 @@ export default {
() => import('../../ChurnFeedbackDialog.vue'),
);
renderDialog(
h(ChurnFeedbackDialog, {
team: this.$team.pg.name,
onUpdated: () => {
toast.success('您的反馈已成功提交');
},
}),
);
toast.success('您的账户已成功禁用');
renderDialog(
h(ChurnFeedbackDialog, {
team: this.$team.pg.name,
onUpdated: () => {
toast.success(this.$t('Your feedback has been submitted successfully'));
},
}),
);
toast.success(this.$t('Your account has been disabled successfully'));
this.reloadAccount();
},
},
enableAccount: {
url: 'jcloud.api.account.enable_account',
onSuccess() {
toast.success('您的账户已成功启用');
toast.success(this.$t('Your account has been enabled successfully'));
this.reloadAccount();
this.showEnableAccountDialog = false;
},
@ -332,7 +335,7 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
this.notifySuccess();
},
notifySuccess() {
toast.success('您的个人资料已成功更新');
toast.success(this.$t('Your profile has been updated successfully'));
},
deactivateAccount(disableAccount2FACode) {
const currency = this.$team.pg.currency;
@ -343,56 +346,53 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
);
renderDialog(h(finalizeInvoicesDialog));
} else if (this.unpaidInvoices) {
if (this.unpaidInvoices.length > 1) {
this.showDisableAccountDialog = false;
if (this.$team.pg.payment_mode === 'Prepaid Credits') {
this.showAddPrepaidCreditsDialog = true;
} else {
confirmDialog({
title: '多张未支付发票',
message:
'您有多张未支付的发票。请从发票页面支付它们',
primaryAction: {
label: '前往发票',
variant: 'solid',
onClick: ({ hide }) => {
router.push({ name: 'BillingInvoices' });
hide();
},
},
});
}
} else {
let invoice = this.unpaidInvoices;
if (invoice.amount_due > minAmount) {
if (this.unpaidInvoices.length > 1) {
this.showDisableAccountDialog = false;
confirmDialog({
title: '清除未支付发票',
message: `您有一张未支付的发票,金额为${
invoice.currency === 'CNY' ? '¥' : '$'
} ${
invoice.amount_due
}请在停用账户前结清它`,
primaryAction: {
label: '立即结算',
variant: 'solid',
onClick: ({ hide }) => {
if (
invoice.stripe_invoice_url &&
this.$team.pg.payment_mode === 'Card'
) {
window.open(
`/api/action/jcloud.api.client.run_pg_method?dt=Invoice&dn=${invoice.name}&method=stripe_payment_url`,
);
} else {
this.showAddPrepaidCreditsDialog = true;
}
hide();
if (this.$team.pg.payment_mode === 'Prepaid Credits') {
this.showAddPrepaidCreditsDialog = true;
} else {
confirmDialog({
title: this.$t('Multiple Unpaid Invoices'),
message: this.$t('You have multiple unpaid invoices. Please pay them from the invoices page.'),
primaryAction: {
label: this.$t('Go to Invoices'),
variant: 'solid',
onClick: ({ hide }) => {
router.push({ name: 'BillingInvoices' });
hide();
},
},
},
});
});
}
} else {
let invoice = this.unpaidInvoices;
if (invoice.amount_due > minAmount) {
this.showDisableAccountDialog = false;
confirmDialog({
title: this.$t('Clear Unpaid Invoice'),
message: this.$t('You have an unpaid invoice of {amount}. Please settle it before deactivating your account.', {
amount: `${invoice.currency === 'CNY' ? '¥' : '$'} ${invoice.amount_due}`
}),
primaryAction: {
label: this.$t('Settle Now'),
variant: 'solid',
onClick: ({ hide }) => {
if (
invoice.stripe_invoice_url &&
this.$team.pg.payment_mode === 'Card'
) {
window.open(
`/api/action/jcloud.api.client.run_pg_method?dt=Invoice&dn=${invoice.name}&method=stripe_payment_url`,
);
} else {
this.showAddPrepaidCreditsDialog = true;
}
hide();
},
},
});
}
}
}
}
// validate if any active servers
@ -409,9 +409,8 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
},
confirmPublisherAccount() {
confirmDialog({
title: '成为市场应用开发者?',
message:
'确认后,您将能够将应用发布到我们的市场。',
title: this.$t('Become a Marketplace Developer?'),
message: this.$t('After confirmation, you will be able to publish apps to our marketplace.'),
onSuccess: ({ hide }) => {
toast.promise(
this.$team.setValue.submit(
@ -431,9 +430,9 @@ url: 'jcloud.api.billing.get_unpaid_invoices',
},
),
{
success: '您现在可以将应用发布到我们的市场',
error: '标记您为开发者失败',
loading: '正在将您设为开发者...',
success: this.$t('You can now publish apps to our marketplace'),
error: this.$t('Failed to mark you as a developer'),
loading: this.$t('Setting you as a developer...'),
},
);
},

View File

@ -1,18 +1,17 @@
<template>
<Card
v-if="referralLink"
title="推荐有礼"
subtitle="您的专属推荐链接"
:title="$t('Referral Program')"
:subtitle="$t('Your exclusive referral link')"
class="mx-auto max-w-3xl"
>
<div class="flex flex-col space-y-4 overflow-hidden">
<ClickToCopyField :textContent="referralLink" />
<span class="text-sm font-medium leading-4 text-gray-700">
邀请他人加入 今果 Jingrow
{{ $t('Invite others to join Jingrow,') }}
<strong>
当他们注册并消费至少 ¥100
您将获得 ¥20 </strong
>
{{ $t('when they register and spend at least ¥100, you will get ¥20') }}
</strong>
</span>
</div>
</Card>

View File

@ -1,7 +1,7 @@
<template>
<Header class="sticky top-0 z-10 bg-white">
<div class="flex items-center space-x-2">
<Breadcrumbs :items="[{ label: '设置', route: '/settings' }]" />
<Breadcrumbs :items="[{ label: $t('Settings'), route: '/settings' }]" />
</div>
</Header>
<div>
@ -20,18 +20,22 @@ import { icon } from '../utils/components';
import TabsWithRouter from '../components/TabsWithRouter.vue';
import { getTeam } from '../data/team';
import { session } from '../data/session';
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
let $team = getTeam();
let $session = session || {};
const tabs = [
{
label: '个人资料',
label: $t('Profile'),
icon: icon('user'),
routeName: 'SettingsProfile'
},
{
label: '团队',
label: $t('Team'),
icon: icon('users'),
routeName: 'SettingsTeam',
condition: () =>
@ -40,7 +44,7 @@ const tabs = [
$session.isSystemUser
},
{
label: '权限',
label: $t('Permissions'),
icon: icon('lock'),
routeName: 'SettingsPermission',
childrenRoutes: [
@ -53,7 +57,7 @@ const tabs = [
$session.isSystemUser
},
{
label: '开发者',
label: $t('Developer'),
icon: icon('code'),
routeName: 'SettingsDeveloper'
}

View File

@ -822,4 +822,159 @@ Country and City,国家和城市,
State and Postal Code,州和邮政编码,
{field} is required,{field} 是必填项,
Billing information updated,账单信息已更新,
Profile,个人资料,
Team,团队,
Permissions,权限,
Add New Member,添加新成员,
Invite Member,邀请成员,
Username,用户名,
Select Role,选择角色,
Username is required,用户名为必填项,
Member added to team,已添加成员到团队,
Remove Member,移除成员,
Are you sure you want to remove <b>{name}</b> from the team?,确定要将 <b>{name}</b> 从团队中移除吗?,
Member removed,成员已被删除,
Add Member,添加成员,
Enforce Two-Factor Authentication,强制启用双因素认证,
Require all team members to enable two-factor authentication,要求所有团队成员启用双因素认证,
API Access,API 访问,
Regenerate API Key,重新生成 API 密钥,
Create New API Key,创建新的 API 密钥,
You don't have an API key yet. Click the button above to create one.,您还没有 API 密钥。点击上面的按钮创建一个。,
Please copy the API key immediately. You will not be able to view it again!,请立即复制 API 密钥。您将无法再次查看它!,
API key and API secret can be used to access,API key 和 API secret 可用于访问,
Jingrow API,今果 Jingrow API,
API key regenerated successfully,API 密钥已成功重新生成,
API key created successfully,API 密钥已成功创建,
SSH Keys,SSH 密钥,
SSH key added successfully,SSH 密钥已成功添加,
Failed to add SSH key,SSH 密钥无法添加,
SSH key updated successfully,SSH 密钥已成功更新,
Failed to set SSH key as default,SSH 密钥无法设置为默认,
SSH key deleted successfully,SSH 密钥已成功删除,
Failed to delete SSH key,SSH 密钥无法删除,
SSH Fingerprint,SSH 指纹,
Added Time,添加时间,
Add SSH Key,添加 SSH 密钥,
Add New SSH Key,添加新 SSH 密钥,
Add a new SSH key to your account,向您的账户添加新的 SSH 密钥,
SSH Key,SSH 密钥,
Starts with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com',以 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', 或 'sk-ssh-ed25519@openssh.com' 开头,
SSH key is required,SSH 密钥是必填项,
Webhooks,Webhooks,
Webhook deleted successfully,Webhook 删除成功,
Failed to delete webhook,Webhook 无法删除,
Endpoint,端点,
Enabled,启用,
Disabled,禁用,
Activate,激活,
Disable,禁用,
Disable Webhook,禁用 Webhook,
Endpoint - {endpoint}<br>Are you sure you want to disable this webhook?<br>,端点 - {endpoint}<br>确定要禁用该 webhook 吗?<br>,
Webhook disabled successfully,Webhook 禁用成功,
Failed to disable webhook,Webhook 无法禁用,
Attempts,尝试,
Add Webhook,添加 Webhook,
Refresh,刷新,
Delete Webhook,删除 Webhook,
Endpoint - {endpoint}<br>Are you sure you want to delete this webhook?<br>,端点 - {endpoint}<br>确定要删除该 webhook 吗?<br>,
First Name,个人名称,
Phone,手机,
Marketplace Developer,应用市场开发者,
Developers can publish their apps on the marketplace for users to subscribe to, either paid or free.,开发者可以在应用市场发布自己的应用,供用户付费或免费订阅。,
Become a Developer,成为开发者,
Disable Two-Factor Authentication,禁用双因素认证,
Enable Two-Factor Authentication,启用双因素认证,
Disable two-factor authentication for your account,为您的账户禁用双因素认证,
Enable two-factor authentication for your account to add an extra layer of security,为您的账户启用双因素认证以增加额外的安全层,
Reset Password,重置密码,
Change your account login password,更改您的账户登录密码,
Update Profile Information,更新个人资料信息,
Save Changes,保存更改,
After confirming this action:,确认此操作后:,
Your account will be disabled,您的账户将被禁用,
Your activated sites will be suspended immediately and deleted after one week.,您激活的站点将立即暂停,并在一周后被删除。,
Your account billing will stop,您的账户计费将停止,
You can log in later to re-enable your account. Do you want to continue?,您可以稍后登录以重新启用您的账户。您要继续吗?,
Enter your 2FA code to confirm,输入您的2FA代码以确认,
Your account will be enabled,您的账户将被启用,
Your suspended sites will be reactivated,您暂停的站点将重新激活,
Your account billing will resume,您的账户计费将恢复,
Do you want to continue?,您要继续吗?,
Your feedback has been submitted successfully,您的反馈已成功提交,
Your account has been disabled successfully,您的账户已成功禁用,
Your account has been enabled successfully,您的账户已成功启用,
Your profile has been updated successfully,您的个人资料已成功更新,
Clear Unpaid Invoice,清除未支付发票,
You have an unpaid invoice of {amount}. Please settle it before deactivating your account.,您有一张未支付的发票,金额为{amount}。请在停用账户前结清它。,
Settle Now,立即结算,
Become a Marketplace Developer?,成为市场应用开发者?,
After confirmation, you will be able to publish apps to our marketplace.,确认后,您将能够将应用发布到我们的市场。,
You can now publish apps to our marketplace,您现在可以将应用发布到我们的市场,
Failed to mark you as a developer,标记您为开发者失败,
Setting you as a developer...,正在将您设为开发者...,
Role,角色,
Members,成员,
Edit Permissions,编辑权限,
Configure Role,配置角色,
Delete Role,删除角色,
Are you sure you want to delete role <b>{role}</b>?,确定要删除角色 <b>{role}</b> 吗?,
Deleting role...,正在删除角色...,
Role {role} deleted,角色 {role} 已删除,
New Role,新建角色,
Create Role,创建角色,
All Roles,所有角色,
Admin Role,管理员角色,
Allowed Sites,允许的站点,
Allowed Release Groups,允许的发布组,
Allowed Servers,允许的服务器,
Items deleted successfully,项目删除成功,
Add Permission,添加权限,
Select {type},选择 {type},
Adding permission...,正在添加权限...,
Permission added successfully,权限添加成功,
Select member to add,选择要添加的成员,
No members have been added to this role yet.,此角色尚未添加任何成员。,
Admin Access,管理员权限,
Grant members permissions similar to team owner. Includes access to all pages and settings.,授予成员类似团队所有者的权限。包括访问所有页面和设置。,
Page Access Permissions,页面访问权限,
Allow Billing Access,允许访问计费,
Allow Apps Access,允许访问应用,
Allow Partner Access,允许访问合作伙伴,
Allow Site Creation,允许创建站点,
Allow Release Group Creation,允许创建站点分组,
Allow Server Creation,允许创建服务器,
Allow Webhook Configuration,允许配置Webhook,
Added {user} to {role},已添加 {user} 到 {role},
Removed {user} from {role},已从 {role} 中移除 {user},
Referral Program,推荐有礼,
Your exclusive referral link,您的专属推荐链接,
Invite others to join Jingrow,,邀请他人加入 今果 Jingrow,
when they register and spend at least ¥100, you will get ¥20,当他们注册并消费至少 ¥100 时,您将获得 ¥20,
Jingrow Partner,Jingrow 合作伙伴,
Jingrow partner associated with your account,与您的账户关联的 Jingrow 合作伙伴,
Add Partner Code,添加合作伙伴代码,
Unlink Partner,取消关联合作伙伴,
Have a Jingrow partner referral code? Click,有 Jingrow 合作伙伴推荐代码吗?点击,
to associate with your partner team.,以与您的合作伙伴团队关联。,
Link Partner Account,关联合作伙伴账户,
Enter the partner code provided by your partner,输入您的合作伙伴提供的合作伙伴代码,
After linking with a partner, the following information will be shared with your partner team:,与合作伙伴关联后,以下信息将与您的合作伙伴团队共享:,
Monthly Billing Amount,月度账单金额,
For example: rGjw3hJ81b,例如rGjw3hJ81b,
Referral code {code} belongs to {partner},推荐代码 {code} 属于 {partner},
Remove Partner,移除合作伙伴,
This will remove the partner associated with your account. Are you sure you want to remove this partner?,这将移除与您的账户关联的合作伙伴。您确定要移除该合作伙伴吗?,
Approval Request has already been sent to Partner,批准请求已发送给合作伙伴,
Approval Request has been sent to Partner,批准请求已发送给合作伙伴,
Failed to add Partner Code,添加合作伙伴代码失败,
Partner removed successfully,合作伙伴已成功移除,
Failed to remove partner,移除合作伙伴失败,
{code} is an invalid referral code,{code} 是无效的推荐码,
Settings,设置,
Developer,开发者,
User,用户,
Default,默认,
Release Group,发布组,
Note,注意,
Submit,提交,

Can't render this file because it has a wrong number of fields in line 390.