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' },
|
||||
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 hasMore = ref(false)
|
||||
@@ -221,7 +221,8 @@ onMounted(() => load(true))
|
||||
<div
|
||||
v-for="issue in items"
|
||||
: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">
|
||||
<span
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ref } from 'vue'
|
||||
|
||||
import SkinSiteAccountCard from './SkinSiteAccountCard.vue'
|
||||
import SkinSiteAnnouncements from './SkinSiteAnnouncements.vue'
|
||||
import SkinSiteBugDetailView from './SkinSiteBugDetailView.vue'
|
||||
import SkinSiteBugListView from './SkinSiteBugListView.vue'
|
||||
import SkinSiteDonationCard from './SkinSiteDonationCard.vue'
|
||||
import SkinSiteDownloadsView from './SkinSiteDownloadsView.vue'
|
||||
@@ -31,7 +32,20 @@ const messages = defineMessages({
|
||||
const YGGDRASIL_URL = 'https://skin.starlight.cool/yggdrasil'
|
||||
const loginModal = ref<InstanceType<typeof SkinSiteLoginModal>>()
|
||||
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() {
|
||||
try {
|
||||
@@ -50,7 +64,12 @@ async function copyAddress() {
|
||||
<SkinSiteDownloadsView v-else-if="view === 'downloads'" @back="view = 'home'" />
|
||||
<SkinSiteMapView v-else-if="view === 'map'" @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>
|
||||
<header class="flex flex-col items-center gap-1 text-center">
|
||||
|
||||
@@ -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.scroll-description": {"message": "Move through long pages with the keyboard."},
|
||||
"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"}
|
||||
}
|
||||
@@ -11518,5 +11518,55 @@
|
||||
"app.shortcut-settings.page-up-action-description": {"message": "将当前页面向上移动一屏。"},
|
||||
"app.shortcut-settings.scroll-description": {"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": "系统"}
|
||||
}
|
||||
Reference in new issue
Block a user