feat: 新增皮肤站问题详情与评论
Sync source to CNB / Sync Git ref (push) Skipped

This commit is contained in:
AxTps committed 2026-10-17 21:59:25 +08:00
1 parent 9bdc965cd5
commit 146b88afce
5 files changed
+551 -6

No files matched your search

@@ -0,0 +1,425 @@
<script setup lang="ts">
import { SpinnerIcon } from '@modrinth/assets'
import { defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import { computed, onMounted, ref } from 'vue'
import {
type SkinSiteBugComment,
skinSiteBugComments,
type SkinSiteBugIssue,
skinSiteBugIssue,
type SkinSiteBugIssueType,
type SkinSiteBugPriority,
type SkinSiteBugResolution,
type SkinSiteBugStatus,
skinSiteCreateBugComment,
skinSiteDeleteBugComment,
} from '@/helpers/skin-site-api'
const props = defineProps<{ issueId: number }>()
const emit = defineEmits<{ (e: 'back'): void }>()
const { formatMessage } = useVIntl()
const { handleError, addNotification } = injectNotificationManager()
const messages = defineMessages({
back: { id: 'app.skin-site.bug-detail.back', defaultMessage: 'Back to list' },
loading: { id: 'app.skin-site.bug-detail.loading', defaultMessage: 'Loading…' },
failed: { id: 'app.skin-site.bug-detail.failed', defaultMessage: 'Could not load this issue.' },
issueNo: { id: 'app.skin-site.bug-detail.issue-no', defaultMessage: 'Issue #{id}' },
by: { id: 'app.skin-site.bug-detail.by', defaultMessage: 'by {name}' },
assignee: { id: 'app.skin-site.bug-detail.assignee', defaultMessage: 'Assignee: {name}' },
unassigned: { id: 'app.skin-site.bug-detail.unassigned', defaultMessage: 'Unassigned' },
descriptionEmpty: {
id: 'app.skin-site.bug-detail.description-empty',
defaultMessage: 'No description provided.',
},
resolution: { id: 'app.skin-site.bug-detail.resolution', defaultMessage: 'Resolution: {name}' },
commentsTitle: { id: 'app.skin-site.bug-detail.comments-title', defaultMessage: 'Comments' },
commentsEmpty: {
id: 'app.skin-site.bug-detail.comments-empty',
defaultMessage: 'No comments yet. Be the first to reply.',
},
commentsLoading: {
id: 'app.skin-site.bug-detail.comments-loading',
defaultMessage: 'Loading comments…',
},
commentPlaceholder: {
id: 'app.skin-site.bug-detail.comment-placeholder',
defaultMessage: 'Write a comment…',
},
commentSubmit: { id: 'app.skin-site.bug-detail.comment-submit', defaultMessage: 'Comment' },
commentSending: { id: 'app.skin-site.bug-detail.comment-sending', defaultMessage: 'Sending…' },
commentEmptyError: {
id: 'app.skin-site.bug-detail.comment-empty-error',
defaultMessage: 'Comment cannot be empty.',
},
commentPosted: {
id: 'app.skin-site.bug-detail.comment-posted',
defaultMessage: 'Comment posted.',
},
commentDeleted: {
id: 'app.skin-site.bug-detail.comment-deleted',
defaultMessage: 'Comment deleted.',
},
delete: { id: 'app.skin-site.bug-detail.delete', defaultMessage: 'Delete' },
deleting: { id: 'app.skin-site.bug-detail.deleting', defaultMessage: 'Deleting…' },
system: { id: 'app.skin-site.bug-detail.system', defaultMessage: 'System' },
typeBug: { id: 'app.skin-site.bugs.type.bug', defaultMessage: 'Bug' },
typeFeature: { id: 'app.skin-site.bugs.type.feature', defaultMessage: 'Feature' },
typeImprovement: { id: 'app.skin-site.bugs.type.improvement', defaultMessage: 'Improvement' },
statusPending: { id: 'app.skin-site.bugs.status.pending', defaultMessage: 'Pending' },
statusConfirmed: { id: 'app.skin-site.bugs.status.confirmed', defaultMessage: 'Confirmed' },
statusNeedsInfo: { id: 'app.skin-site.bugs.status.needs-info', defaultMessage: 'Needs info' },
statusInProgress: { id: 'app.skin-site.bugs.status.in-progress', defaultMessage: 'In progress' },
statusVerifying: { id: 'app.skin-site.bugs.status.verifying', defaultMessage: 'Verifying' },
statusClosed: { id: 'app.skin-site.bugs.status.closed', defaultMessage: 'Closed' },
priorityCritical: { id: 'app.skin-site.bugs.priority.critical', defaultMessage: 'Critical' },
priorityHigh: { id: 'app.skin-site.bugs.priority.high', defaultMessage: 'High' },
priorityNormal: { id: 'app.skin-site.bugs.priority.normal', defaultMessage: 'Normal' },
priorityLow: { id: 'app.skin-site.bugs.priority.low', defaultMessage: 'Low' },
resFixed: { id: 'app.skin-site.bugs.resolution.fixed', defaultMessage: 'Fixed' },
resWontFix: { id: 'app.skin-site.bugs.resolution.wont-fix', defaultMessage: 'Won’t fix' },
resByDesign: { id: 'app.skin-site.bugs.resolution.by-design', defaultMessage: 'By design' },
resDuplicate: { id: 'app.skin-site.bugs.resolution.duplicate', defaultMessage: 'Duplicate' },
resCannotReproduce: {
id: 'app.skin-site.bugs.resolution.cannot-reproduce',
defaultMessage: 'Cannot reproduce',
},
resInvalid: { id: 'app.skin-site.bugs.resolution.invalid', defaultMessage: 'Invalid' },
})
const issue = ref<SkinSiteBugIssue | null>(null)
const loading = ref(true)
const failed = ref(false)
const comments = ref<SkinSiteBugComment[]>([])
const commentsLoading = ref(true)
const commentsHasMore = ref(false)
const commentsLoadingMore = ref(false)
const commentDraft = ref('')
const commentSending = ref(false)
const deletingId = ref<number | null>(null)
function typeLabel(type: SkinSiteBugIssueType): string {
switch (type) {
case 'FEATURE':
return formatMessage(messages.typeFeature)
case 'IMPROVEMENT':
return formatMessage(messages.typeImprovement)
default:
return formatMessage(messages.typeBug)
}
}
function statusLabel(status: SkinSiteBugStatus): string {
switch (status) {
case 'CONFIRMED':
return formatMessage(messages.statusConfirmed)
case 'NEEDS_INFO':
return formatMessage(messages.statusNeedsInfo)
case 'IN_PROGRESS':
return formatMessage(messages.statusInProgress)
case 'VERIFYING':
return formatMessage(messages.statusVerifying)
case 'CLOSED':
return formatMessage(messages.statusClosed)
default:
return formatMessage(messages.statusPending)
}
}
function priorityLabel(priority: SkinSiteBugPriority): string {
switch (priority) {
case 'CRITICAL':
return formatMessage(messages.priorityCritical)
case 'HIGH':
return formatMessage(messages.priorityHigh)
case 'LOW':
return formatMessage(messages.priorityLow)
default:
return formatMessage(messages.priorityNormal)
}
}
function resolutionLabel(resolution: SkinSiteBugResolution): string {
switch (resolution) {
case 'WONT_FIX':
return formatMessage(messages.resWontFix)
case 'BY_DESIGN':
return formatMessage(messages.resByDesign)
case 'DUPLICATE':
return formatMessage(messages.resDuplicate)
case 'CANNOT_REPRODUCE':
return formatMessage(messages.resCannotReproduce)
case 'INVALID':
return formatMessage(messages.resInvalid)
default:
return formatMessage(messages.resFixed)
}
}
function formatDateTime(ts: number): string {
try {
return new Date(ts).toLocaleString()
} catch {
return ''
}
}
const canSubmit = computed(() => !commentSending.value && commentDraft.value.trim().length > 0)
async function loadIssue() {
loading.value = true
failed.value = false
try {
const result = await skinSiteBugIssue(props.issueId)
if (!result.success) throw new Error(result.errorMessage)
issue.value = result.payload
} catch (error) {
failed.value = true
handleError(error)
} finally {
loading.value = false
}
}
async function loadComments(reset = true) {
if (reset) {
commentsLoading.value = true
comments.value = []
} else {
commentsLoadingMore.value = true
}
try {
const result = await skinSiteBugComments(props.issueId, reset ? 0 : comments.value.length, 50)
if (!result.success) throw new Error(result.errorMessage)
const page = result.payload
comments.value = reset ? page.items : [...comments.value, ...page.items]
commentsHasMore.value = page.hasMore
} catch (error) {
handleError(error)
} finally {
commentsLoading.value = false
commentsLoadingMore.value = false
}
}
async function submitComment() {
const content = commentDraft.value.trim()
if (!content || commentSending.value) return
commentSending.value = true
try {
const result = await skinSiteCreateBugComment(props.issueId, content)
if (!result.success) throw new Error(result.errorMessage)
comments.value = [...comments.value, result.payload]
commentDraft.value = ''
addNotification({ type: 'success', title: formatMessage(messages.commentPosted) })
} catch (error) {
handleError(error)
} finally {
commentSending.value = false
}
}
async function deleteComment(comment: SkinSiteBugComment) {
if (deletingId.value !== null) return
deletingId.value = comment.id
try {
const result = await skinSiteDeleteBugComment(comment.id)
if (!result.success) throw new Error(result.errorMessage)
comments.value = comments.value.filter((c) => c.id !== comment.id)
addNotification({ type: 'success', title: formatMessage(messages.commentDeleted) })
} catch (error) {
handleError(error)
} finally {
deletingId.value = null
}
}
onMounted(() => {
void loadIssue()
void loadComments(true)
})
</script>
<template>
<div class="flex flex-col gap-4">
<button
type="button"
class="flex w-fit items-center gap-1 rounded-full bg-button-bg px-3 py-1 text-xs font-semibold text-contrast hover:brightness-110"
@click="emit('back')"
>
← {{ formatMessage(messages.back) }}
</button>
<div v-if="loading" class="flex items-center gap-2 text-sm text-secondary">
<SpinnerIcon class="size-4 animate-spin" />
{{ formatMessage(messages.loading) }}
</div>
<p v-else-if="failed" class="m-0 text-sm text-secondary">
{{ formatMessage(messages.failed) }}
</p>
<template v-else-if="issue">
<header
class="flex flex-col gap-2 rounded-xl border border-solid border-surface-5 bg-bg-raised p-4"
>
<div class="flex flex-wrap items-center gap-2">
<span
class="shrink-0 rounded-full bg-button-bg px-2 py-0.5 text-[10px] font-semibold text-secondary"
>
{{ typeLabel(issue.type) }}
</span>
<span
class="shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold"
:class="{
'bg-green text-contrast':
issue.status === 'CONFIRMED' || issue.status === 'VERIFYING',
'bg-yellow text-contrast':
issue.status === 'NEEDS_INFO' || issue.status === 'IN_PROGRESS',
'bg-button-bg text-secondary': issue.status === 'PENDING',
'bg-surface-5 text-secondary': issue.status === 'CLOSED',
}"
>
{{ statusLabel(issue.status) }}
</span>
<span
class="shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold"
:class="{
'bg-red text-contrast': issue.priority === 'CRITICAL',
'bg-orange text-contrast': issue.priority === 'HIGH',
'bg-button-bg text-secondary':
issue.priority === 'NORMAL' || issue.priority === 'LOW',
}"
>
{{ priorityLabel(issue.priority) }}
</span>
<span v-if="issue.private" class="shrink-0 text-[10px] font-semibold text-secondary">
🔒
</span>
<span class="min-w-0 flex-1 truncate text-xs text-secondary">
{{ formatMessage(messages.issueNo, { id: issue.id }) }}
</span>
</div>
<h1 class="m-0 text-xl font-bold text-contrast">{{ issue.title }}</h1>
<div class="flex flex-wrap items-center gap-3 text-xs text-secondary">
<span>{{ formatMessage(messages.by, { name: issue.authorName }) }}</span>
<span>{{ formatDateTime(issue.createdAt) }}</span>
<span v-if="issue.assigneeName">
{{ formatMessage(messages.assignee, { name: issue.assigneeName }) }}
</span>
<span v-else>{{ formatMessage(messages.unassigned) }}</span>
</div>
<span v-if="issue.resolution" class="text-xs font-semibold text-secondary">
{{ formatMessage(messages.resolution, { name: resolutionLabel(issue.resolution) }) }}
</span>
</header>
<div class="rounded-xl border border-solid border-surface-5 bg-bg-raised p-4">
<p
v-if="issue.description"
class="m-0 whitespace-pre-wrap break-words text-sm text-contrast"
>
{{ issue.description }}
</p>
<p v-else class="m-0 text-sm text-secondary">
{{ formatMessage(messages.descriptionEmpty) }}
</p>
<div v-if="issue.images.length" class="mt-3 flex flex-wrap gap-2">
<span
v-for="image in issue.images"
:key="image.hash"
class="rounded-lg bg-surface-5 px-2 py-1 text-[10px] font-mono text-secondary"
>
{{ image.hash }}
</span>
</div>
</div>
<section class="flex flex-col gap-3">
<h2 class="m-0 text-base font-bold text-contrast">
{{ formatMessage(messages.commentsTitle) }}
</h2>
<div class="flex flex-col gap-2">
<div v-if="commentsLoading" class="flex items-center gap-2 text-sm text-secondary">
<SpinnerIcon class="size-4 animate-spin" />
{{ formatMessage(messages.commentsLoading) }}
</div>
<p
v-else-if="!comments.length"
class="m-0 rounded-xl border border-solid border-surface-5 bg-bg-raised p-4 text-sm text-secondary"
>
{{ formatMessage(messages.commentsEmpty) }}
</p>
<div
v-for="comment in comments"
v-else
:key="comment.id"
class="flex flex-col gap-1.5 rounded-xl border border-solid border-surface-5 bg-bg-raised p-3"
>
<div class="flex flex-wrap items-center gap-2 text-xs text-secondary">
<span class="font-semibold text-contrast">{{ comment.authorName }}</span>
<span
v-if="comment.system"
class="rounded-full bg-button-bg px-2 py-0.5 text-[10px] font-semibold"
>
{{ formatMessage(messages.system) }}
</span>
<span>{{ formatDateTime(comment.createdAt) }}</span>
<span v-if="comment.editedAt">(edited)</span>
<button
v-if="comment.canDelete"
type="button"
class="ml-auto rounded-full bg-button-bg px-2 py-0.5 text-[10px] font-semibold text-secondary hover:brightness-110 disabled:opacity-50"
:disabled="deletingId !== null"
@click="deleteComment(comment)"
>
{{
deletingId === comment.id
? formatMessage(messages.deleting)
: formatMessage(messages.delete)
}}
</button>
</div>
<p class="m-0 whitespace-pre-wrap break-words text-sm text-contrast">
{{ comment.content }}
</p>
</div>
</div>
<button
v-if="commentsHasMore"
type="button"
class="w-fit self-center rounded-full bg-button-bg px-3 py-1 text-xs font-semibold text-contrast hover:brightness-110 disabled:opacity-50"
:disabled="commentsLoadingMore"
@click="loadComments(false)"
>
{{ formatMessage(messages.commentsTitle) }}
</button>
<form class="flex flex-col gap-2" @submit.prevent="submitComment">
<textarea
v-model="commentDraft"
rows="3"
class="w-full resize-y rounded-xl border border-solid border-surface-5 bg-bg-raised p-3 text-sm text-contrast outline-none focus:border-brand"
:placeholder="formatMessage(messages.commentPlaceholder)"
/>
<button
type="submit"
class="w-fit self-end rounded-full bg-brand px-4 py-2 text-sm font-semibold text-contrast disabled:opacity-50"
:disabled="!canSubmit"
>
{{
commentSending
? formatMessage(messages.commentSending)
: formatMessage(messages.commentSubmit)
}}
</button>
</form>
</section>
</template>
</div>
</template>
@@ -42,7 +42,7 @@ const messages = defineMessages({
priorityNormal: { id: 'app.skin-site.bugs.priority.normal', defaultMessage: 'Normal' }, priorityNormal: { id: 'app.skin-site.bugs.priority.normal', defaultMessage: 'Normal' },
priorityLow: { id: 'app.skin-site.bugs.priority.low', defaultMessage: 'Low' }, priorityLow: { id: 'app.skin-site.bugs.priority.low', defaultMessage: 'Low' },
}) })
const emit = defineEmits<{ (e: 'back'): void }>() const emit = defineEmits<{ (e: 'back'): void; (e: 'open', id: number): void }>()
const items = ref<SkinSiteBugIssue[]>([]) const items = ref<SkinSiteBugIssue[]>([])
const hasMore = ref(false) const hasMore = ref(false)
@@ -221,7 +221,8 @@ onMounted(() => load(true))
<div <div
v-for="issue in items" v-for="issue in items"
:key="issue.id" :key="issue.id"
class="flex flex-col gap-1.5 rounded-xl border border-solid border-surface-5 bg-bg-raised p-3" class="flex cursor-pointer flex-col gap-1.5 rounded-xl border border-solid border-surface-5 bg-bg-raised p-3 hover:border-brand"
@click="emit('open', issue.id)"
> >
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<span <span
@@ -5,6 +5,7 @@ import { ref } from 'vue'
import SkinSiteAccountCard from './SkinSiteAccountCard.vue' import SkinSiteAccountCard from './SkinSiteAccountCard.vue'
import SkinSiteAnnouncements from './SkinSiteAnnouncements.vue' import SkinSiteAnnouncements from './SkinSiteAnnouncements.vue'
import SkinSiteBugDetailView from './SkinSiteBugDetailView.vue'
import SkinSiteBugListView from './SkinSiteBugListView.vue' import SkinSiteBugListView from './SkinSiteBugListView.vue'
import SkinSiteDonationCard from './SkinSiteDonationCard.vue' import SkinSiteDonationCard from './SkinSiteDonationCard.vue'
import SkinSiteDownloadsView from './SkinSiteDownloadsView.vue' import SkinSiteDownloadsView from './SkinSiteDownloadsView.vue'
@@ -31,7 +32,20 @@ const messages = defineMessages({
const YGGDRASIL_URL = 'https://skin.starlight.cool/yggdrasil' const YGGDRASIL_URL = 'https://skin.starlight.cool/yggdrasil'
const loginModal = ref<InstanceType<typeof SkinSiteLoginModal>>() const loginModal = ref<InstanceType<typeof SkinSiteLoginModal>>()
const profileModal = ref<InstanceType<typeof SkinSiteProfileModal>>() const profileModal = ref<InstanceType<typeof SkinSiteProfileModal>>()
const view = ref<'home' | 'players' | 'downloads' | 'map' | 'review' | 'bugs'>('home') const view = ref<'home' | 'players' | 'downloads' | 'map' | 'review' | 'bugs' | 'bug-detail'>(
'home',
)
const selectedBugId = ref<number | null>(null)
function openBug(id: number) {
selectedBugId.value = id
view.value = 'bug-detail'
}
function backToBugList() {
selectedBugId.value = null
view.value = 'bugs'
}
async function copyAddress() { async function copyAddress() {
try { try {
@@ -50,7 +64,12 @@ async function copyAddress() {
<SkinSiteDownloadsView v-else-if="view === 'downloads'" @back="view = 'home'" /> <SkinSiteDownloadsView v-else-if="view === 'downloads'" @back="view = 'home'" />
<SkinSiteMapView v-else-if="view === 'map'" @back="view = 'home'" /> <SkinSiteMapView v-else-if="view === 'map'" @back="view = 'home'" />
<SkinSiteReviewView v-else-if="view === 'review'" @back="view = 'home'" /> <SkinSiteReviewView v-else-if="view === 'review'" @back="view = 'home'" />
<SkinSiteBugListView v-else-if="view === 'bugs'" @back="view = 'home'" /> <SkinSiteBugListView v-else-if="view === 'bugs'" @back="view = 'home'" @open="openBug" />
<SkinSiteBugDetailView
v-else-if="view === 'bug-detail' && selectedBugId !== null"
:issue-id="selectedBugId"
@back="backToBugList"
/>
<template v-else> <template v-else>
<header class="flex flex-col items-center gap-1 text-center"> <header class="flex flex-col items-center gap-1 text-center">
+51 -1
View File
@@ -11687,5 +11687,55 @@
"app.shortcut-settings.page-up-action-description": {"message": "Moves the page you are reading up by one screen."}, "app.shortcut-settings.page-up-action-description": {"message": "Moves the page you are reading up by one screen."},
"app.shortcut-settings.scroll-description": {"message": "Move through long pages with the keyboard."}, "app.shortcut-settings.scroll-description": {"message": "Move through long pages with the keyboard."},
"app.shortcut-settings.scroll-title": {"message": "Quick scrolling"}, "app.shortcut-settings.scroll-title": {"message": "Quick scrolling"},
"app.settings.tabs.shortcut-settings": {"message": "Keyboard shortcuts"} "app.settings.tabs.shortcut-settings": {"message": "Keyboard shortcuts"},
"app.skin-site.bugs.title": {"message": "Bugs & suggestions"},
"app.skin-site.bugs.back": {"message": "Back to home"},
"app.skin-site.bugs.subtitle": {"message": "Report issues and follow their progress."},
"app.skin-site.bugs.loading": {"message": "Loading…"},
"app.skin-site.bugs.failed": {"message": "Could not load issues."},
"app.skin-site.bugs.empty": {"message": "No issues match these filters."},
"app.skin-site.bugs.all": {"message": "All"},
"app.skin-site.bugs.more": {"message": "Load more"},
"app.skin-site.bugs.open-only": {"message": "Open only"},
"app.skin-site.bugs.comments": {"message": "{count} comments"},
"app.skin-site.bugs.type.bug": {"message": "Bug"},
"app.skin-site.bugs.type.feature": {"message": "Feature"},
"app.skin-site.bugs.type.improvement": {"message": "Improvement"},
"app.skin-site.bugs.status.pending": {"message": "Pending"},
"app.skin-site.bugs.status.confirmed": {"message": "Confirmed"},
"app.skin-site.bugs.status.needs-info": {"message": "Needs info"},
"app.skin-site.bugs.status.in-progress": {"message": "In progress"},
"app.skin-site.bugs.status.verifying": {"message": "Verifying"},
"app.skin-site.bugs.status.closed": {"message": "Closed"},
"app.skin-site.bugs.priority.critical": {"message": "Critical"},
"app.skin-site.bugs.priority.high": {"message": "High"},
"app.skin-site.bugs.priority.normal": {"message": "Normal"},
"app.skin-site.bugs.priority.low": {"message": "Low"},
"app.skin-site.bugs.resolution.fixed": {"message": "Fixed"},
"app.skin-site.bugs.resolution.wont-fix": {"message": "Won’t fix"},
"app.skin-site.bugs.resolution.by-design": {"message": "By design"},
"app.skin-site.bugs.resolution.duplicate": {"message": "Duplicate"},
"app.skin-site.bugs.resolution.cannot-reproduce": {"message": "Cannot reproduce"},
"app.skin-site.bugs.resolution.invalid": {"message": "Invalid"},
"app.skin-site.bug-detail.back": {"message": "Back to list"},
"app.skin-site.bug-detail.loading": {"message": "Loading…"},
"app.skin-site.bug-detail.failed": {"message": "Could not load this issue."},
"app.skin-site.bug-detail.issue-no": {"message": "Issue #{id}"},
"app.skin-site.bug-detail.by": {"message": "by {name}"},
"app.skin-site.bug-detail.assignee": {"message": "Assignee: {name}"},
"app.skin-site.bug-detail.unassigned": {"message": "Unassigned"},
"app.skin-site.bug-detail.description-empty": {"message": "No description provided."},
"app.skin-site.bug-detail.resolution": {"message": "Resolution: {name}"},
"app.skin-site.bug-detail.comments-title": {"message": "Comments"},
"app.skin-site.bug-detail.comments-empty": {"message": "No comments yet. Be the first to reply."},
"app.skin-site.bug-detail.comments-loading": {"message": "Loading comments…"},
"app.skin-site.bug-detail.comment-placeholder": {"message": "Write a comment…"},
"app.skin-site.bug-detail.comment-submit": {"message": "Comment"},
"app.skin-site.bug-detail.comment-sending": {"message": "Sending…"},
"app.skin-site.bug-detail.comment-empty-error": {"message": "Comment cannot be empty."},
"app.skin-site.bug-detail.comment-posted": {"message": "Comment posted."},
"app.skin-site.bug-detail.comment-deleted": {"message": "Comment deleted."},
"app.skin-site.bug-detail.delete": {"message": "Delete"},
"app.skin-site.bug-detail.deleting": {"message": "Deleting…"},
"app.skin-site.bug-detail.system": {"message": "System"}
} }
+51 -1
View File
@@ -11518,5 +11518,55 @@
"app.shortcut-settings.page-up-action-description": {"message": "将当前页面向上移动一屏。"}, "app.shortcut-settings.page-up-action-description": {"message": "将当前页面向上移动一屏。"},
"app.shortcut-settings.scroll-description": {"message": "使用键盘在长页面中快速滚动。"}, "app.shortcut-settings.scroll-description": {"message": "使用键盘在长页面中快速滚动。"},
"app.shortcut-settings.scroll-title": {"message": "快速滚动"}, "app.shortcut-settings.scroll-title": {"message": "快速滚动"},
"app.settings.tabs.shortcut-settings": {"message": "键盘快捷键"} "app.settings.tabs.shortcut-settings": {"message": "键盘快捷键"},
"app.skin-site.bugs.title": {"message": "问题与建议"},
"app.skin-site.bugs.back": {"message": "返回主页"},
"app.skin-site.bugs.subtitle": {"message": "反馈问题并跟踪处理进度。"},
"app.skin-site.bugs.loading": {"message": "加载中…"},
"app.skin-site.bugs.failed": {"message": "无法加载问题列表。"},
"app.skin-site.bugs.empty": {"message": "没有符合筛选条件的问题。"},
"app.skin-site.bugs.all": {"message": "全部"},
"app.skin-site.bugs.more": {"message": "加载更多"},
"app.skin-site.bugs.open-only": {"message": "仅显示未关闭"},
"app.skin-site.bugs.comments": {"message": "{count} 条评论"},
"app.skin-site.bugs.type.bug": {"message": "缺陷"},
"app.skin-site.bugs.type.feature": {"message": "功能建议"},
"app.skin-site.bugs.type.improvement": {"message": "改进"},
"app.skin-site.bugs.status.pending": {"message": "待处理"},
"app.skin-site.bugs.status.confirmed": {"message": "已确认"},
"app.skin-site.bugs.status.needs-info": {"message": "待补充信息"},
"app.skin-site.bugs.status.in-progress": {"message": "处理中"},
"app.skin-site.bugs.status.verifying": {"message": "验证中"},
"app.skin-site.bugs.status.closed": {"message": "已关闭"},
"app.skin-site.bugs.priority.critical": {"message": "紧急"},
"app.skin-site.bugs.priority.high": {"message": "高"},
"app.skin-site.bugs.priority.normal": {"message": "普通"},
"app.skin-site.bugs.priority.low": {"message": "低"},
"app.skin-site.bugs.resolution.fixed": {"message": "已修复"},
"app.skin-site.bugs.resolution.wont-fix": {"message": "不予修复"},
"app.skin-site.bugs.resolution.by-design": {"message": "设计如此"},
"app.skin-site.bugs.resolution.duplicate": {"message": "重复问题"},
"app.skin-site.bugs.resolution.cannot-reproduce": {"message": "无法复现"},
"app.skin-site.bugs.resolution.invalid": {"message": "无效"},
"app.skin-site.bug-detail.back": {"message": "返回列表"},
"app.skin-site.bug-detail.loading": {"message": "加载中…"},
"app.skin-site.bug-detail.failed": {"message": "无法加载该问题。"},
"app.skin-site.bug-detail.issue-no": {"message": "问题 #{id}"},
"app.skin-site.bug-detail.by": {"message": "由 {name} 提交"},
"app.skin-site.bug-detail.assignee": {"message": "负责人:{name}"},
"app.skin-site.bug-detail.unassigned": {"message": "未分配"},
"app.skin-site.bug-detail.description-empty": {"message": "未提供描述。"},
"app.skin-site.bug-detail.resolution": {"message": "处理结果:{name}"},
"app.skin-site.bug-detail.comments-title": {"message": "评论"},
"app.skin-site.bug-detail.comments-empty": {"message": "还没有评论,来抢沙发吧。"},
"app.skin-site.bug-detail.comments-loading": {"message": "加载评论中…"},
"app.skin-site.bug-detail.comment-placeholder": {"message": "写下你的评论…"},
"app.skin-site.bug-detail.comment-submit": {"message": "发表评论"},
"app.skin-site.bug-detail.comment-sending": {"message": "发送中…"},
"app.skin-site.bug-detail.comment-empty-error": {"message": "评论内容不能为空。"},
"app.skin-site.bug-detail.comment-posted": {"message": "评论已发表。"},
"app.skin-site.bug-detail.comment-deleted": {"message": "评论已删除。"},
"app.skin-site.bug-detail.delete": {"message": "删除"},
"app.skin-site.bug-detail.deleting": {"message": "删除中…"},
"app.skin-site.bug-detail.system": {"message": "系统"}
} }