diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json index f10352a..b24ed39 100644 --- a/apps/app-frontend/package.json +++ b/apps/app-frontend/package.json @@ -50,7 +50,8 @@ "vue": "^3.5.13", "vue-i18n": "^10.0.0", "vue-router": "^4.6.0", - "vuedraggable": "^4.1.0" + "vuedraggable": "^4.1.0", + "xxhash-wasm": "1.1.0" }, "devDependencies": { "@eslint/compat": "^1.1.1", diff --git a/apps/app-frontend/src/components/ui/skin-site/ChatUserPicker.vue b/apps/app-frontend/src/components/ui/skin-site/ChatUserPicker.vue new file mode 100644 index 0000000..ad24d06 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/ChatUserPicker.vue @@ -0,0 +1,290 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/RoomAvatar.vue b/apps/app-frontend/src/components/ui/skin-site/RoomAvatar.vue new file mode 100644 index 0000000..c991d57 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/RoomAvatar.vue @@ -0,0 +1,63 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/RoomAvatarEditor.vue b/apps/app-frontend/src/components/ui/skin-site/RoomAvatarEditor.vue new file mode 100644 index 0000000..9652dbe --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/RoomAvatarEditor.vue @@ -0,0 +1,161 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue b/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue index bff6268..76162e3 100644 --- a/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue +++ b/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue @@ -4,12 +4,19 @@ import { computed, ref } from 'vue' import { skinSiteUser } from '@/composables/skin-site-session' +import AdminAccessAuditPage from './admin/AdminAccessAuditPage.vue' import AdminBroadcastPage from './admin/AdminBroadcastPage.vue' +import AdminChatPage from './admin/AdminChatPage.vue' +import AdminDonationsPage from './admin/AdminDonationsPage.vue' +import AdminDownloadsPage from './admin/AdminDownloadsPage.vue' import AdminLauncherPage from './admin/AdminLauncherPage.vue' +import AdminModsPage from './admin/AdminModsPage.vue' import AdminPlayersPage from './admin/AdminPlayersPage.vue' import AdminReviewPage from './admin/AdminReviewPage.vue' +import AdminServersPage from './admin/AdminServersPage.vue' import AdminSummaryPage from './admin/AdminSummaryPage.vue' import AdminUsersPage from './admin/AdminUsersPage.vue' +import AdminWikiPage from './admin/AdminWikiPage.vue' const emit = defineEmits<{ (e: 'back'): void }>() @@ -72,7 +79,9 @@ const navGroups = computed(() => [ id: 'security', label: formatMessage(messages.groupSecurity), items: [ - { key: 'servers', label: formatMessage(messages.itemServers) }, + ...(hasWebAdmin.value + ? [{ key: 'servers' as AdminKey, label: formatMessage(messages.itemServers) }] + : []), { key: 'summary', label: formatMessage(messages.itemSummary) }, { key: 'access-audit', label: formatMessage(messages.itemAccessAudit) }, ], @@ -93,7 +102,9 @@ const navGroups = computed(() => [ { key: 'broadcast', label: formatMessage(messages.itemBroadcast) }, { key: 'chat', label: formatMessage(messages.itemChat) }, { key: 'wiki', label: formatMessage(messages.itemWiki) }, - { key: 'donations', label: formatMessage(messages.itemDonations) }, + ...(hasWebAdmin.value + ? [{ key: 'donations' as AdminKey, label: formatMessage(messages.itemDonations) }] + : []), ], }, { @@ -112,6 +123,11 @@ const hasAdminAccess = computed(() => { return permissions.includes('WEB_ADMIN') || permissions.includes('SERVER_ADMIN') }) +const hasWebAdmin = computed(() => { + const permissions = skinSiteUser.value?.permissions ?? [] + return permissions.includes('WEB_ADMIN') +}) + const current = ref('summary') diff --git a/apps/app-frontend/src/components/ui/skin-site/admin/AdminWikiPage.vue b/apps/app-frontend/src/components/ui/skin-site/admin/AdminWikiPage.vue new file mode 100644 index 0000000..f0bd173 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/admin/AdminWikiPage.vue @@ -0,0 +1,1258 @@ + + diff --git a/apps/app-frontend/src/components/ui/skin-site/admin/ModUploadModal.vue b/apps/app-frontend/src/components/ui/skin-site/admin/ModUploadModal.vue new file mode 100644 index 0000000..a8485e2 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/admin/ModUploadModal.vue @@ -0,0 +1,440 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/room-avatar-cache.ts b/apps/app-frontend/src/components/ui/skin-site/room-avatar-cache.ts new file mode 100644 index 0000000..4e0a88c --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/room-avatar-cache.ts @@ -0,0 +1,30 @@ +import { skinSiteChatRoomAvatarFetch } from '@/helpers/skin-site-api' + +const avatarUrls = new Map() +const pending = new Map>() + +export function loadCachedRoomAvatar( + roomId: number, + attachmentId: number, +): Promise { + const cached = avatarUrls.get(attachmentId) + if (cached) return Promise.resolve(cached) + const running = pending.get(attachmentId) + if (running) return running + + const request = skinSiteChatRoomAvatarFetch(roomId).then((blob) => { + pending.delete(attachmentId) + if (!blob) return null + const url = URL.createObjectURL(blob) + avatarUrls.set(attachmentId, url) + return url + }) + pending.set(attachmentId, request) + return request +} + +export function forgetRoomAvatar(attachmentId: number) { + const url = avatarUrls.get(attachmentId) + if (url) URL.revokeObjectURL(url) + avatarUrls.delete(attachmentId) +} diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/WikiAttachmentNode.vue b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiAttachmentNode.vue new file mode 100644 index 0000000..4f73b5a --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiAttachmentNode.vue @@ -0,0 +1,74 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNodeRenderer.vue b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNodeRenderer.vue new file mode 100644 index 0000000..0c5288f --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNodeRenderer.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNotice.vue b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNotice.vue new file mode 100644 index 0000000..95ae146 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiNotice.vue @@ -0,0 +1,32 @@ + + + diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/wiki-color.ts b/apps/app-frontend/src/components/ui/skin-site/wiki/wiki-color.ts new file mode 100644 index 0000000..e64cb4e --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/wiki-color.ts @@ -0,0 +1,18 @@ +export function safeWikiColor(value: unknown): string | null { + if (typeof value !== 'string') return null + const color = value.trim() + return color.length <= 80 && + /^(?:[a-z]{1,20}|#[\da-f]{3}|#[\da-f]{4}|#[\da-f]{6}|#[\da-f]{8}|rgba?\([\d\s.,%]+\))$/i.test(color) + ? color + : null +} + +export function safeExternalUrl(url?: string | null): string { + if (!url) return '#' + try { + const parsed = new URL(url) + return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? parsed.toString() : '#' + } catch { + return '#' + } +} diff --git a/apps/app-frontend/src/helpers/skin-site-api.ts b/apps/app-frontend/src/helpers/skin-site-api.ts index 5f322ce..c3bba36 100644 --- a/apps/app-frontend/src/helpers/skin-site-api.ts +++ b/apps/app-frontend/src/helpers/skin-site-api.ts @@ -389,12 +389,12 @@ export function skinSitePasswordReset(payload: { // Donations & announcements // --------------------------------------------------------------------------- -export interface SkinSiteDonationSummary { +export interface SkinSitePublicDonationSummary { totalAmount: string donorCount: number } -export interface SkinSiteDonationEntry { +export interface SkinSitePublicDonationEntry { id: number rank: number username: string @@ -402,14 +402,16 @@ export interface SkinSiteDonationEntry { amount: string } -export interface SkinSiteDonationPage { - items: SkinSiteDonationEntry[] - summary: SkinSiteDonationSummary +export interface SkinSitePublicDonationPage { + items: SkinSitePublicDonationEntry[] + summary: SkinSitePublicDonationSummary hasMore: boolean } -export function skinSiteDonationSummary(): Promise> { - return skinSiteRequest('donation/summary', { auth: false }) +export function skinSitePublicDonationSummary(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('donation/summary', { auth: false }) } export function skinSiteDonationList(offset = 0): Promise> { @@ -487,71 +489,73 @@ export async function skinSiteUploadAvatar(blob: Blob): Promise> { +}): Promise> { const query = new URLSearchParams({ limit: String(options?.limit ?? 200), offset: String(options?.offset ?? 0), }) if (options?.category) query.set('category', options.category) - return skinSiteRequest(`download/list?${query.toString()}`, { + return skinSiteRequest(`download/list?${query.toString()}`, { auth: false, }) } -export function skinSiteDownloadCount(category?: string): Promise> { +export function skinSitePublicDownloadCount(category?: string): Promise> { const suffix = category ? `?category=${encodeURIComponent(category)}` : '' return skinSiteRequest(`download/count${suffix}`, { auth: false }) } -export function skinSiteDownloadCategories(): Promise< - SkinSiteResponse +export function skinSitePublicDownloadCategories(): Promise< + SkinSiteResponse > { - return skinSiteRequest('download/category/list', { auth: false }) + return skinSiteRequest('download/category/list', { + auth: false, + }) } // --------------------------------------------------------------------------- // Maps // --------------------------------------------------------------------------- -export interface SkinSiteMapEntry { +export interface SkinSitePublicMapEntry { name: string url: string } -export interface SkinSiteMapServer { +export interface SkinSitePublicMapServer { name: string internal: boolean - maps: SkinSiteMapEntry[] + maps: SkinSitePublicMapEntry[] } -export function skinSiteMapServers(): Promise> { - return skinSiteRequest('maps', { auth: false }) +export function skinSitePublicMapServers(): Promise> { + return skinSiteRequest('maps', { auth: false }) } // --------------------------------------------------------------------------- // Player review (public side) @@ -560,32 +564,32 @@ export function skinSiteMapServers(): Promise> { + const query = new URLSearchParams({ + limit: String(params?.limit ?? 200), + offset: String(params?.offset ?? 0), + }) + if (params?.category?.trim()) query.set('category', params.category.trim()) + return skinSiteRequest('download/list?' + query.toString()) +} + +export function skinSiteDownloadItemCount(category?: string): Promise> { + const query = new URLSearchParams() + if (category?.trim()) query.set('category', category.trim()) + const suffix = query.toString() ? '?' + query.toString() : '' + return skinSiteRequest('download/count' + suffix) +} + +export function skinSiteAddDownloadItem( + data: SkinSiteDownloadItemInput, +): Promise> { + return skinSiteRequest('download/add', { method: 'POST', body: data }) +} + +export function skinSiteUpdateDownloadItem( + id: number, + data: SkinSiteDownloadItemInput, +): Promise> { + return skinSiteRequest('download/update/' + id, { method: 'POST', body: data }) +} + +export function skinSiteDeleteDownloadItem(id: number): Promise> { + return skinSiteRequest('download/' + id, { method: 'DELETE' }) +} + +export function skinSiteDownloadCategories(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('download/category/list') +} + +export function skinSiteAddDownloadCategory(name: string): Promise> { + return skinSiteRequest('download/category/add/' + encodeURIComponent(name), { + method: 'POST', + }) +} + +export function skinSiteDeleteDownloadCategory(id: number): Promise> { + return skinSiteRequest('download/category/' + id, { method: 'DELETE' }) +} + +export function skinSiteBindDownloadCategory( + infoId: number, + categoryId: number, +): Promise> { + return skinSiteRequest('download/category/bind/' + infoId + '/' + categoryId, { + method: 'POST', + }) +} + +export function skinSiteUnbindDownloadCategory( + infoId: number, + categoryId: number, +): Promise> { + return skinSiteRequest('download/category/unbind/' + infoId + '/' + categoryId, { + method: 'POST', + }) +} + +export interface SkinSiteMapServer { + name: string + internal: boolean + maps: Array<{ name: string; url: string }> +} + +export function skinSiteMapServers(): Promise> { + return skinSiteRequest('maps') +} + +export function skinSiteSaveMapServers( + servers: SkinSiteMapServer[], +): Promise> { + return skinSiteRequest('maps', { method: 'PUT', body: servers }) +} + +export interface SkinSiteAccessAuditEntry { + id: number + subject: string + action: string + sourceIp: string + proxy: boolean + reportedPlayerIp: string | null + internalServer: boolean + count: number + firstAt: number + lastAt: number +} + +export interface SkinSiteAccessAuditPage { + items: SkinSiteAccessAuditEntry[] + nextCursor: number | null + retentionDays: number + recordLimit: number +} + +export function skinSiteAccessAudit(params?: { + before?: number + subject?: string + ip?: string + action?: string +}): Promise> { + const query = new URLSearchParams() + if (params) { + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== '') query.set(key, String(value)) + } + } + const suffix = query.toString() ? '?' + query.toString() : '' + return skinSiteRequest('admin/access-audit' + suffix) +} + +export function skinSiteUpdateAccessAuditSettings( + retentionDays: number, +): Promise> { + return skinSiteRequest<{ retentionDays: number }>('admin/access-audit/settings', { + method: 'PUT', + body: { retentionDays }, + }) +} + +export interface SkinSiteDonationEntry { + id: number + rank: number + username: string + avatar: string | null + amount: string +} + +export interface SkinSiteDonationSummary { + totalAmount: string + donorCount: number +} + +export interface SkinSiteDonationAdminEntry { + donor: SkinSiteDonationEntry + userUuid: string | null + qq: string | null + external: boolean + version: number + updatedAt: number +} + +export interface SkinSiteDonationAdminPage { + items: SkinSiteDonationAdminEntry[] + summary: SkinSiteDonationSummary + hasMore: boolean +} + +export function skinSiteAdminDonations( + offset = 0, +): Promise> { + return skinSiteRequest('donation/admin/list?limit=20&offset=' + offset) +} + +export function skinSiteFindAdminDonations(identity: { + userUuid?: string + qq?: string +}): Promise> { + const query = new URLSearchParams() + if (identity.userUuid) query.set('userUuid', identity.userUuid) + if (identity.qq) query.set('qq', identity.qq) + return skinSiteRequest('donation/admin/find?' + query.toString()) +} + +export interface SkinSiteDonationCreate { + amount: string + userUuid?: string + qq?: string + displayName?: string +} + +export function skinSiteCreateDonation( + data: SkinSiteDonationCreate, +): Promise> { + return skinSiteRequest('donation/admin', { method: 'POST', body: data }) +} + +export interface SkinSiteDonationUpdate { + amount: string + version: number + displayName?: string + mode?: 'REPLACE' | 'APPEND' +} + +export function skinSiteUpdateDonation( + id: number, + data: SkinSiteDonationUpdate, +): Promise> { + return skinSiteRequest('donation/admin/' + id, { method: 'PUT', body: data }) +} + +export function skinSiteDeleteDonation( + id: number, + version: number, +): Promise> { + return skinSiteRequest('donation/admin/' + id + '?version=' + version, { + method: 'DELETE', + }) +} + +export type SkinSiteModLoaderType = 'FABRIC' | 'FORGE' + +export interface SkinSiteModMetadata { + modId?: string + name?: string + version?: string + iconBytes?: string + loaderType: SkinSiteModLoaderType[] +} + +export interface SkinSiteModInfo { + domainRules: string[] + hash: string + name: string + size: number + metadata: SkinSiteModMetadata +} + +export interface SkinSiteModRuleTag { + id: number + name: string + rules: string[] +} + +export function skinSiteModCount(params?: { + serverAddr?: string + ruleTagId?: number | null +}): Promise> { + const query = new URLSearchParams() + const addr = params?.serverAddr?.trim() + if (addr) query.set('server_addr', addr) + if (params?.ruleTagId != null) query.set('rule_tag_id', String(params.ruleTagId)) + const suffix = query.toString() ? '?' + query.toString() : '' + return skinSiteRequest('query/mod/number' + suffix, { type: 'mod' }) +} + +export function skinSiteModList(params: { + limit: number + offset: number + serverAddr?: string + ruleTagId?: number | null +}): Promise> { + return skinSiteRequest('query/mod/list', { + method: 'POST', + type: 'mod', + body: { + offset: params.offset || 0, + limit: params.limit || 0, + serverAddr: params.serverAddr?.trim() || undefined, + ruleTagId: params.ruleTagId ?? undefined, + }, + }) +} + +export function skinSiteModSearch( + name: string, + ruleTagId?: number | null, +): Promise> { + const query = new URLSearchParams() + if (ruleTagId != null) query.set('rule_tag_id', String(ruleTagId)) + const suffix = query.toString() ? '?' + query.toString() : '' + return skinSiteRequest( + 'query/mod/search/' + encodeURIComponent(name) + suffix, + { type: 'mod' }, + ) +} + +export function skinSiteUpdateModInfo(info: SkinSiteModInfo): Promise> { + return skinSiteRequest('update/info', { method: 'POST', type: 'mod', body: info }) +} + +export function skinSiteDeleteModFile(hash: string): Promise> { + return skinSiteRequest(hash, { method: 'DELETE', type: 'mod' }) +} + +export function skinSiteDeleteMultiModFile(hashes: string[]): Promise> { + return skinSiteRequest('delete/multimod', { + method: 'POST', + type: 'mod', + body: { modHashes: hashes }, + }) +} + +export function skinSiteModRuleTags(): Promise> { + return skinSiteRequest('rule-tags', { type: 'mod' }) +} + +export function skinSiteCreateModRuleTag( + name: string, + rules: string[], +): Promise> { + return skinSiteRequest('rule-tags', { + method: 'POST', + type: 'mod', + body: { name, rules }, + }) +} + +export function skinSiteUpdateModRuleTag( + id: number, + name: string, + rules: string[], +): Promise> { + return skinSiteRequest('rule-tags/' + id, { + method: 'POST', + type: 'mod', + body: { name, rules }, + }) +} + +export function skinSiteDeleteModRuleTag(id: number): Promise> { + return skinSiteRequest('rule-tags/' + id, { method: 'DELETE', type: 'mod' }) +} + +export interface SkinSiteModRuntimeRelease { + id: number + version: string + fileName: string + sha256: string + fileSize: number + uploadedBy: string + uploadedAt: number + publishedAt: number | null + active: boolean + available: boolean + downloadUrl: string +} + +export interface SkinSiteModRuntimeAdminState { + manifestUrl: string + releases: SkinSiteModRuntimeRelease[] +} + +export function skinSiteModRuntimeReleases(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('runtime/admin', { type: 'mod' }) +} + +export function skinSiteUploadModRuntime( + version: string, + file: File, +): Promise> { + const form = new FormData() + form.append('runtime', file, file.name) + return skinSiteRequest( + 'runtime/admin/upload?version=' + encodeURIComponent(version), + { method: 'POST', type: 'mod', form }, + ) +} + +export function skinSiteActivateModRuntime( + id: number, +): Promise> { + return skinSiteRequest('runtime/admin/' + id + '/activate', { + method: 'POST', + type: 'mod', + }) +} + +export function skinSiteDeleteModRuntime(id: number): Promise> { + return skinSiteRequest('runtime/admin/' + id, { method: 'DELETE', type: 'mod' }) +} + +export type SkinSiteWikiReadPolicy = 'PUBLIC' | 'MEMBER' +export type SkinSiteWikiEditPolicy = 'MEMBER' | 'WEB_ADMIN' + +export interface SkinSiteWikiNode { + type: string + color?: string | null + align?: 'left' | 'center' | 'right' | 'justify' | null + start?: number | null + colspan?: number | null + rowspan?: number | null + variant?: string | null + iconFamily?: string | null + iconName?: string | null + iconVersion?: number | null + linkTitle?: string | null + linkLabel?: string | null + tooltip?: string | null + exists?: boolean | null + text?: string | null + level?: number | null + href?: string | null + title?: string | null + children: SkinSiteWikiNode[] + rows: SkinSiteWikiNode[][][] +} + +export interface SkinSiteWikiAttachmentInfo { + id: number + versionId: number + name: string + mime: string + size: number + image: boolean + uploaderName: string + createdAt: number +} + +export interface SkinSiteWikiPageDetail { + title: string + revisionId: number + source: string + nodes: SkinSiteWikiNode[] + categories: string[] + updatedAt: number + authorName: string + summary: string + readPolicy: SkinSiteWikiReadPolicy + editPolicy: SkinSiteWikiEditPolicy + redirectTarget?: string | null + canEdit: boolean + watched: boolean +} + +export interface SkinSiteWikiSettings { + domains: string[] + homePage: string + defaultSkin: string + maxPageUtf8Bytes: number + maxAttachmentBytes: number +} + +export interface SkinSiteWikiAuditInfo { + id: number + actorName: string + action: string + target?: string | null + detail?: string | null + createdAt: number +} + +export interface SkinSiteWikiAuditPage { + items: SkinSiteWikiAuditInfo[] + nextCursor?: number | null +} + +export function skinSiteWikiPage(path: string): Promise> { + return skinSiteRequest('wiki/page?path=' + encodeURIComponent(path)) +} + +export function skinSiteWikiProtectPage( + path: string, + readPolicy: SkinSiteWikiReadPolicy, + editPolicy: SkinSiteWikiEditPolicy, +): Promise> { + return skinSiteRequest('wiki/admin/protect', { + method: 'POST', + body: { path, readPolicy, editPolicy }, + }) +} + +export function skinSiteWikiSetHomePage(path: string): Promise> { + return skinSiteRequest('wiki/admin/homepage', { method: 'POST', body: { path } }) +} + +export function skinSiteWikiSetPageDeleted( + path: string, + deleted: boolean, +): Promise> { + return skinSiteRequest('wiki/admin/delete', { + method: 'POST', + body: { path, deleted }, + }) +} + +export function skinSiteWikiAudit( + before?: number, +): Promise> { + const suffix = before ? '?before=' + before : '' + return skinSiteRequest('wiki/admin/audit' + suffix) +} + +export function skinSiteWikiSettings(): Promise> { + return skinSiteRequest('wiki/settings') +} + +export function skinSiteWikiUpdateSettings( + settings: Partial, +): Promise> { + return skinSiteRequest('wiki/admin/settings', { + method: 'POST', + body: settings, + }) +} + + +export interface SkinSiteReviewPaperSummary { + id: string + enabled: boolean + title: string + annotation?: string | null + mode: SkinSiteReviewMode + passScore: number + targetPermissions: string[] + version: number + questionCount: number + totalScore: number + updatedAt: number + prerequisiteMode: 'ANY' | 'ALL' + prerequisitePermissions: string[] + eligible: boolean +} + +export interface SkinSiteReviewMailConfig { + enabled: boolean + subject: string + message: string + includeInviteCode: boolean + extraText: string +} + +export interface SkinSiteReviewConfigData { + preventDuplicate?: boolean + mail?: SkinSiteReviewMailConfig + id: string + enabled: boolean + title: string + annotation?: string | null + mode: SkinSiteReviewMode + passScore: number + targetPermissions: string[] + version: number + questions: SkinSiteReviewQuestion[] + updatedAt: number + prerequisiteMode: 'ANY' | 'ALL' + prerequisitePermissions: string[] +} + +export function skinSiteReviewPaperList(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('review/admin/papers') +} + +export function skinSiteReviewPaperAdmin( + paperId: string, +): Promise> { + return skinSiteRequest( + 'review/admin/papers/' + encodeURIComponent(paperId), + ) +} + +export function skinSiteCreateReviewPaperAdmin( + data: SkinSiteReviewConfigData, +): Promise> { + return skinSiteRequest('review/admin/papers', { + method: 'POST', + body: data, + }) +} + +export function skinSiteUpdateReviewPaperAdmin( + paperId: string, + data: SkinSiteReviewConfigData, +): Promise> { + return skinSiteRequest( + 'review/admin/papers/' + encodeURIComponent(paperId), + { method: 'PUT', body: data }, + ) +} + +export interface SkinSiteBotInfo { + exists: boolean + username?: string | null + active: boolean + creationBanned: boolean + tokenHint?: string | null + createdAt?: number | null + updatedAt?: number | null +} + +export interface SkinSiteUserFrequencyLimit { + dropPercent?: number | null +} + +export type SkinSiteUserAdminAuditAction = + | 'USER_UPDATED' + | 'USER_DELETED' + | 'RATE_LIMIT_UPDATED' + | 'BOT_USERNAME_UPDATED' + | 'BOT_TOKEN_INVALIDATED' + | 'BOT_CREATION_BANNED' + | 'BOT_CREATION_UNBANNED' + +export interface SkinSiteUserAdminAuditChange { + field: string + before?: string | null + after?: string | null +} + +export interface SkinSiteUserAdminAuditInfo { + id: number + actorUuid: string + actorName: string + targetUuid: string + targetName: string + action: SkinSiteUserAdminAuditAction + changes: SkinSiteUserAdminAuditChange[] + createdAt: number +} + +export interface SkinSiteUserAdminAuditPage { + items: SkinSiteUserAdminAuditInfo[] + nextCursor?: number | null +} + +export interface SkinSiteInviteCode { + email: string + code: string +} + +export function skinSiteAdminBotInfo( + userUuid: string, +): Promise> { + return skinSiteRequest('admin/users/' + encodeURIComponent(userUuid) + '/bot') +} + +export function skinSiteAdminRenameBot( + userUuid: string, + username: string, +): Promise> { + return skinSiteRequest( + 'admin/users/' + encodeURIComponent(userUuid) + '/bot/username', + { method: 'POST', body: { username } }, + ) +} + +export function skinSiteAdminInvalidateBotToken( + userUuid: string, +): Promise> { + return skinSiteRequest( + 'admin/users/' + encodeURIComponent(userUuid) + '/bot/invalidate', + { method: 'POST' }, + ) +} + +export function skinSiteAdminSetBotCreationBanned( + userUuid: string, + banned: boolean, +): Promise> { + return skinSiteRequest( + 'admin/users/' + encodeURIComponent(userUuid) + '/bot/ban', + { method: 'POST', body: { banned } }, + ) +} + +export function skinSiteAdminUpdateUserFrequencyLimit( + userUuid: string, + dropPercent: number | null, +): Promise> { + return skinSiteRequest( + 'admin/users/' + encodeURIComponent(userUuid) + '/frequency-limit', + { method: 'POST', body: { dropPercent } }, + ) +} + +export function skinSiteAdminUserAudit(params?: { + targetUuid?: string + before?: number +}): Promise> { + const query = new URLSearchParams() + if (params?.targetUuid) query.set('targetUuid', params.targetUuid) + if (params?.before) query.set('before', String(params.before)) + const suffix = query.size > 0 ? '?' + query.toString() : '' + return skinSiteRequest('admin/users/audit' + suffix) +} + +export function skinSiteAdminInviteUser(email: string): Promise> { + return skinSiteRequest('admin/inviteCode', { + method: 'POST', + body: { email }, + }) +} + +export function skinSiteModHashFromFile(file: File): Promise { + return import('xxhash-wasm').then(async ({ default: xxhash }) => { + const { create64 } = await xxhash() + const hasher = create64() + const reader = file.stream().getReader() + for (;;) { + const { done, value } = await reader.read() + if (done) break + hasher.update(value) + } + const raw: bigint = hasher.digest() + const mask = (1n << 64n) - 1n + const v = raw & mask + const signed = v >= 1n << 63n ? v - (1n << 64n) : v + return signed.toString() + }) +} + +export function skinSiteModExistsByHash(hash: string): Promise> { + return skinSiteRequest('query/hash/' + encodeURIComponent(hash), { type: 'mod' }) +} + +export function skinSiteModExistsByName(modName: string): Promise> { + const q = new URLSearchParams({ mod_name: modName }) + return skinSiteRequest('query/name?' + q.toString(), { type: 'mod' }) +} + +export async function skinSiteUploadMod( + file: File, + ruleTagIds: number[] = [], + overwrite = false, +): Promise> { + const hash = await skinSiteModHashFromFile(file) + const form = new FormData() + form.append('mods', file, file.name) + const query = new URLSearchParams() + if (ruleTagIds.length) query.set('rule_tag_ids', ruleTagIds.join(',')) + if (overwrite) query.set('overwrite', 'true') + const suffix = query.size ? '?' + query.toString() : '' + return skinSiteRequest('upload/' + hash + suffix, { + method: 'POST', + type: 'mod', + form, + }) +} + +export function skinSiteUpdateMultiModInfo( + mods: SkinSiteModInfo[], +): Promise> { + return skinSiteRequest('update/info/multi', { + method: 'POST', + type: 'mod', + body: mods, + }) +} + +// --------------------------------------------------------------------------- +// Chat +// --------------------------------------------------------------------------- + +export type SkinSiteChatRoomVisibility = 'PUBLIC' | 'PRIVATE' +export type SkinSiteChatRoomKind = 'STANDARD' | 'SETTLEMENT' +export type SkinSiteChatPrerequisiteMode = 'ANY' | 'ALL' + +export interface SkinSiteChatRoomInfo { + id: number + name: string + description: string + visibility: SkinSiteChatRoomVisibility + joined: boolean + invited: boolean + manager: boolean + createdAt: number + kind: SkinSiteChatRoomKind + avatarAttachmentId?: number | null + voiceEnabled: boolean + voiceAvailable: boolean + voiceParticipantCount: number + voicePublisherCount: number + voicePublisherLimit: number + voiceParticipantLimit: number + voiceListenerLimit: number + prerequisiteMode: SkinSiteChatPrerequisiteMode + prerequisitePermissions: string[] + prerequisiteEligible: boolean +} + +export interface SkinSiteChatUploadInfo { + token: string + attachment: { + id: number + kind: 'IMAGE' | 'FILE' + name: string + size: number + } +} + +export interface SkinSiteChatRoomCreateInput { + name: string + description: string + visibility: SkinSiteChatRoomVisibility + kind?: SkinSiteChatRoomKind + prerequisiteMode: SkinSiteChatPrerequisiteMode + prerequisitePermissions: string[] +} + +export interface SkinSiteChatRoomUpdateInput { + name: string + description: string + visibility: SkinSiteChatRoomVisibility + prerequisiteMode: SkinSiteChatPrerequisiteMode + prerequisitePermissions: string[] +} + +export function skinSiteChatRoomList(): Promise> { + return skinSiteRequest('chat/admin/rooms') +} + +export function skinSiteChatRoomCreate( + data: SkinSiteChatRoomCreateInput, +): Promise> { + return skinSiteRequest('chat/admin/rooms', { + method: 'POST', + body: data, + }) +} + +export function skinSiteChatRoomUpdate( + id: number, + data: SkinSiteChatRoomUpdateInput, +): Promise> { + return skinSiteRequest('chat/rooms/' + id, { + method: 'PUT', + body: data, + }) +} + +export function skinSiteChatRoomDelete(id: number): Promise> { + return skinSiteRequest('chat/admin/rooms/' + id, { method: 'DELETE' }) +} + +export function skinSiteChatRoomAvatarUpdate( + roomId: number, + data: { imageToken?: string; removeImage?: boolean }, +): Promise> { + return skinSiteRequest('chat/rooms/' + roomId + '/avatar', { + method: 'PUT', + body: data, + }) +} + +export function skinSiteChatAttachmentUpload( + file: File, +): Promise> { + const form = new FormData() + form.append('file', file, file.name) + return skinSiteRequest('chat/attachments', { + method: 'POST', + form, + }) +} + +export async function skinSiteChatRoomAvatarFetch(roomId: number): Promise { + const url = `${SKIN_SITE_ORIGIN}${REQUEST_TYPE_PATH.starlight}/chat/rooms/${roomId}/avatar` + const headers: Record = { Accept: 'image/*' } + const token = getSkinSiteToken() + if (token) headers['Authorization'] = `Bearer ${token}` + try { + const response = await tauriFetch(url, { method: 'GET', headers }) + if (!response.ok) return null + const type = response.headers.get('content-type') ?? '' + if (type.includes('application/json') || type.includes('text/')) return null + return await response.blob() + } catch { + return null + } +} + +export interface SkinSiteChatUserInfo { + uuid: string + id: number + username: string + avatar?: string | null + email: string + qq?: number | null + permissions: string[] + banned: boolean + registerTime: number +} + +export interface SkinSiteChatAuditInfo { + id: number + actorUuid: string + actorName?: string + action: string + target?: string | null + createdAt: number +} + +export function skinSiteChatUserSearch( + keyword: string, + roomId?: number, + searchBy: 'user' | 'player' = 'user', +): Promise> { + const query = new URLSearchParams({ keyword }) + if (roomId !== undefined) query.set('room_id', String(roomId)) + if (searchBy === 'player') query.set('search_by', 'player') + return skinSiteRequest('chat/users/search?' + query.toString()) +} + +export function skinSiteChatManagerSet( + roomId: number, + userUuid: string, + manager: boolean, +): Promise> { + return skinSiteRequest('chat/admin/rooms/' + roomId + '/manager', { + method: 'POST', + body: { userUuid, manager }, + }) +} + +export function skinSiteChatSettlementOwnerAssign( + roomId: number, + userUuid: string, +): Promise> { + return skinSiteRequest('chat/settlements/' + roomId + '/owner', { + method: 'POST', + body: { userUuid }, + }) +} + +export function skinSiteChatAuditList( + roomId: number, +): Promise> { + return skinSiteRequest('chat/rooms/' + roomId + '/audit') +} + +export type SkinSiteChatVoiceRuntimeState = + | 'DISABLED' + | 'NOT_INSTALLED' + | 'DOWNLOADING' + | 'VERIFYING' + | 'EXTRACTING' + | 'STARTING' + | 'READY' + | 'FAILED' + | 'UNSUPPORTED_PLATFORM' + +export interface SkinSiteChatVoiceRuntimeInfo { + state: SkinSiteChatVoiceRuntimeState + version: string + progress?: number | null + message?: string | null + retryCount: number + publicUrl?: string | null +} + +export interface SkinSiteChatVoiceGlobalConfig { + enabled: boolean + runtimePath: string + mirrorBaseUrl: string + publicUrl: string + bindAddress: string + signalPort: number + rtcTcpPort: number + rtcUdpPort: number + startupTimeoutSeconds: number + useExternalIp: boolean + nodeIp: string +} + +export interface SkinSiteChatVoiceGlobalConfigUpdateResult { + config: SkinSiteChatVoiceGlobalConfig + runtime: SkinSiteChatVoiceRuntimeInfo + restarted: boolean +} + +export interface SkinSiteChatVoiceLogEntry { + id: number + timestamp: number + message: string +} + +export interface SkinSiteChatVoiceLogPage { + entries: SkinSiteChatVoiceLogEntry[] + latestId: number +} + +export interface SkinSiteChatVoiceRoomInfo { + roomId: number + enabled: boolean + available: boolean + publisherLimit: number + participantLimit: number + listenerLimit: number + participantCount: number + publisherCount: number + listenerCount: number + joined: boolean + observer: boolean + muted: boolean + bannedUntil?: number | null +} + +export function skinSiteChatVoiceRuntime(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('chat/voice/admin/runtime') +} + +export function skinSiteChatVoiceRuntimeRetry(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('chat/voice/admin/runtime/retry', { + method: 'POST', + }) +} + +export function skinSiteChatVoiceConfig(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('chat/voice/admin/config') +} + +export function skinSiteChatVoiceConfigUpdate( + data: SkinSiteChatVoiceGlobalConfig, +): Promise> { + return skinSiteRequest( + 'chat/voice/admin/config', + { method: 'PUT', body: data }, + ) +} + +export function skinSiteChatVoiceLogs( + after?: number, + limit = 300, +): Promise> { + const query = new URLSearchParams({ limit: String(limit) }) + if (after !== undefined) query.set('after', String(after)) + return skinSiteRequest('chat/voice/admin/logs?' + query.toString()) +} + +export function skinSiteChatVoiceCapacityUpdate( + roomId: number, + data: { participantLimit: number; listenerLimit: number }, +): Promise> { + return skinSiteRequest( + 'chat/admin/rooms/' + roomId + '/voice/capacity', + { method: 'PUT', body: data }, + ) +} + +// --------------------------------------------------------------------------- +// Wiki migration +// --------------------------------------------------------------------------- + +export type SkinSiteWikiMigrationState = + | 'IMPORTING' + | 'REVIEW' + | 'PUBLISHING' + | 'PUBLISHED' + | 'FAILED' + | 'ROLLED_BACK' + +export type SkinSiteWikiMigrationDecision = 'PENDING' | 'IMPORT' | 'EXCLUDE' + +export interface SkinSiteWikiMigrationJobInfo { + id: number + source: string + state: SkinSiteWikiMigrationState + total: number + reviewed: number + imported: number + excluded: number + error?: string | null + missingAttachmentCount?: number +} + +export interface SkinSiteWikiMigrationCandidate { + id: number + title: string + lastUpdatedAt: number + maintainerCount: number + suspiciousReasons: string[] + decision: SkinSiteWikiMigrationDecision + conflict: boolean +} + +export interface SkinSiteWikiMigrationPage { + items: SkinSiteWikiMigrationCandidate[] + nextCursor?: number | null +} + +export interface SkinSiteWikiMigrationPublishResult { + imported: number + overwritten: number +} + +export interface SkinSiteWikiAutomaticMigrationInput { + apiUrl: string + username?: string | null + password?: string | null + allowPrivateAddress: boolean + rules: { + staleDays: number + maintainerWindowDays: number + maximumMaintainers: number + staleMarkers: string[] + } +} + +export function skinSiteWikiMigrationJobs(): Promise< + SkinSiteResponse +> { + return skinSiteRequest('wiki/admin/migration') +} + +export function skinSiteWikiMigrationStartAutomatic( + data: SkinSiteWikiAutomaticMigrationInput, +): Promise> { + return skinSiteRequest('wiki/admin/migration/automatic', { + method: 'POST', + body: data, + }) +} + +export function skinSiteWikiMigrationStartXml( + file: File, +): Promise> { + const form = new FormData() + form.append('xml', file, file.name) + return skinSiteRequest('wiki/admin/migration/xml', { + method: 'POST', + form, + }) +} + +export function skinSiteWikiMigrationCandidates( + jobId: number, + after?: number, +): Promise> { + const suffix = after ? '?after=' + after : '' + return skinSiteRequest( + 'wiki/admin/migration/' + jobId + '/candidates' + suffix, + ) +} + +export function skinSiteWikiMigrationDecide( + candidateId: number, + data: { decision: SkinSiteWikiMigrationDecision; targetTitle?: string | null; overwrite?: boolean }, +): Promise> { + return skinSiteRequest('wiki/admin/migration/candidates/' + candidateId, { + method: 'POST', + body: data, + }) +} + +export function skinSiteWikiMigrationPublish( + jobId: number, +): Promise> { + return skinSiteRequest( + 'wiki/admin/migration/' + jobId + '/publish', + { method: 'POST' }, + ) +} + +export function skinSiteWikiMigrationRollback( + jobId: number, +): Promise> { + return skinSiteRequest('wiki/admin/migration/' + jobId + '/rollback', { + method: 'POST', + }) +} + +export function skinSiteWikiMigrationDelete(jobId: number): Promise> { + return skinSiteRequest('wiki/admin/migration/' + jobId, { method: 'DELETE' }) +} + +export function skinSiteWikiAttachment( + name: string, +): Promise> { + return skinSiteRequest( + 'wiki/attachment?name=' + encodeURIComponent(name), + ) +} + +export function skinSiteWikiAttachmentUrl(id: number): string { + return `${SKIN_SITE_ORIGIN}${REQUEST_TYPE_PATH.starlight}/wiki/attachment/${id}/content` +} diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 065b42c..9a3ae7f 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -11952,5 +11952,487 @@ "app.skin-site.admin.launcher.prev": {"message": "Previous"}, "app.skin-site.admin.launcher.next": {"message": "Next"}, "app.skin-site.admin.launcher.page": {"message": "Page {page}"}, - "app.skin-site.admin.launcher.total": {"message": "{count} releases"} + "app.skin-site.admin.launcher.total": {"message": "{count} releases"}, + "app.skin-site.admin.downloads.title": {"message": "Downloads"}, + "app.skin-site.admin.downloads.loading": {"message": "Loading…"}, + "app.skin-site.admin.downloads.failed": {"message": "Could not load downloads."}, + "app.skin-site.admin.downloads.empty": {"message": "No download items."}, + "app.skin-site.admin.downloads.categories": {"message": "Categories"}, + "app.skin-site.admin.downloads.all": {"message": "All items"}, + "app.skin-site.admin.downloads.add-category": {"message": "Add"}, + "app.skin-site.admin.downloads.category-name": {"message": "Category name"}, + "app.skin-site.admin.downloads.remove-category": {"message": "Remove"}, + "app.skin-site.admin.downloads.total": {"message": "{count} items"}, + "app.skin-site.admin.downloads.add-item": {"message": "New item"}, + "app.skin-site.admin.downloads.save": {"message": "Save"}, + "app.skin-site.admin.downloads.cancel": {"message": "Cancel"}, + "app.skin-site.admin.downloads.edit": {"message": "Edit"}, + "app.skin-site.admin.downloads.remove": {"message": "Delete"}, + "app.skin-site.admin.downloads.filename": {"message": "File name"}, + "app.skin-site.admin.downloads.description": {"message": "Description"}, + "app.skin-site.admin.downloads.routes": {"message": "Download routes"}, + "app.skin-site.admin.downloads.route-name": {"message": "Name"}, + "app.skin-site.admin.downloads.route-url": {"message": "URL"}, + "app.skin-site.admin.downloads.add-route": {"message": "Add route"}, + "app.skin-site.admin.downloads.remove-route": {"message": "Remove"}, + "app.skin-site.admin.downloads.statuses": {"message": "Statuses"}, + "app.skin-site.admin.downloads.bind-category": {"message": "Categories"}, + "app.skin-site.admin.downloads.uploader": {"message": "by {name}"}, + "app.skin-site.admin.downloads.created": {"message": "Item created."}, + "app.skin-site.admin.downloads.updated": {"message": "Item updated."}, + "app.skin-site.admin.downloads.deleted": {"message": "Item deleted."}, + "app.skin-site.admin.downloads.category-added": {"message": "Category added."}, + "app.skin-site.admin.downloads.category-deleted": {"message": "Category deleted."}, + "app.skin-site.admin.downloads.confirm-delete-item": {"message": "Delete “{name}”? This cannot be undone."}, + "app.skin-site.admin.downloads.confirm-delete-category": {"message": "Delete category “{name}”?"}, + "app.skin-site.admin.servers.title": {"message": "Map servers"}, + "app.skin-site.admin.servers.loading": {"message": "Loading…"}, + "app.skin-site.admin.servers.failed": {"message": "Could not load servers."}, + "app.skin-site.admin.servers.empty": {"message": "No map servers configured."}, + "app.skin-site.admin.servers.server-name": {"message": "Server name"}, + "app.skin-site.admin.servers.internal": {"message": "Internal"}, + "app.skin-site.admin.servers.maps": {"message": "Maps"}, + "app.skin-site.admin.servers.map-name": {"message": "Map name"}, + "app.skin-site.admin.servers.map-url": {"message": "Map URL"}, + "app.skin-site.admin.servers.add-map": {"message": "Add map"}, + "app.skin-site.admin.servers.remove-map": {"message": "Remove"}, + "app.skin-site.admin.servers.add-server": {"message": "Add server"}, + "app.skin-site.admin.servers.remove-server": {"message": "Remove server"}, + "app.skin-site.admin.servers.save": {"message": "Save changes"}, + "app.skin-site.admin.servers.saving": {"message": "Saving…"}, + "app.skin-site.admin.servers.saved": {"message": "Map servers saved."}, + "app.skin-site.admin.audit.title": {"message": "Access audit"}, + "app.skin-site.admin.audit.loading": {"message": "Loading…"}, + "app.skin-site.admin.audit.failed": {"message": "Could not load audit log."}, + "app.skin-site.admin.audit.empty": {"message": "No audit entries."}, + "app.skin-site.admin.audit.subject": {"message": "Subject"}, + "app.skin-site.admin.audit.action": {"message": "Action"}, + "app.skin-site.admin.audit.source-ip": {"message": "Source IP"}, + "app.skin-site.admin.audit.reported-ip": {"message": "Reported player IP"}, + "app.skin-site.admin.audit.count": {"message": "Count"}, + "app.skin-site.admin.audit.first-at": {"message": "First seen"}, + "app.skin-site.admin.audit.last-at": {"message": "Last seen"}, + "app.skin-site.admin.audit.proxy": {"message": "Proxy"}, + "app.skin-site.admin.audit.internal": {"message": "Internal"}, + "app.skin-site.admin.audit.search-subject": {"message": "Subject"}, + "app.skin-site.admin.audit.search-ip": {"message": "IP"}, + "app.skin-site.admin.audit.search-action": {"message": "Action"}, + "app.skin-site.admin.audit.search": {"message": "Search"}, + "app.skin-site.admin.audit.clear": {"message": "Clear"}, + "app.skin-site.admin.audit.load-more": {"message": "Load more"}, + "app.skin-site.admin.audit.retention": {"message": "Retention (days)"}, + "app.skin-site.admin.audit.record-limit": {"message": "Record limit"}, + "app.skin-site.admin.audit.save-retention": {"message": "Save"}, + "app.skin-site.admin.audit.retention-saved": {"message": "Retention updated."}, + "app.skin-site.admin.donations.title": {"message": "Donations"}, + "app.skin-site.admin.donations.loading": {"message": "Loading…"}, + "app.skin-site.admin.donations.failed": {"message": "Could not load donations."}, + "app.skin-site.admin.donations.empty": {"message": "No donations yet."}, + "app.skin-site.admin.donations.total-amount": {"message": "Total"}, + "app.skin-site.admin.donations.donor-count": {"message": "Donors"}, + "app.skin-site.admin.donations.donor": {"message": "Donor"}, + "app.skin-site.admin.donations.amount": {"message": "Amount"}, + "app.skin-site.admin.donations.updated-at": {"message": "Updated"}, + "app.skin-site.admin.donations.external": {"message": "External"}, + "app.skin-site.admin.donations.linked": {"message": "Linked"}, + "app.skin-site.admin.donations.create": {"message": "Add donation"}, + "app.skin-site.admin.donations.save": {"message": "Save"}, + "app.skin-site.admin.donations.cancel": {"message": "Cancel"}, + "app.skin-site.admin.donations.edit": {"message": "Edit"}, + "app.skin-site.admin.donations.remove": {"message": "Delete"}, + "app.skin-site.admin.donations.amount-field": {"message": "Amount"}, + "app.skin-site.admin.donations.display-name": {"message": "Display name"}, + "app.skin-site.admin.donations.user-uuid": {"message": "User UUID"}, + "app.skin-site.admin.donations.qq": {"message": "QQ"}, + "app.skin-site.admin.donations.append-mode": {"message": "Append to existing amount"}, + "app.skin-site.admin.donations.created": {"message": "Donation created."}, + "app.skin-site.admin.donations.updated": {"message": "Donation updated."}, + "app.skin-site.admin.donations.deleted": {"message": "Donation deleted."}, + "app.skin-site.admin.donations.confirm-delete": {"message": "Delete the donation from “{name}”? This cannot be undone."}, + "app.skin-site.admin.donations.load-more": {"message": "Load more"}, + "app.skin-site.admin.mods.title": {"message": "Mods"}, + "app.skin-site.admin.mods.tab-mods": {"message": "Mod files"}, + "app.skin-site.admin.mods.tab-tags": {"message": "Rule tags"}, + "app.skin-site.admin.mods.tab-runtime": {"message": "Runtime"}, + "app.skin-site.admin.mods.loading": {"message": "Loading…"}, + "app.skin-site.admin.mods.failed": {"message": "Could not load mods."}, + "app.skin-site.admin.mods.empty": {"message": "No mods found."}, + "app.skin-site.admin.mods.total": {"message": "{count} mods"}, + "app.skin-site.admin.mods.search": {"message": "Search by name"}, + "app.skin-site.admin.mods.filter-all": {"message": "All tags"}, + "app.skin-site.admin.mods.prev": {"message": "Previous"}, + "app.skin-site.admin.mods.next": {"message": "Next"}, + "app.skin-site.admin.mods.page": {"message": "Page {page}"}, + "app.skin-site.admin.mods.name": {"message": "Name"}, + "app.skin-site.admin.mods.version": {"message": "Version"}, + "app.skin-site.admin.mods.mod-id": {"message": "Mod ID"}, + "app.skin-site.admin.mods.size": {"message": "Size"}, + "app.skin-site.admin.mods.loaders": {"message": "Loaders"}, + "app.skin-site.admin.mods.domain-rules": {"message": "Domain rules"}, + "app.skin-site.admin.mods.edit": {"message": "Edit"}, + "app.skin-site.admin.mods.save": {"message": "Save"}, + "app.skin-site.admin.mods.cancel": {"message": "Cancel"}, + "app.skin-site.admin.mods.remove": {"message": "Delete"}, + "app.skin-site.admin.mods.updated": {"message": "Mod updated."}, + "app.skin-site.admin.mods.deleted": {"message": "Mod deleted."}, + "app.skin-site.admin.mods.confirm-delete": {"message": "Delete mod “{name}”? This cannot be undone."}, + "app.skin-site.admin.mods.rule-tags": {"message": "Rule tags"}, + "app.skin-site.admin.mods.tag-name": {"message": "Tag name"}, + "app.skin-site.admin.mods.tag-rules": {"message": "Rules"}, + "app.skin-site.admin.mods.tag-rules-hint": {"message": "One rule per line."}, + "app.skin-site.admin.mods.new-tag": {"message": "New tag"}, + "app.skin-site.admin.mods.tag-created": {"message": "Tag created."}, + "app.skin-site.admin.mods.tag-updated": {"message": "Tag updated."}, + "app.skin-site.admin.mods.tag-deleted": {"message": "Tag deleted."}, + "app.skin-site.admin.mods.confirm-delete-tag": {"message": "Delete tag “{name}”?"}, + "app.skin-site.admin.mods.runtime": {"message": "Runtime releases"}, + "app.skin-site.admin.mods.manifest-url": {"message": "Manifest URL"}, + "app.skin-site.admin.mods.upload-runtime": {"message": "Upload runtime"}, + "app.skin-site.admin.mods.runtime-version": {"message": "Version"}, + "app.skin-site.admin.mods.uploading": {"message": "Uploading…"}, + "app.skin-site.admin.mods.runtime-uploaded": {"message": "Runtime uploaded."}, + "app.skin-site.admin.mods.activate": {"message": "Activate"}, + "app.skin-site.admin.mods.activated": {"message": "Runtime activated."}, + "app.skin-site.admin.mods.available": {"message": "Available"}, + "app.skin-site.admin.mods.unavailable": {"message": "Unavailable"}, + "app.skin-site.admin.mods.active": {"message": "Active"}, + "app.skin-site.admin.mods.uploader": {"message": "by {name}"}, + "app.skin-site.home.nav.title": {"message": "Skin site"}, + "app.skin-site.home.nav.download": {"message": "Downloads"}, + "app.skin-site.home.nav.download-desc": {"message": "Browse files shared by the server."}, + "app.skin-site.home.nav.review": {"message": "Player review"}, + "app.skin-site.home.nav.review-desc": {"message": "Submit and review player applications."}, + "app.skin-site.home.nav.wiki": {"message": "Server wiki"}, + "app.skin-site.home.nav.wiki-desc": {"message": "Guides and documentation for the server."}, + "app.skin-site.home.nav.bugs": {"message": "Bug tracker"}, + "app.skin-site.home.nav.bugs-desc": {"message": "Report issues and follow their progress."}, + "app.skin-site.home.nav.map": {"message": "Server map"}, + "app.skin-site.home.nav.map-desc": {"message": "Explore the live world map."}, + "app.skin-site.home.nav.profile": {"message": "Profile"}, + "app.skin-site.home.nav.profile-desc": {"message": "Manage your account and textures."}, + "app.skin-site.admin.wiki.title": {"message": "Wiki"}, + "app.skin-site.admin.wiki.tab-page": {"message": "Page"}, + "app.skin-site.admin.wiki.tab-audit": {"message": "Audit log"}, + "app.skin-site.admin.wiki.tab-settings": {"message": "Settings"}, + "app.skin-site.admin.wiki.loading": {"message": "Loading…"}, + "app.skin-site.admin.wiki.page-path": {"message": "Page path"}, + "app.skin-site.admin.wiki.load-page": {"message": "Load page"}, + "app.skin-site.admin.wiki.page-not-found": {"message": "Page not found."}, + "app.skin-site.admin.wiki.revision": {"message": "Revision"}, + "app.skin-site.admin.wiki.author": {"message": "Author"}, + "app.skin-site.admin.wiki.updated-at": {"message": "Updated"}, + "app.skin-site.admin.wiki.categories": {"message": "Categories"}, + "app.skin-site.admin.wiki.read-policy": {"message": "Read policy"}, + "app.skin-site.admin.wiki.edit-policy": {"message": "Edit policy"}, + "app.skin-site.admin.wiki.save-protect": {"message": "Save policies"}, + "app.skin-site.admin.wiki.protect-saved": {"message": "Page policies updated."}, + "app.skin-site.admin.wiki.set-home": {"message": "Set as home page"}, + "app.skin-site.admin.wiki.home-set": {"message": "Home page updated."}, + "app.skin-site.admin.wiki.set-deleted": {"message": "Mark as deleted"}, + "app.skin-site.admin.wiki.set-restored": {"message": "Restore page"}, + "app.skin-site.admin.wiki.deleted-updated": {"message": "Page deletion state updated."}, + "app.skin-site.admin.wiki.audit-empty": {"message": "No audit entries."}, + "app.skin-site.admin.wiki.audit-target": {"message": "Target"}, + "app.skin-site.admin.wiki.load-more": {"message": "Load more"}, + "app.skin-site.admin.wiki.settings-hint": {"message": "Comma separated domains."}, + "app.skin-site.admin.wiki.domains": {"message": "Domains"}, + "app.skin-site.admin.wiki.home-page": {"message": "Home page"}, + "app.skin-site.admin.wiki.default-skin": {"message": "Default skin"}, + "app.skin-site.admin.wiki.max-page-bytes": {"message": "Max page bytes"}, + "app.skin-site.admin.wiki.max-attachment-bytes": {"message": "Max attachment bytes"}, + "app.skin-site.admin.wiki.save-settings": {"message": "Save settings"}, + "app.skin-site.admin.wiki.settings-saved": {"message": "Settings saved."}, + "app.skin-site.admin.review.tab-list": {"message": "Submissions"}, + "app.skin-site.admin.review.tab-papers": {"message": "Papers"}, + "app.skin-site.admin.review.papers-empty": {"message": "No review papers yet."}, + "app.skin-site.admin.review.new-paper": {"message": "New paper"}, + "app.skin-site.admin.review.edit-paper": {"message": "Edit"}, + "app.skin-site.admin.review.paper-title": {"message": "Title"}, + "app.skin-site.admin.review.paper-annotation": {"message": "Description"}, + "app.skin-site.admin.review.paper-enabled": {"message": "Enabled"}, + "app.skin-site.admin.review.paper-mode": {"message": "Mode"}, + "app.skin-site.admin.review.paper-pass-score": {"message": "Pass score"}, + "app.skin-site.admin.review.paper-target-permissions": {"message": "Target permissions"}, + "app.skin-site.admin.review.paper-questions": {"message": "Questions"}, + "app.skin-site.admin.review.add-question": {"message": "Add question"}, + "app.skin-site.admin.review.question-title": {"message": "Question title"}, + "app.skin-site.admin.review.question-type": {"message": "Type"}, + "app.skin-site.admin.review.question-score": {"message": "Score"}, + "app.skin-site.admin.review.question-annotation": {"message": "Annotation"}, + "app.skin-site.admin.review.add-option": {"message": "Add option"}, + "app.skin-site.admin.review.option-description": {"message": "Option text"}, + "app.skin-site.admin.review.option-correct": {"message": "Correct"}, + "app.skin-site.admin.review.remove-question": {"message": "Remove"}, + "app.skin-site.admin.review.question-no": {"message": "Question {n}"}, + "app.skin-site.admin.review.paper-created": {"message": "Paper created."}, + "app.skin-site.admin.review.paper-saved": {"message": "Paper saved."}, + "app.skin-site.admin.review.mail-title": {"message": "Pass mail"}, + "app.skin-site.admin.review.mail-enabled": {"message": "Send mail on pass"}, + "app.skin-site.admin.review.mail-subject": {"message": "Subject"}, + "app.skin-site.admin.review.mail-message": {"message": "Body"}, + "app.skin-site.admin.review.mail-extra": {"message": "Extra text"}, + "app.skin-site.admin.review.mail-include-invite": {"message": "Include invite code"}, + "app.skin-site.admin.review.prevent-duplicate": {"message": "Reject duplicate submissions"}, + "app.skin-site.admin.review.copy-link": {"message": "Copy link"}, + "app.skin-site.admin.review.link-copied": {"message": "Link copied."}, + "app.skin-site.admin.review.move-up": {"message": "Move up"}, + "app.skin-site.admin.review.move-down": {"message": "Move down"}, + "app.skin-site.admin.review.mail-subject-required": {"message": "Subject is required when mail is enabled."}, + "app.skin-site.admin.review.mail-message-required": {"message": "Body is required when mail is enabled."}, + "app.skin-site.admin.users.actions": {"message": "Actions"}, + "app.skin-site.admin.users.audit": {"message": "Audit"}, + "app.skin-site.admin.users.audit-empty": {"message": "No audit records yet."}, + "app.skin-site.admin.users.audit-more": {"message": "Load earlier"}, + "app.skin-site.admin.users.audit-title": {"message": "User management audit"}, + "app.skin-site.admin.users.bot": {"message": "Bot"}, + "app.skin-site.admin.users.bot-active": {"message": "Token valid"}, + "app.skin-site.admin.users.bot-ban": {"message": "Ban bot creation"}, + "app.skin-site.admin.users.bot-creation-banned": {"message": "Token creation banned"}, + "app.skin-site.admin.users.bot-exists": {"message": "Bot created"}, + "app.skin-site.admin.users.bot-inactive": {"message": "Token invalidated"}, + "app.skin-site.admin.users.bot-invalidate": {"message": "Invalidate token"}, + "app.skin-site.admin.users.bot-not-exists": {"message": "No bot yet"}, + "app.skin-site.admin.users.bot-save-username": {"message": "Save bot username"}, + "app.skin-site.admin.users.bot-title": {"message": "Bot management"}, + "app.skin-site.admin.users.bot-token-hint": {"message": "Token hint"}, + "app.skin-site.admin.users.bot-unban": {"message": "Unban bot creation"}, + "app.skin-site.admin.users.bot-username": {"message": "Bot username"}, + "app.skin-site.admin.users.bound-players-title": {"message": "Linked players"}, + "app.skin-site.admin.users.frequency": {"message": "Rate limit"}, + "app.skin-site.admin.users.frequency-clear": {"message": "Clear limit"}, + "app.skin-site.admin.users.frequency-current": {"message": "Currently {percent}%"}, + "app.skin-site.admin.users.frequency-hint": {"message": "Packets from this user will be dropped at the given rate."}, + "app.skin-site.admin.users.frequency-none": {"message": "Not enabled"}, + "app.skin-site.admin.users.frequency-range": {"message": "Only integer values from 10% to 100% are allowed."}, + "app.skin-site.admin.users.frequency-save": {"message": "Save limit"}, + "app.skin-site.admin.users.frequency-title": {"message": "Rate limit"}, + "app.skin-site.admin.users.invite": {"message": "Invite user"}, + "app.skin-site.admin.users.invite-copied": {"message": "Invite code copied."}, + "app.skin-site.admin.users.invite-copy": {"message": "Copy"}, + "app.skin-site.admin.users.invite-email": {"message": "Email"}, + "app.skin-site.admin.users.invite-email-invalid": {"message": "Enter a valid email address."}, + "app.skin-site.admin.users.invite-generate": {"message": "Generate invite code"}, + "app.skin-site.admin.users.invite-title": {"message": "Invite user"}, + "app.skin-site.admin.users.legacy-adapted": {"message": "Legacy internal adapted"}, + "app.skin-site.admin.users.registered-at": {"message": "Registered at"}, + "app.skin-site.admin.users.status": {"message": "Status"}, + "app.skin-site.admin.users.status-banned": {"message": "Banned"}, + "app.skin-site.admin.users.status-normal": {"message": "Normal"}, + "app.skin-site.admin.mods.upload": {"message": "Upload"}, + "app.skin-site.admin.mods.select-mode": {"message": "Select multiple"}, + "app.skin-site.admin.mods.exit-select": {"message": "Done selecting"}, + "app.skin-site.admin.mods.select-all": {"message": "Select all"}, + "app.skin-site.admin.mods.clear-selection": {"message": "Clear selection"}, + "app.skin-site.admin.mods.selected-count": {"message": "{count} selected"}, + "app.skin-site.admin.mods.delete-selected": {"message": "Delete selected"}, + "app.skin-site.admin.mods.confirm-delete-multi": {"message": "Delete {count} selected mods? This cannot be undone."}, + "app.skin-site.admin.mods.deleted-multi": {"message": "Selected mods deleted."}, + "app.skin-site.admin.mods.server-address": {"message": "Server address"}, + "app.skin-site.admin.mods.server-address-hint": {"message": "Filter by server address"}, + "app.skin-site.admin.mods.apply": {"message": "Apply"}, + "app.skin-site.admin.mods.clear": {"message": "Clear"}, + "app.skin-site.admin.mods.upload-title": {"message": "Upload mods"}, + "app.skin-site.admin.mods.upload-drop": {"message": "Drag .jar files here, or click to choose"}, + "app.skin-site.admin.mods.upload-sub": {"message": "Multiple files supported, up to {size} MB each"}, + "app.skin-site.admin.mods.upload-tags": {"message": "Attach rule tags"}, + "app.skin-site.admin.mods.upload-queue": {"message": "Files"}, + "app.skin-site.admin.mods.upload-pending": {"message": "Pending"}, + "app.skin-site.admin.mods.upload-checking": {"message": "Checking…"}, + "app.skin-site.admin.mods.upload-uploading": {"message": "Uploading…"}, + "app.skin-site.admin.mods.upload-conflict": {"message": "Name exists"}, + "app.skin-site.admin.mods.upload-success": {"message": "Done"}, + "app.skin-site.admin.mods.upload-error": {"message": "Failed"}, + "app.skin-site.admin.mods.upload-exists": {"message": "This file already exists on the server."}, + "app.skin-site.admin.mods.upload-name-conflict": {"message": "A mod with the same file name already exists"}, + "app.skin-site.admin.mods.upload-name-conflict-hint": {"message": "Overwrite the existing mod, or skip this file."}, + "app.skin-site.admin.mods.upload-overwrite": {"message": "Overwrite"}, + "app.skin-site.admin.mods.upload-skip": {"message": "Skip"}, + "app.skin-site.admin.mods.upload-close": {"message": "Close"}, + "app.skin-site.admin.mods.upload-all-done": {"message": "All files processed."}, + "app.skin-site.admin.mods.upload-too-large": {"message": "File too large: {name}"}, + "app.skin-site.chat.avatar.label": {"message": "Room avatar"}, + "app.skin-site.chat.avatar.upload": {"message": "Upload avatar"}, + "app.skin-site.chat.avatar.replace": {"message": "Replace avatar"}, + "app.skin-site.chat.avatar.remove": {"message": "Remove avatar"}, + "app.skin-site.chat.avatar.hint": {"message": "PNG, JPEG or WebP, up to 8192 px per side and 5 MB."}, + "app.skin-site.chat.avatar.bad-format": {"message": "Only PNG, JPEG and WebP are supported."}, + "app.skin-site.chat.avatar.too-large": {"message": "Avatar must not exceed 5 MB."}, + "app.skin-site.chat.avatar.not-image": {"message": "Avatar must be an image."}, + "app.skin-site.chat.admin.title": {"message": "Chat rooms"}, + "app.skin-site.chat.admin.subtitle": {"message": "Create public or private rooms and hand daily moderation to trusted members."}, + "app.skin-site.chat.admin.create-room": {"message": "Create room"}, + "app.skin-site.chat.admin.loading": {"message": "Loading…"}, + "app.skin-site.chat.admin.failed": {"message": "Could not load rooms."}, + "app.skin-site.chat.admin.empty": {"message": "No chat rooms yet."}, + "app.skin-site.chat.admin.edit": {"message": "Edit"}, + "app.skin-site.chat.admin.remove": {"message": "Delete"}, + "app.skin-site.chat.admin.save": {"message": "Save"}, + "app.skin-site.chat.admin.cancel": {"message": "Cancel"}, + "app.skin-site.chat.admin.name": {"message": "Name"}, + "app.skin-site.chat.admin.description": {"message": "Description"}, + "app.skin-site.chat.admin.visibility": {"message": "Visibility"}, + "app.skin-site.chat.admin.visibility-public": {"message": "Public"}, + "app.skin-site.chat.admin.visibility-private": {"message": "Private"}, + "app.skin-site.chat.admin.kind": {"message": "Kind"}, + "app.skin-site.chat.admin.kind-standard": {"message": "Standard"}, + "app.skin-site.chat.admin.kind-settlement": {"message": "Settlement"}, + "app.skin-site.chat.admin.prerequisite-mode": {"message": "Prerequisite mode"}, + "app.skin-site.chat.admin.prerequisite-any": {"message": "Any"}, + "app.skin-site.chat.admin.prerequisite-all": {"message": "All"}, + "app.skin-site.chat.admin.prerequisite-permissions": {"message": "Required permissions"}, + "app.skin-site.chat.admin.prerequisite-hint": {"message": "Comma separated. Leave empty for no restriction."}, + "app.skin-site.chat.admin.created": {"message": "Room created."}, + "app.skin-site.chat.admin.updated": {"message": "Room updated."}, + "app.skin-site.chat.admin.deleted": {"message": "Room deleted."}, + "app.skin-site.chat.admin.confirm-delete": {"message": "Permanently delete room “{name}” and all its messages?"}, + "app.skin-site.chat.admin.manager-badge": {"message": "Manager"}, + "app.skin-site.chat.admin.voice-badge": {"message": "Voice"}, + "app.skin-site.chat.user-picker.label": {"message": "Pick a user"}, + "app.skin-site.chat.user-picker.placeholder": {"message": "Search username, email or account ID"}, + "app.skin-site.chat.user-picker.by-user": {"message": "By user"}, + "app.skin-site.chat.user-picker.by-player": {"message": "By player"}, + "app.skin-site.chat.user-picker.hint": {"message": "Fuzzy search on username and email, or search by account ID."}, + "app.skin-site.chat.user-picker.player-hint": {"message": "Enter a player name to find the bound user."}, + "app.skin-site.chat.user-picker.empty": {"message": "No matching user."}, + "app.skin-site.chat.user-picker.banned": {"message": "This account is banned."}, + "app.skin-site.chat.user-picker.guest": {"message": "Guests cannot become a manager or settlement owner."}, + "app.skin-site.chat.user-picker.already-in": {"message": "This user is already in the room."}, + "app.skin-site.chat.user-picker.not-allowed": {"message": "This user is outside the allowed range."}, + "app.skin-site.chat.user-picker.select": {"message": "Select"}, + "app.skin-site.chat.user-picker.selected": {"message": "Selected"}, + "app.skin-site.chat.user-picker.id": {"message": "ID"}, + "app.skin-site.chat.admin.managers": {"message": "Managers"}, + "app.skin-site.chat.admin.manager-title": {"message": "Room manager"}, + "app.skin-site.chat.admin.manager-hint": {"message": "Managers can moderate members, messages and files in this room."}, + "app.skin-site.chat.admin.grant": {"message": "Grant"}, + "app.skin-site.chat.admin.revoke": {"message": "Revoke"}, + "app.skin-site.chat.admin.manager-granted": {"message": "Manager granted."}, + "app.skin-site.chat.admin.manager-revoked": {"message": "Manager revoked."}, + "app.skin-site.chat.admin.owner-title": {"message": "Settlement owner"}, + "app.skin-site.chat.admin.owner-hint": {"message": "Assign the first settlement owner for this room."}, + "app.skin-site.chat.admin.owner-assigned": {"message": "Settlement owner assigned."}, + "app.skin-site.chat.admin.owner-current": {"message": "Current owner: {name}"}, + "app.skin-site.chat.admin.owner-none": {"message": "No settlement owner yet."}, + "app.skin-site.chat.admin.audit": {"message": "Audit"}, + "app.skin-site.chat.admin.audit-title": {"message": "Room audit"}, + "app.skin-site.chat.admin.audit-empty": {"message": "No audit records yet."}, + "app.skin-site.chat.admin.audit-target": {"message": "Target: {name}"}, + "app.skin-site.chat.admin.close": {"message": "Close"}, + "app.skin-site.chat.voice-runtime-title": {"message": "Realtime voice runtime"}, + "app.skin-site.chat.voice-logs": {"message": "View logs"}, + "app.skin-site.chat.voice-config": {"message": "Configure runtime"}, + "app.skin-site.chat.voice-retry": {"message": "Retry deployment"}, + "app.skin-site.chat.voice-config-title": {"message": "Voice runtime configuration"}, + "app.skin-site.chat.voice-logs-title": {"message": "LiveKit logs"}, + "app.skin-site.chat.voice-capacity-title": {"message": "Voice capacity"}, + "app.skin-site.chat.voice-capacity": {"message": "Voice capacity"}, + "app.skin-site.chat.participant-limit": {"message": "Participant limit"}, + "app.skin-site.chat.listener-limit": {"message": "Listener limit"}, + "app.skin-site.chat.voice-capacity-updated": {"message": "Voice capacity updated."}, + "app.skin-site.chat.voice-enabled": {"message": "Enable realtime voice"}, + "app.skin-site.chat.voice-runtime-path": {"message": "Runtime directory"}, + "app.skin-site.chat.voice-mirror": {"message": "Mirror base URL"}, + "app.skin-site.chat.voice-public-url": {"message": "Public URL"}, + "app.skin-site.chat.voice-bind": {"message": "Bind address"}, + "app.skin-site.chat.voice-signal-port": {"message": "Signal port"}, + "app.skin-site.chat.voice-rtc-tcp-port": {"message": "Media TCP port"}, + "app.skin-site.chat.voice-rtc-udp-port": {"message": "Media UDP port"}, + "app.skin-site.chat.voice-startup-timeout": {"message": "Startup timeout (s)"}, + "app.skin-site.chat.voice-use-external-ip": {"message": "Use external IP"}, + "app.skin-site.chat.voice-node-ip": {"message": "Node IP"}, + "app.skin-site.chat.voice-saved": {"message": "Voice configuration saved."}, + "app.skin-site.chat.voice-logs-auto-refresh": {"message": "Auto refresh"}, + "app.skin-site.chat.voice-logs-refresh": {"message": "Refresh"}, + "app.skin-site.chat.voice-logs-empty": {"message": "No logs yet."}, + "app.skin-site.chat.admin.voice-runtime-title": {"message": "Realtime voice runtime"}, + "app.skin-site.chat.admin.voice-logs": {"message": "View logs"}, + "app.skin-site.chat.admin.voice-config": {"message": "Configure runtime"}, + "app.skin-site.chat.admin.voice-retry": {"message": "Retry deployment"}, + "app.skin-site.chat.admin.voice-config-title": {"message": "Voice runtime configuration"}, + "app.skin-site.chat.admin.voice-logs-title": {"message": "LiveKit logs"}, + "app.skin-site.chat.admin.voice-capacity-title": {"message": "Voice capacity"}, + "app.skin-site.chat.admin.voice-capacity": {"message": "Voice capacity"}, + "app.skin-site.chat.admin.participant-limit": {"message": "Participant limit"}, + "app.skin-site.chat.admin.listener-limit": {"message": "Listener limit"}, + "app.skin-site.chat.admin.voice-capacity-updated": {"message": "Voice capacity updated."}, + "app.skin-site.chat.admin.voice-enabled": {"message": "Enable realtime voice"}, + "app.skin-site.chat.admin.voice-runtime-path": {"message": "Runtime directory"}, + "app.skin-site.chat.admin.voice-mirror": {"message": "Mirror base URL"}, + "app.skin-site.chat.admin.voice-public-url": {"message": "Public URL"}, + "app.skin-site.chat.admin.voice-bind": {"message": "Bind address"}, + "app.skin-site.chat.admin.voice-signal-port": {"message": "Signal port"}, + "app.skin-site.chat.admin.voice-rtc-tcp-port": {"message": "Media TCP port"}, + "app.skin-site.chat.admin.voice-rtc-udp-port": {"message": "Media UDP port"}, + "app.skin-site.chat.admin.voice-startup-timeout": {"message": "Startup timeout (s)"}, + "app.skin-site.chat.admin.voice-use-external-ip": {"message": "Use external IP"}, + "app.skin-site.chat.admin.voice-node-ip": {"message": "Node IP"}, + "app.skin-site.chat.admin.voice-saved": {"message": "Voice configuration saved."}, + "app.skin-site.chat.admin.voice-logs-auto-refresh": {"message": "Auto refresh"}, + "app.skin-site.chat.admin.voice-logs-refresh": {"message": "Refresh"}, + "app.skin-site.chat.admin.voice-logs-empty": {"message": "No logs yet."}, + "app.skin-site.admin.wiki.tab-migration": {"message": "Migration"}, + "app.skin-site.admin.wiki.migration-title": {"message": "MediaWiki migration"}, + "app.skin-site.admin.wiki.migration-hint": {"message": "Migrated content lands in a hidden staging area and is published in one batch after every page is reviewed."}, + "app.skin-site.admin.wiki.auto-title": {"message": "Read from MediaWiki API"}, + "app.skin-site.admin.wiki.api-url": {"message": "api.php URL"}, + "app.skin-site.admin.wiki.bot-username": {"message": "Bot username (optional)"}, + "app.skin-site.admin.wiki.bot-password": {"message": "Bot password (optional)"}, + "app.skin-site.admin.wiki.credentials-hint": {"message": "Credentials stay in memory for this job only. HTTPS is required when supplied."}, + "app.skin-site.admin.wiki.allow-private": {"message": "Allow private network source"}, + "app.skin-site.admin.wiki.allow-private-hint": {"message": "Private sources may reach services inside the server network."}, + "app.skin-site.admin.wiki.start-auto": {"message": "Create automatic migration job"}, + "app.skin-site.admin.wiki.xml-title": {"message": "Import MediaWiki XML"}, + "app.skin-site.admin.wiki.xml-file": {"message": "Export file"}, + "app.skin-site.admin.wiki.start-xml": {"message": "Upload and stage"}, + "app.skin-site.admin.wiki.stale-rules": {"message": "Stale page detection rules"}, + "app.skin-site.admin.wiki.stale-days": {"message": "Days without update"}, + "app.skin-site.admin.wiki.maintainer-window": {"message": "Maintainer window (days)"}, + "app.skin-site.admin.wiki.max-maintainers": {"message": "Max maintainers"}, + "app.skin-site.admin.wiki.stale-markers": {"message": "Stale categories, templates or text"}, + "app.skin-site.admin.wiki.stale-markers-hint": {"message": "Comma separated"}, + "app.skin-site.admin.wiki.jobs": {"message": "Migration jobs"}, + "app.skin-site.admin.wiki.refresh-jobs": {"message": "Refresh"}, + "app.skin-site.admin.wiki.jobs-empty": {"message": "No migration jobs yet."}, + "app.skin-site.admin.wiki.reviewed-count": {"message": "Reviewed {reviewed} / {total}"}, + "app.skin-site.admin.wiki.imported-excluded": {"message": "Imported {imported} · Excluded {excluded}"}, + "app.skin-site.admin.wiki.view-job": {"message": "View job"}, + "app.skin-site.admin.wiki.delete-job": {"message": "Delete job record"}, + "app.skin-site.admin.wiki.confirm-delete-job": {"message": "Delete migration job #{id}? This cannot be undone."}, + "app.skin-site.admin.wiki.candidates": {"message": "Candidate pages"}, + "app.skin-site.admin.wiki.candidates-empty": {"message": "Select a job to review its pages."}, + "app.skin-site.admin.wiki.last-updated": {"message": "Updated {date}"}, + "app.skin-site.admin.wiki.maintainer-count": {"message": "{count} maintainers"}, + "app.skin-site.admin.wiki.conflict": {"message": "Title conflict"}, + "app.skin-site.admin.wiki.target-title": {"message": "Target title"}, + "app.skin-site.admin.wiki.overwrite": {"message": "Overwrite"}, + "app.skin-site.admin.wiki.import": {"message": "Import"}, + "app.skin-site.admin.wiki.exclude": {"message": "Exclude"}, + "app.skin-site.admin.wiki.publish": {"message": "Publish batch"}, + "app.skin-site.admin.wiki.rollback": {"message": "Roll back"}, + "app.skin-site.admin.wiki.confirm-rollback": {"message": "Roll back this migration batch? Published pages will be reverted."}, + "app.skin-site.admin.wiki.state-importing": {"message": "Importing"}, + "app.skin-site.admin.wiki.state-review": {"message": "Waiting review"}, + "app.skin-site.admin.wiki.state-publishing": {"message": "Publishing"}, + "app.skin-site.admin.wiki.state-published": {"message": "Published"}, + "app.skin-site.admin.wiki.state-failed": {"message": "Failed"}, + "app.skin-site.admin.wiki.state-rolled-back": {"message": "Rolled back"}, + "app.skin-site.admin.wiki.decision-pending": {"message": "Pending"}, + "app.skin-site.admin.wiki.decision-import": {"message": "Import"}, + "app.skin-site.admin.wiki.decision-exclude": {"message": "Exclude"}, + "app.skin-site.admin.wiki.migration-started": {"message": "Migration job created."}, + "app.skin-site.admin.wiki.xml-staged": {"message": "XML parsed and staged for review."}, + "app.skin-site.admin.wiki.published": {"message": "Published {imported} pages, overwrote {overwritten}."}, + "app.skin-site.admin.wiki.rolled-back": {"message": "Migration batch rolled back."}, + "app.skin-site.admin.wiki.job-deleted": {"message": "Migration record deleted."}, + "app.skin-site.admin.wiki.select-xml": {"message": "Choose a MediaWiki XML file first."}, + "app.skin-site.wiki.title": {"message": "StarLight Wiki"}, + "app.skin-site.wiki.search-placeholder": {"message": "Enter a page title"}, + "app.skin-site.wiki.open": {"message": "Open"}, + "app.skin-site.wiki.loading": {"message": "Loading…"}, + "app.skin-site.wiki.not-found": {"message": "Page not found."}, + "app.skin-site.wiki.empty": {"message": "Enter a page title above to start reading."}, + "app.skin-site.wiki.updated-at": {"message": "Last edited {date} by {author}"}, + "app.skin-site.wiki.revision": {"message": "Revision #{id}"}, + "app.skin-site.wiki.categories": {"message": "Categories"}, + "app.skin-site.wiki.back": {"message": "Back"} } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index 7026430..0e33964 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -11783,5 +11783,487 @@ "app.skin-site.admin.launcher.prev": {"message": "上一页"}, "app.skin-site.admin.launcher.next": {"message": "下一页"}, "app.skin-site.admin.launcher.page": {"message": "第 {page} 页"}, - "app.skin-site.admin.launcher.total": {"message": "共 {count} 个版本"} + "app.skin-site.admin.launcher.total": {"message": "共 {count} 个版本"}, + "app.skin-site.admin.downloads.title": {"message": "资源下载"}, + "app.skin-site.admin.downloads.loading": {"message": "加载中…"}, + "app.skin-site.admin.downloads.failed": {"message": "无法加载资源列表。"}, + "app.skin-site.admin.downloads.empty": {"message": "没有下载条目。"}, + "app.skin-site.admin.downloads.categories": {"message": "分类"}, + "app.skin-site.admin.downloads.all": {"message": "全部条目"}, + "app.skin-site.admin.downloads.add-category": {"message": "添加"}, + "app.skin-site.admin.downloads.category-name": {"message": "分类名称"}, + "app.skin-site.admin.downloads.remove-category": {"message": "移除"}, + "app.skin-site.admin.downloads.total": {"message": "共 {count} 个条目"}, + "app.skin-site.admin.downloads.add-item": {"message": "新建条目"}, + "app.skin-site.admin.downloads.save": {"message": "保存"}, + "app.skin-site.admin.downloads.cancel": {"message": "取消"}, + "app.skin-site.admin.downloads.edit": {"message": "编辑"}, + "app.skin-site.admin.downloads.remove": {"message": "删除"}, + "app.skin-site.admin.downloads.filename": {"message": "文件名"}, + "app.skin-site.admin.downloads.description": {"message": "描述"}, + "app.skin-site.admin.downloads.routes": {"message": "下载线路"}, + "app.skin-site.admin.downloads.route-name": {"message": "名称"}, + "app.skin-site.admin.downloads.route-url": {"message": "URL"}, + "app.skin-site.admin.downloads.add-route": {"message": "添加线路"}, + "app.skin-site.admin.downloads.remove-route": {"message": "移除"}, + "app.skin-site.admin.downloads.statuses": {"message": "状态"}, + "app.skin-site.admin.downloads.bind-category": {"message": "所属分类"}, + "app.skin-site.admin.downloads.uploader": {"message": "由 {name} 上传"}, + "app.skin-site.admin.downloads.created": {"message": "条目已创建。"}, + "app.skin-site.admin.downloads.updated": {"message": "条目已更新。"}, + "app.skin-site.admin.downloads.deleted": {"message": "条目已删除。"}, + "app.skin-site.admin.downloads.category-added": {"message": "分类已添加。"}, + "app.skin-site.admin.downloads.category-deleted": {"message": "分类已删除。"}, + "app.skin-site.admin.downloads.confirm-delete-item": {"message": "确定删除「{name}」吗?此操作不可撤销。"}, + "app.skin-site.admin.downloads.confirm-delete-category": {"message": "确定删除分类「{name}」吗?"}, + "app.skin-site.admin.servers.title": {"message": "地图服务器"}, + "app.skin-site.admin.servers.loading": {"message": "加载中…"}, + "app.skin-site.admin.servers.failed": {"message": "无法加载服务器列表。"}, + "app.skin-site.admin.servers.empty": {"message": "尚未配置地图服务器。"}, + "app.skin-site.admin.servers.server-name": {"message": "服务器名称"}, + "app.skin-site.admin.servers.internal": {"message": "内部"}, + "app.skin-site.admin.servers.maps": {"message": "地图"}, + "app.skin-site.admin.servers.map-name": {"message": "地图名称"}, + "app.skin-site.admin.servers.map-url": {"message": "地图地址"}, + "app.skin-site.admin.servers.add-map": {"message": "添加地图"}, + "app.skin-site.admin.servers.remove-map": {"message": "移除"}, + "app.skin-site.admin.servers.add-server": {"message": "添加服务器"}, + "app.skin-site.admin.servers.remove-server": {"message": "删除服务器"}, + "app.skin-site.admin.servers.save": {"message": "保存更改"}, + "app.skin-site.admin.servers.saving": {"message": "保存中…"}, + "app.skin-site.admin.servers.saved": {"message": "地图服务器已保存。"}, + "app.skin-site.admin.audit.title": {"message": "登录与认证审计"}, + "app.skin-site.admin.audit.loading": {"message": "加载中…"}, + "app.skin-site.admin.audit.failed": {"message": "无法加载审计日志。"}, + "app.skin-site.admin.audit.empty": {"message": "没有审计记录。"}, + "app.skin-site.admin.audit.subject": {"message": "主体"}, + "app.skin-site.admin.audit.action": {"message": "操作"}, + "app.skin-site.admin.audit.source-ip": {"message": "来源 IP"}, + "app.skin-site.admin.audit.reported-ip": {"message": "上报玩家 IP"}, + "app.skin-site.admin.audit.count": {"message": "次数"}, + "app.skin-site.admin.audit.first-at": {"message": "首次出现"}, + "app.skin-site.admin.audit.last-at": {"message": "最近出现"}, + "app.skin-site.admin.audit.proxy": {"message": "代理"}, + "app.skin-site.admin.audit.internal": {"message": "内部"}, + "app.skin-site.admin.audit.search-subject": {"message": "主体"}, + "app.skin-site.admin.audit.search-ip": {"message": "IP"}, + "app.skin-site.admin.audit.search-action": {"message": "操作"}, + "app.skin-site.admin.audit.search": {"message": "搜索"}, + "app.skin-site.admin.audit.clear": {"message": "清空"}, + "app.skin-site.admin.audit.load-more": {"message": "加载更多"}, + "app.skin-site.admin.audit.retention": {"message": "保留天数"}, + "app.skin-site.admin.audit.record-limit": {"message": "记录上限"}, + "app.skin-site.admin.audit.save-retention": {"message": "保存"}, + "app.skin-site.admin.audit.retention-saved": {"message": "保留天数已更新。"}, + "app.skin-site.admin.donations.title": {"message": "捐赠管理"}, + "app.skin-site.admin.donations.loading": {"message": "加载中…"}, + "app.skin-site.admin.donations.failed": {"message": "无法加载捐赠列表。"}, + "app.skin-site.admin.donations.empty": {"message": "暂无捐赠记录。"}, + "app.skin-site.admin.donations.total-amount": {"message": "总额"}, + "app.skin-site.admin.donations.donor-count": {"message": "捐赠人数"}, + "app.skin-site.admin.donations.donor": {"message": "捐赠者"}, + "app.skin-site.admin.donations.amount": {"message": "金额"}, + "app.skin-site.admin.donations.updated-at": {"message": "更新时间"}, + "app.skin-site.admin.donations.external": {"message": "外部"}, + "app.skin-site.admin.donations.linked": {"message": "已绑定"}, + "app.skin-site.admin.donations.create": {"message": "新增捐赠"}, + "app.skin-site.admin.donations.save": {"message": "保存"}, + "app.skin-site.admin.donations.cancel": {"message": "取消"}, + "app.skin-site.admin.donations.edit": {"message": "编辑"}, + "app.skin-site.admin.donations.remove": {"message": "删除"}, + "app.skin-site.admin.donations.amount-field": {"message": "金额"}, + "app.skin-site.admin.donations.display-name": {"message": "显示名称"}, + "app.skin-site.admin.donations.user-uuid": {"message": "用户 UUID"}, + "app.skin-site.admin.donations.qq": {"message": "QQ"}, + "app.skin-site.admin.donations.append-mode": {"message": "在原有金额上累加"}, + "app.skin-site.admin.donations.created": {"message": "捐赠记录已创建。"}, + "app.skin-site.admin.donations.updated": {"message": "捐赠记录已更新。"}, + "app.skin-site.admin.donations.deleted": {"message": "捐赠记录已删除。"}, + "app.skin-site.admin.donations.confirm-delete": {"message": "确定删除「{name}」的捐赠记录吗?此操作不可撤销。"}, + "app.skin-site.admin.donations.load-more": {"message": "加载更多"}, + "app.skin-site.admin.mods.title": {"message": "Mod 管理"}, + "app.skin-site.admin.mods.tab-mods": {"message": "Mod 文件"}, + "app.skin-site.admin.mods.tab-tags": {"message": "规则标签"}, + "app.skin-site.admin.mods.tab-runtime": {"message": "运行时"}, + "app.skin-site.admin.mods.loading": {"message": "加载中…"}, + "app.skin-site.admin.mods.failed": {"message": "无法加载 Mod 列表。"}, + "app.skin-site.admin.mods.empty": {"message": "没有找到 Mod。"}, + "app.skin-site.admin.mods.total": {"message": "共 {count} 个 Mod"}, + "app.skin-site.admin.mods.search": {"message": "按名称搜索"}, + "app.skin-site.admin.mods.filter-all": {"message": "全部标签"}, + "app.skin-site.admin.mods.prev": {"message": "上一页"}, + "app.skin-site.admin.mods.next": {"message": "下一页"}, + "app.skin-site.admin.mods.page": {"message": "第 {page} 页"}, + "app.skin-site.admin.mods.name": {"message": "名称"}, + "app.skin-site.admin.mods.version": {"message": "版本"}, + "app.skin-site.admin.mods.mod-id": {"message": "Mod ID"}, + "app.skin-site.admin.mods.size": {"message": "大小"}, + "app.skin-site.admin.mods.loaders": {"message": "加载器"}, + "app.skin-site.admin.mods.domain-rules": {"message": "域名规则"}, + "app.skin-site.admin.mods.edit": {"message": "编辑"}, + "app.skin-site.admin.mods.save": {"message": "保存"}, + "app.skin-site.admin.mods.cancel": {"message": "取消"}, + "app.skin-site.admin.mods.remove": {"message": "删除"}, + "app.skin-site.admin.mods.updated": {"message": "Mod 已更新。"}, + "app.skin-site.admin.mods.deleted": {"message": "Mod 已删除。"}, + "app.skin-site.admin.mods.confirm-delete": {"message": "确定删除 Mod「{name}」吗?此操作不可撤销。"}, + "app.skin-site.admin.mods.rule-tags": {"message": "规则标签"}, + "app.skin-site.admin.mods.tag-name": {"message": "标签名称"}, + "app.skin-site.admin.mods.tag-rules": {"message": "规则"}, + "app.skin-site.admin.mods.tag-rules-hint": {"message": "每行一条规则。"}, + "app.skin-site.admin.mods.new-tag": {"message": "新建标签"}, + "app.skin-site.admin.mods.tag-created": {"message": "标签已创建。"}, + "app.skin-site.admin.mods.tag-updated": {"message": "标签已更新。"}, + "app.skin-site.admin.mods.tag-deleted": {"message": "标签已删除。"}, + "app.skin-site.admin.mods.confirm-delete-tag": {"message": "确定删除标签「{name}」吗?"}, + "app.skin-site.admin.mods.runtime": {"message": "运行时版本"}, + "app.skin-site.admin.mods.manifest-url": {"message": "清单地址"}, + "app.skin-site.admin.mods.upload-runtime": {"message": "上传运行时"}, + "app.skin-site.admin.mods.runtime-version": {"message": "版本"}, + "app.skin-site.admin.mods.uploading": {"message": "上传中…"}, + "app.skin-site.admin.mods.runtime-uploaded": {"message": "运行时已上传。"}, + "app.skin-site.admin.mods.activate": {"message": "设为当前"}, + "app.skin-site.admin.mods.activated": {"message": "运行时已启用。"}, + "app.skin-site.admin.mods.available": {"message": "可用"}, + "app.skin-site.admin.mods.unavailable": {"message": "不可用"}, + "app.skin-site.admin.mods.active": {"message": "当前版本"}, + "app.skin-site.admin.mods.uploader": {"message": "由 {name} 上传"}, + "app.skin-site.home.nav.title": {"message": "皮肤站"}, + "app.skin-site.home.nav.download": {"message": "资源下载"}, + "app.skin-site.home.nav.download-desc": {"message": "浏览服务器分享的文件。"}, + "app.skin-site.home.nav.review": {"message": "玩家审核"}, + "app.skin-site.home.nav.review-desc": {"message": "提交并审核玩家申请。"}, + "app.skin-site.home.nav.wiki": {"message": "服务器维基"}, + "app.skin-site.home.nav.wiki-desc": {"message": "服务器的指南与文档。"}, + "app.skin-site.home.nav.bugs": {"message": "问题追踪"}, + "app.skin-site.home.nav.bugs-desc": {"message": "反馈问题并跟踪处理进度。"}, + "app.skin-site.home.nav.map": {"message": "服务器地图"}, + "app.skin-site.home.nav.map-desc": {"message": "探索实时世界地图。"}, + "app.skin-site.home.nav.profile": {"message": "个人资料"}, + "app.skin-site.home.nav.profile-desc": {"message": "管理你的账号与皮肤。"}, + "app.skin-site.admin.wiki.title": {"message": "维基管理"}, + "app.skin-site.admin.wiki.tab-page": {"message": "页面"}, + "app.skin-site.admin.wiki.tab-audit": {"message": "审计日志"}, + "app.skin-site.admin.wiki.tab-settings": {"message": "设置"}, + "app.skin-site.admin.wiki.loading": {"message": "加载中…"}, + "app.skin-site.admin.wiki.page-path": {"message": "页面路径"}, + "app.skin-site.admin.wiki.load-page": {"message": "加载页面"}, + "app.skin-site.admin.wiki.page-not-found": {"message": "未找到该页面。"}, + "app.skin-site.admin.wiki.revision": {"message": "版本"}, + "app.skin-site.admin.wiki.author": {"message": "作者"}, + "app.skin-site.admin.wiki.updated-at": {"message": "更新时间"}, + "app.skin-site.admin.wiki.categories": {"message": "分类"}, + "app.skin-site.admin.wiki.read-policy": {"message": "阅读权限"}, + "app.skin-site.admin.wiki.edit-policy": {"message": "编辑权限"}, + "app.skin-site.admin.wiki.save-protect": {"message": "保存权限"}, + "app.skin-site.admin.wiki.protect-saved": {"message": "页面权限已更新。"}, + "app.skin-site.admin.wiki.set-home": {"message": "设为首页"}, + "app.skin-site.admin.wiki.home-set": {"message": "首页已更新。"}, + "app.skin-site.admin.wiki.set-deleted": {"message": "标记为已删除"}, + "app.skin-site.admin.wiki.set-restored": {"message": "恢复页面"}, + "app.skin-site.admin.wiki.deleted-updated": {"message": "页面删除状态已更新。"}, + "app.skin-site.admin.wiki.audit-empty": {"message": "没有审计记录。"}, + "app.skin-site.admin.wiki.audit-target": {"message": "目标"}, + "app.skin-site.admin.wiki.load-more": {"message": "加载更多"}, + "app.skin-site.admin.wiki.settings-hint": {"message": "以逗号分隔域名。"}, + "app.skin-site.admin.wiki.domains": {"message": "域名"}, + "app.skin-site.admin.wiki.home-page": {"message": "首页"}, + "app.skin-site.admin.wiki.default-skin": {"message": "默认皮肤"}, + "app.skin-site.admin.wiki.max-page-bytes": {"message": "页面最大字节数"}, + "app.skin-site.admin.wiki.max-attachment-bytes": {"message": "附件最大字节数"}, + "app.skin-site.admin.wiki.save-settings": {"message": "保存设置"}, + "app.skin-site.admin.wiki.settings-saved": {"message": "设置已保存。"}, + "app.skin-site.admin.review.tab-list": {"message": "申请列表"}, + "app.skin-site.admin.review.tab-papers": {"message": "试卷管理"}, + "app.skin-site.admin.review.papers-empty": {"message": "还没有审核试卷。"}, + "app.skin-site.admin.review.new-paper": {"message": "新建试卷"}, + "app.skin-site.admin.review.edit-paper": {"message": "编辑"}, + "app.skin-site.admin.review.paper-title": {"message": "试卷标题"}, + "app.skin-site.admin.review.paper-annotation": {"message": "描述"}, + "app.skin-site.admin.review.paper-enabled": {"message": "启用"}, + "app.skin-site.admin.review.paper-mode": {"message": "阅卷模式"}, + "app.skin-site.admin.review.paper-pass-score": {"message": "通过分数"}, + "app.skin-site.admin.review.paper-target-permissions": {"message": "通过后获得的身份"}, + "app.skin-site.admin.review.paper-questions": {"message": "题目"}, + "app.skin-site.admin.review.add-question": {"message": "添加题目"}, + "app.skin-site.admin.review.question-title": {"message": "题目"}, + "app.skin-site.admin.review.question-type": {"message": "题型"}, + "app.skin-site.admin.review.question-score": {"message": "分数"}, + "app.skin-site.admin.review.question-annotation": {"message": "题目注解"}, + "app.skin-site.admin.review.add-option": {"message": "添加选项"}, + "app.skin-site.admin.review.option-description": {"message": "选项文本"}, + "app.skin-site.admin.review.option-correct": {"message": "正确"}, + "app.skin-site.admin.review.remove-question": {"message": "删除"}, + "app.skin-site.admin.review.question-no": {"message": "第 {n} 题"}, + "app.skin-site.admin.review.paper-created": {"message": "试卷已创建。"}, + "app.skin-site.admin.review.paper-saved": {"message": "试卷已保存。"}, + "app.skin-site.admin.review.mail-title": {"message": "通过邮件"}, + "app.skin-site.admin.review.mail-enabled": {"message": "审核通过后发送邮件"}, + "app.skin-site.admin.review.mail-subject": {"message": "邮件标题"}, + "app.skin-site.admin.review.mail-message": {"message": "邮件正文"}, + "app.skin-site.admin.review.mail-extra": {"message": "邮件补充说明"}, + "app.skin-site.admin.review.mail-include-invite": {"message": "附带邀请码"}, + "app.skin-site.admin.review.prevent-duplicate": {"message": "禁止重复作答"}, + "app.skin-site.admin.review.copy-link": {"message": "复制直链"}, + "app.skin-site.admin.review.link-copied": {"message": "直链已复制。"}, + "app.skin-site.admin.review.move-up": {"message": "上移"}, + "app.skin-site.admin.review.move-down": {"message": "下移"}, + "app.skin-site.admin.review.mail-subject-required": {"message": "开启邮件后必须填写邮件标题。"}, + "app.skin-site.admin.review.mail-message-required": {"message": "开启邮件后必须填写邮件正文。"}, + "app.skin-site.admin.users.actions": {"message": "操作"}, + "app.skin-site.admin.users.audit": {"message": "审计"}, + "app.skin-site.admin.users.audit-empty": {"message": "暂无管理审计记录。"}, + "app.skin-site.admin.users.audit-more": {"message": "加载更早记录"}, + "app.skin-site.admin.users.audit-title": {"message": "用户管理审计"}, + "app.skin-site.admin.users.bot": {"message": "机器人"}, + "app.skin-site.admin.users.bot-active": {"message": "Token 有效"}, + "app.skin-site.admin.users.bot-ban": {"message": "封禁 Bot 创建"}, + "app.skin-site.admin.users.bot-creation-banned": {"message": "禁止创建 Token"}, + "app.skin-site.admin.users.bot-exists": {"message": "已创建 Bot"}, + "app.skin-site.admin.users.bot-inactive": {"message": "Token 已失效"}, + "app.skin-site.admin.users.bot-invalidate": {"message": "使 Token 失效"}, + "app.skin-site.admin.users.bot-not-exists": {"message": "尚未创建 Bot"}, + "app.skin-site.admin.users.bot-save-username": {"message": "保存机器人用户名"}, + "app.skin-site.admin.users.bot-title": {"message": "Bot 管理"}, + "app.skin-site.admin.users.bot-token-hint": {"message": "Token 标识"}, + "app.skin-site.admin.users.bot-unban": {"message": "解除 Bot 封禁"}, + "app.skin-site.admin.users.bot-username": {"message": "机器人用户名"}, + "app.skin-site.admin.users.bound-players-title": {"message": "绑定玩家"}, + "app.skin-site.admin.users.frequency": {"message": "频率限制"}, + "app.skin-site.admin.users.frequency-clear": {"message": "取消限制"}, + "app.skin-site.admin.users.frequency-current": {"message": "当前 {percent}%"}, + "app.skin-site.admin.users.frequency-hint": {"message": "以下列频率对指定用户进行丢包处理,限制其进入服务器的频率。"}, + "app.skin-site.admin.users.frequency-none": {"message": "当前未启用"}, + "app.skin-site.admin.users.frequency-range": {"message": "仅允许设置 10% 至 100% 的整数。"}, + "app.skin-site.admin.users.frequency-save": {"message": "保存限制"}, + "app.skin-site.admin.users.frequency-title": {"message": "频率限制"}, + "app.skin-site.admin.users.invite": {"message": "邀请用户"}, + "app.skin-site.admin.users.invite-copied": {"message": "邀请码已复制。"}, + "app.skin-site.admin.users.invite-copy": {"message": "复制"}, + "app.skin-site.admin.users.invite-email": {"message": "邮箱"}, + "app.skin-site.admin.users.invite-email-invalid": {"message": "请输入正确格式的邮箱。"}, + "app.skin-site.admin.users.invite-generate": {"message": "生成邀请码"}, + "app.skin-site.admin.users.invite-title": {"message": "邀请用户"}, + "app.skin-site.admin.users.legacy-adapted": {"message": "已适配旧内服"}, + "app.skin-site.admin.users.registered-at": {"message": "注册时间"}, + "app.skin-site.admin.users.status": {"message": "状态"}, + "app.skin-site.admin.users.status-banned": {"message": "已封禁"}, + "app.skin-site.admin.users.status-normal": {"message": "正常"}, + "app.skin-site.admin.mods.upload": {"message": "上传"}, + "app.skin-site.admin.mods.select-mode": {"message": "选择多个"}, + "app.skin-site.admin.mods.exit-select": {"message": "完成选择"}, + "app.skin-site.admin.mods.select-all": {"message": "全选本页"}, + "app.skin-site.admin.mods.clear-selection": {"message": "取消全选"}, + "app.skin-site.admin.mods.selected-count": {"message": "已选择 {count} 项"}, + "app.skin-site.admin.mods.delete-selected": {"message": "删除所选"}, + "app.skin-site.admin.mods.confirm-delete-multi": {"message": "确定删除选中的 {count} 个 Mod?此操作不可撤销。"}, + "app.skin-site.admin.mods.deleted-multi": {"message": "已删除所选 Mod。"}, + "app.skin-site.admin.mods.server-address": {"message": "服务器地址"}, + "app.skin-site.admin.mods.server-address-hint": {"message": "按服务器地址过滤"}, + "app.skin-site.admin.mods.apply": {"message": "应用"}, + "app.skin-site.admin.mods.clear": {"message": "清除"}, + "app.skin-site.admin.mods.upload-title": {"message": "上传 Mod"}, + "app.skin-site.admin.mods.upload-drop": {"message": "拖拽 .jar 文件到此处,或点击选择"}, + "app.skin-site.admin.mods.upload-sub": {"message": "支持多文件,单个不超过 {size} MB"}, + "app.skin-site.admin.mods.upload-tags": {"message": "附加规则标签"}, + "app.skin-site.admin.mods.upload-queue": {"message": "文件列表"}, + "app.skin-site.admin.mods.upload-pending": {"message": "等待中"}, + "app.skin-site.admin.mods.upload-checking": {"message": "检查中…"}, + "app.skin-site.admin.mods.upload-uploading": {"message": "上传中…"}, + "app.skin-site.admin.mods.upload-conflict": {"message": "名称已存在"}, + "app.skin-site.admin.mods.upload-success": {"message": "完成"}, + "app.skin-site.admin.mods.upload-error": {"message": "失败"}, + "app.skin-site.admin.mods.upload-exists": {"message": "该文件已存在于服务器。"}, + "app.skin-site.admin.mods.upload-name-conflict": {"message": "已存在同名 Mod"}, + "app.skin-site.admin.mods.upload-name-conflict-hint": {"message": "覆盖已有 Mod,或跳过此文件。"}, + "app.skin-site.admin.mods.upload-overwrite": {"message": "覆盖"}, + "app.skin-site.admin.mods.upload-skip": {"message": "跳过"}, + "app.skin-site.admin.mods.upload-close": {"message": "关闭"}, + "app.skin-site.admin.mods.upload-all-done": {"message": "全部文件处理完成。"}, + "app.skin-site.admin.mods.upload-too-large": {"message": "文件过大:{name}"}, + "app.skin-site.chat.avatar.label": {"message": "房间头像"}, + "app.skin-site.chat.avatar.upload": {"message": "上传头像"}, + "app.skin-site.chat.avatar.replace": {"message": "更换头像"}, + "app.skin-site.chat.avatar.remove": {"message": "移除头像"}, + "app.skin-site.chat.avatar.hint": {"message": "支持 PNG、JPEG、WebP,单边不超过 8192 像素,文件不超过 5 MB。"}, + "app.skin-site.chat.avatar.bad-format": {"message": "房间头像仅支持 PNG、JPEG 和 WebP 格式。"}, + "app.skin-site.chat.avatar.too-large": {"message": "房间头像大小不能超过 5 MB。"}, + "app.skin-site.chat.avatar.not-image": {"message": "房间头像必须是图片。"}, + "app.skin-site.chat.admin.title": {"message": "聊天室管理"}, + "app.skin-site.chat.admin.subtitle": {"message": "创建公开或私有房间,并将日常管理权限交给合适的成员。"}, + "app.skin-site.chat.admin.create-room": {"message": "创建房间"}, + "app.skin-site.chat.admin.loading": {"message": "加载中…"}, + "app.skin-site.chat.admin.failed": {"message": "无法加载房间列表。"}, + "app.skin-site.chat.admin.empty": {"message": "还没有聊天室。"}, + "app.skin-site.chat.admin.edit": {"message": "编辑"}, + "app.skin-site.chat.admin.remove": {"message": "删除"}, + "app.skin-site.chat.admin.save": {"message": "保存"}, + "app.skin-site.chat.admin.cancel": {"message": "取消"}, + "app.skin-site.chat.admin.name": {"message": "房间名称"}, + "app.skin-site.chat.admin.description": {"message": "房间简介"}, + "app.skin-site.chat.admin.visibility": {"message": "可见性"}, + "app.skin-site.chat.admin.visibility-public": {"message": "公开"}, + "app.skin-site.chat.admin.visibility-private": {"message": "私有"}, + "app.skin-site.chat.admin.kind": {"message": "房间类型"}, + "app.skin-site.chat.admin.kind-standard": {"message": "普通房间"}, + "app.skin-site.chat.admin.kind-settlement": {"message": "聚落房间"}, + "app.skin-site.chat.admin.prerequisite-mode": {"message": "前置条件判定"}, + "app.skin-site.chat.admin.prerequisite-any": {"message": "满足任一"}, + "app.skin-site.chat.admin.prerequisite-all": {"message": "满足全部"}, + "app.skin-site.chat.admin.prerequisite-permissions": {"message": "所需权限"}, + "app.skin-site.chat.admin.prerequisite-hint": {"message": "以逗号分隔;留空表示无限制。"}, + "app.skin-site.chat.admin.created": {"message": "房间已创建。"}, + "app.skin-site.chat.admin.updated": {"message": "房间资料已更新。"}, + "app.skin-site.chat.admin.deleted": {"message": "房间已删除。"}, + "app.skin-site.chat.admin.confirm-delete": {"message": "确认永久删除房间“{name}”及其中全部消息吗?"}, + "app.skin-site.chat.admin.manager-badge": {"message": "管理"}, + "app.skin-site.chat.admin.voice-badge": {"message": "语音"}, + "app.skin-site.chat.user-picker.label": {"message": "选择用户"}, + "app.skin-site.chat.user-picker.placeholder": {"message": "搜索用户名、邮箱或账号 ID"}, + "app.skin-site.chat.user-picker.by-user": {"message": "按用户信息"}, + "app.skin-site.chat.user-picker.by-player": {"message": "按玩家 ID"}, + "app.skin-site.chat.user-picker.hint": {"message": "支持按用户名、邮箱模糊搜索,也可输入账号 ID 搜索。"}, + "app.skin-site.chat.user-picker.player-hint": {"message": "输入玩家名称,查找绑定该玩家 ID 的用户。"}, + "app.skin-site.chat.user-picker.empty": {"message": "没有找到匹配的用户"}, + "app.skin-site.chat.user-picker.banned": {"message": "该账号已被封禁,不能执行此操作。"}, + "app.skin-site.chat.user-picker.guest": {"message": "游客不能成为房管、聚落主或聚落成员。"}, + "app.skin-site.chat.user-picker.already-in": {"message": "该用户已经在当前房间中。"}, + "app.skin-site.chat.user-picker.not-allowed": {"message": "该用户不在当前允许选择的范围内"}, + "app.skin-site.chat.user-picker.select": {"message": "选择此用户"}, + "app.skin-site.chat.user-picker.selected": {"message": "已选择此用户"}, + "app.skin-site.chat.user-picker.id": {"message": "ID"}, + "app.skin-site.chat.admin.managers": {"message": "房管"}, + "app.skin-site.chat.admin.manager-title": {"message": "任命房管"}, + "app.skin-site.chat.admin.manager-hint": {"message": "房管可以管理该房间的成员、消息与文件。"}, + "app.skin-site.chat.admin.grant": {"message": "任命"}, + "app.skin-site.chat.admin.revoke": {"message": "撤销"}, + "app.skin-site.chat.admin.manager-granted": {"message": "已任命房管。"}, + "app.skin-site.chat.admin.manager-revoked": {"message": "已撤销房管。"}, + "app.skin-site.chat.admin.owner-title": {"message": "聚落主"}, + "app.skin-site.chat.admin.owner-hint": {"message": "为该房间任命首任聚落主。"}, + "app.skin-site.chat.admin.owner-assigned": {"message": "已任命聚落主。"}, + "app.skin-site.chat.admin.owner-current": {"message": "当前聚落主:{name}"}, + "app.skin-site.chat.admin.owner-none": {"message": "尚未设置聚落主。"}, + "app.skin-site.chat.admin.audit": {"message": "审计"}, + "app.skin-site.chat.admin.audit-title": {"message": "房间审计"}, + "app.skin-site.chat.admin.audit-empty": {"message": "暂无审计记录。"}, + "app.skin-site.chat.admin.audit-target": {"message": "对象:{name}"}, + "app.skin-site.chat.admin.close": {"message": "关闭"}, + "app.skin-site.chat.voice-runtime-title": {"message": "实时语音库"}, + "app.skin-site.chat.voice-logs": {"message": "查看日志"}, + "app.skin-site.chat.voice-config": {"message": "配置语音库"}, + "app.skin-site.chat.voice-retry": {"message": "重新部署"}, + "app.skin-site.chat.voice-config-title": {"message": "语音库配置"}, + "app.skin-site.chat.voice-logs-title": {"message": "LiveKit 日志"}, + "app.skin-site.chat.voice-capacity-title": {"message": "语音容量"}, + "app.skin-site.chat.voice-capacity": {"message": "语音容量"}, + "app.skin-site.chat.participant-limit": {"message": "参与人数上限"}, + "app.skin-site.chat.listener-limit": {"message": "旁听人数上限"}, + "app.skin-site.chat.voice-capacity-updated": {"message": "语音容量已更新。"}, + "app.skin-site.chat.voice-enabled": {"message": "启用实时语音"}, + "app.skin-site.chat.voice-runtime-path": {"message": "语音库目录"}, + "app.skin-site.chat.voice-mirror": {"message": "镜像基础地址"}, + "app.skin-site.chat.voice-public-url": {"message": "公网访问地址"}, + "app.skin-site.chat.voice-bind": {"message": "监听地址"}, + "app.skin-site.chat.voice-signal-port": {"message": "信令端口"}, + "app.skin-site.chat.voice-rtc-tcp-port": {"message": "媒体 TCP 端口"}, + "app.skin-site.chat.voice-rtc-udp-port": {"message": "媒体 UDP 端口"}, + "app.skin-site.chat.voice-startup-timeout": {"message": "启动等待时间(秒)"}, + "app.skin-site.chat.voice-use-external-ip": {"message": "使用外部 IP"}, + "app.skin-site.chat.voice-node-ip": {"message": "节点 IP"}, + "app.skin-site.chat.voice-saved": {"message": "语音库配置已保存。"}, + "app.skin-site.chat.voice-logs-auto-refresh": {"message": "自动刷新"}, + "app.skin-site.chat.voice-logs-refresh": {"message": "刷新"}, + "app.skin-site.chat.voice-logs-empty": {"message": "暂无日志。"}, + "app.skin-site.chat.admin.voice-runtime-title": {"message": "实时语音库"}, + "app.skin-site.chat.admin.voice-logs": {"message": "查看日志"}, + "app.skin-site.chat.admin.voice-config": {"message": "配置语音库"}, + "app.skin-site.chat.admin.voice-retry": {"message": "重新部署"}, + "app.skin-site.chat.admin.voice-config-title": {"message": "语音库配置"}, + "app.skin-site.chat.admin.voice-logs-title": {"message": "LiveKit 日志"}, + "app.skin-site.chat.admin.voice-capacity-title": {"message": "语音容量"}, + "app.skin-site.chat.admin.voice-capacity": {"message": "语音容量"}, + "app.skin-site.chat.admin.participant-limit": {"message": "参与人数上限"}, + "app.skin-site.chat.admin.listener-limit": {"message": "旁听人数上限"}, + "app.skin-site.chat.admin.voice-capacity-updated": {"message": "语音容量已更新。"}, + "app.skin-site.chat.admin.voice-enabled": {"message": "启用实时语音"}, + "app.skin-site.chat.admin.voice-runtime-path": {"message": "语音库目录"}, + "app.skin-site.chat.admin.voice-mirror": {"message": "镜像基础地址"}, + "app.skin-site.chat.admin.voice-public-url": {"message": "公网访问地址"}, + "app.skin-site.chat.admin.voice-bind": {"message": "监听地址"}, + "app.skin-site.chat.admin.voice-signal-port": {"message": "信令端口"}, + "app.skin-site.chat.admin.voice-rtc-tcp-port": {"message": "媒体 TCP 端口"}, + "app.skin-site.chat.admin.voice-rtc-udp-port": {"message": "媒体 UDP 端口"}, + "app.skin-site.chat.admin.voice-startup-timeout": {"message": "启动等待时间(秒)"}, + "app.skin-site.chat.admin.voice-use-external-ip": {"message": "使用外部 IP"}, + "app.skin-site.chat.admin.voice-node-ip": {"message": "节点 IP"}, + "app.skin-site.chat.admin.voice-saved": {"message": "语音库配置已保存。"}, + "app.skin-site.chat.admin.voice-logs-auto-refresh": {"message": "自动刷新"}, + "app.skin-site.chat.admin.voice-logs-refresh": {"message": "刷新"}, + "app.skin-site.chat.admin.voice-logs-empty": {"message": "暂无日志。"}, + "app.skin-site.admin.wiki.tab-migration": {"message": "迁移中心"}, + "app.skin-site.admin.wiki.migration-title": {"message": "MediaWiki 迁移"}, + "app.skin-site.admin.wiki.migration-hint": {"message": "迁移内容会先进入不可见暂存区,所有页面完成导入或排除审核后才能整批发布。"}, + "app.skin-site.admin.wiki.auto-title": {"message": "从 MediaWiki API 自动读取"}, + "app.skin-site.admin.wiki.api-url": {"message": "api.php 地址"}, + "app.skin-site.admin.wiki.bot-username": {"message": "Bot 用户名(选填)"}, + "app.skin-site.admin.wiki.bot-password": {"message": "Bot 密码(选填)"}, + "app.skin-site.admin.wiki.credentials-hint": {"message": "凭据只存在于本次后台任务的内存中,携带凭据时必须使用 HTTPS。"}, + "app.skin-site.admin.wiki.allow-private": {"message": "明确允许访问内网来源"}, + "app.skin-site.admin.wiki.allow-private-hint": {"message": "内网来源可能访问皮肤站所在网络中的服务,请只对可信地址启用。"}, + "app.skin-site.admin.wiki.start-auto": {"message": "创建自动迁移任务"}, + "app.skin-site.admin.wiki.xml-title": {"message": "导入 MediaWiki XML"}, + "app.skin-site.admin.wiki.xml-file": {"message": "导出文件"}, + "app.skin-site.admin.wiki.start-xml": {"message": "上传并建立暂存任务"}, + "app.skin-site.admin.wiki.stale-rules": {"message": "疑似过时页面筛选规则"}, + "app.skin-site.admin.wiki.stale-days": {"message": "未更新天数"}, + "app.skin-site.admin.wiki.maintainer-window": {"message": "维护者统计天数"}, + "app.skin-site.admin.wiki.max-maintainers": {"message": "维护者人数不超过"}, + "app.skin-site.admin.wiki.stale-markers": {"message": "过时分类、模板或文字标记"}, + "app.skin-site.admin.wiki.stale-markers-hint": {"message": "使用逗号分隔"}, + "app.skin-site.admin.wiki.jobs": {"message": "迁移任务"}, + "app.skin-site.admin.wiki.refresh-jobs": {"message": "刷新"}, + "app.skin-site.admin.wiki.jobs-empty": {"message": "还没有迁移任务。"}, + "app.skin-site.admin.wiki.reviewed-count": {"message": "已审核 {reviewed} / {total}"}, + "app.skin-site.admin.wiki.imported-excluded": {"message": "导入 {imported} · 排除 {excluded}"}, + "app.skin-site.admin.wiki.view-job": {"message": "查看任务"}, + "app.skin-site.admin.wiki.delete-job": {"message": "删除迁移记录"}, + "app.skin-site.admin.wiki.confirm-delete-job": {"message": "确认删除迁移任务 #{id}?此操作不可恢复。"}, + "app.skin-site.admin.wiki.candidates": {"message": "候选页面"}, + "app.skin-site.admin.wiki.candidates-empty": {"message": "选择任务后查看其页面。"}, + "app.skin-site.admin.wiki.last-updated": {"message": "更新于 {date}"}, + "app.skin-site.admin.wiki.maintainer-count": {"message": "{count} 位维护者"}, + "app.skin-site.admin.wiki.conflict": {"message": "标题冲突"}, + "app.skin-site.admin.wiki.target-title": {"message": "目标标题"}, + "app.skin-site.admin.wiki.overwrite": {"message": "覆盖"}, + "app.skin-site.admin.wiki.import": {"message": "导入"}, + "app.skin-site.admin.wiki.exclude": {"message": "排除"}, + "app.skin-site.admin.wiki.publish": {"message": "整批发布"}, + "app.skin-site.admin.wiki.rollback": {"message": "回滚"}, + "app.skin-site.admin.wiki.confirm-rollback": {"message": "确认回滚本批迁移?已发布的页面将被还原。"}, + "app.skin-site.admin.wiki.state-importing": {"message": "正在读取"}, + "app.skin-site.admin.wiki.state-review": {"message": "等待审核"}, + "app.skin-site.admin.wiki.state-publishing": {"message": "正在发布"}, + "app.skin-site.admin.wiki.state-published": {"message": "已发布"}, + "app.skin-site.admin.wiki.state-failed": {"message": "失败"}, + "app.skin-site.admin.wiki.state-rolled-back": {"message": "已回滚"}, + "app.skin-site.admin.wiki.decision-pending": {"message": "待审核"}, + "app.skin-site.admin.wiki.decision-import": {"message": "导入"}, + "app.skin-site.admin.wiki.decision-exclude": {"message": "排除"}, + "app.skin-site.admin.wiki.migration-started": {"message": "迁移任务已创建。"}, + "app.skin-site.admin.wiki.xml-staged": {"message": "XML 已解析并进入逐页审核阶段。"}, + "app.skin-site.admin.wiki.published": {"message": "已发布 {imported} 个页面,覆盖 {overwritten} 个。"}, + "app.skin-site.admin.wiki.rolled-back": {"message": "迁移批次已安全回滚。"}, + "app.skin-site.admin.wiki.job-deleted": {"message": "迁移记录已删除。"}, + "app.skin-site.admin.wiki.select-xml": {"message": "请先选择 MediaWiki XML 文件。"}, + "app.skin-site.wiki.title": {"message": "StarLight 维基"}, + "app.skin-site.wiki.search-placeholder": {"message": "输入页面标题"}, + "app.skin-site.wiki.open": {"message": "打开"}, + "app.skin-site.wiki.loading": {"message": "加载中…"}, + "app.skin-site.wiki.not-found": {"message": "页面不存在。"}, + "app.skin-site.wiki.empty": {"message": "在上方输入页面标题开始阅读。"}, + "app.skin-site.wiki.updated-at": {"message": "最后编辑于 {date},编辑者 {author}"}, + "app.skin-site.wiki.revision": {"message": "修订 #{id}"}, + "app.skin-site.wiki.categories": {"message": "分类"}, + "app.skin-site.wiki.back": {"message": "返回"} } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6c8ba0..ec019e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -188,6 +188,9 @@ importers: vuedraggable: specifier: ^4.1.0 version: 4.1.0(vue@3.5.27(typescript@5.9.3)) + xxhash-wasm: + specifier: 1.1.0 + version: 1.1.0 devDependencies: '@eslint/compat': specifier: ^1.1.1 @@ -7103,6 +7106,9 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -14160,6 +14166,8 @@ snapshots: xtend@4.0.2: {} + xxhash-wasm@1.1.0: {} + y18n@5.0.8: {} yallist@3.1.1: {} diff --git a/scripts/axolotl/i18n-check.mjs b/scripts/axolotl/i18n-check.mjs index d3255ff..1f52425 100644 --- a/scripts/axolotl/i18n-check.mjs +++ b/scripts/axolotl/i18n-check.mjs @@ -31,8 +31,11 @@ const allowedUntranslatedMessages = new Set([ 'LiteLoader', 'LogShare.CN', 'UUID', + 'ID', 'QQ', 'IP', + 'URL', + 'Mod ID', 'LogShare AI', 'NeoForge', 'OptiFine',