feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
import { ref } from 'vue'
import { useLocale } from '~/utils/locale'
interface ToastMessage {
id: number
message: string
type: 'success' | 'error' | 'info' | 'warning'
duration: number
}
const toasts = ref<ToastMessage[]>([])
let toastId = 0
const exactToastTranslations = new Map<string, string>([
['需要登录才能点歌', 'Please sign in to request songs'],
['需要登录才能投票', 'Please sign in to vote'],
['您已经为这首歌投过票了', 'You have already voted for this song'],
['需要登录才能撤回歌曲', 'Please sign in to withdraw songs'],
['需要登录才能删除歌曲', 'Please sign in to delete songs'],
['歌曲已成功删除', 'Song deleted successfully'],
['歌曲已成功删除!', 'Song deleted successfully'],
['需要登录才能标记歌曲', 'Please sign in to mark songs'],
['歌曲已成功标记为已播放!', 'Song marked as played successfully'],
['需要登录才能撤回歌曲已播放状态', 'Please sign in to revert played status'],
['歌曲已成功撤回已播放状态!', 'Song played status reverted successfully'],
['需要登录才能申请重播', 'Please sign in to request replay'],
['申请重播成功', 'Replay request submitted'],
['您已经申请过重播这首歌了', 'You have already requested replay for this song'],
['需要登录才能取消重播申请', 'Please sign in to cancel replay request'],
['已取消重播申请', 'Replay request canceled'],
['没有上一首歌曲', 'No previous song'],
['没有下一首歌曲', 'No next song'],
['播放上一首歌曲失败', 'Failed to play previous song'],
['播放下一首歌曲失败', 'Failed to play next song'],
[
'浏览器限制了自动播放,请点击播放器中的播放按钮继续播放',
'The browser blocked autoplay. Click the play button in the player to continue.'
],
[
'浏览器拦截了新标签页,请允许弹窗后重试',
'The browser blocked the new tab. Allow pop-ups and try again.'
],
['已为你打开原始链接', 'Opened the original link for you'],
['当前播放链接无效,已切换备用音源', 'Current playback link is invalid. Switched to a backup source.'],
[
'连续多首歌曲播放失败,已停止自动播放',
'Multiple songs failed to play consecutively. Autoplay has been stopped.'
],
['哔哩哔哩视频播放失败,自动跳过', 'Bilibili video playback failed. Skipping automatically.'],
['已切换为单曲循环', 'Switched to single-track loop'],
['已切换为单曲播放', 'Switched to single-track playback'],
['已切换为列表循环', 'Switched to playlist loop'],
['请先登录网易云账号后再点赞评论', 'Please sign in to NetEase Cloud Music before liking comments'],
['评论点赞失败', 'Failed to like comment'],
['导入失败', 'Import failed'],
['预览已刷新', 'Preview refreshed'],
['正在准备打印', 'Preparing print...'],
['正在准备打印...', 'Preparing print...'],
['PDF导出成功', 'PDF exported successfully'],
['正在准备PDF', 'Preparing PDF...'],
['正在准备PDF...', 'Preparing PDF...'],
['预览内容未找到', 'Preview content not found'],
['排期内容过长,将自动分段导出', 'Schedule content is too long. It will be exported in sections.'],
['图片导出成功', 'Image exported successfully'],
['当前没有排期数据,请先在排期管理中添加排期', 'No schedule data yet. Add schedules in Schedule Management first.'],
['快速合并内存不足,已切换兼容模式重试', 'Not enough memory for quick merge. Retrying in compatibility mode.'],
['导入环境配置失败', 'Failed to import environment configuration'],
['批量更新状态成功', 'Batch status updated successfully'],
['用户更新成功', 'User updated successfully'],
['用户创建成功', 'User created successfully'],
['用户删除成功', 'User deleted successfully'],
['禁止在用户管理中修改自己的账户', 'You cannot modify your own account in User Management'],
['禁止在用户管理中重置自己的密码', 'You cannot reset your own password in User Management'],
['不能删除自己的账户', 'You cannot delete your own account'],
['密码重置成功', 'Password reset successfully'],
[
'Excel处理库加载失败,请刷新页面后重试',
'Failed to load the Excel processing library. Refresh the page and try again.'
],
['歌曲已成功上传到网易云音乐云盘', 'Song uploaded to NetEase Cloud Music Drive successfully'],
[
'数据库恢复完成,3秒后将返回首页重新登录',
'Database restore completed. Returning home to sign in again in 3 seconds.'
],
['无法解析备份文件,请检查文件格式是否正确', 'Unable to parse backup file. Please check the file format.'],
['无法解析备份文件,请检查文件格式是否正确。', 'Unable to parse backup file. Please check the file format.'],
['备份创建失败:无效的响应格式', 'Backup creation failed: invalid response format'],
['备份创建失败:服务器响应格式错误', 'Backup creation failed: invalid server response format'],
['文件下载失败', 'File download failed'],
['未知错误', 'Unknown error']
])
const toastOperationTranslations = new Map<string, string>([
['播放', 'Playback'],
['上传', 'Upload'],
['预下载', 'Pre-download'],
['合并', 'Merge'],
['加载排期', 'Loading schedule'],
['打印', 'Print'],
['导出PDF', 'PDF export'],
['导出长图', 'Long image export'],
['文件下载', 'File download'],
['删除用户', 'Deleting user'],
['加载用户', 'Loading users'],
['导入备份', 'Backup import'],
['备份导入', 'Backup import'],
['创建备份', 'Backup creation'],
['保存用户', 'Saving user'],
['重置密码', 'Password reset'],
['批量更新', 'Batch update'],
['添加歌曲', 'Adding song'],
['更新歌曲', 'Updating song'],
['删除歌曲', 'Deleting song'],
['批量删除', 'Batch deletion'],
['驳回歌曲', 'Rejecting song'],
['刷新歌曲', 'Refreshing songs'],
['标记已播放', 'Marking as played'],
['标记未播放', 'Marking as unplayed'],
['导入', 'Import']
])
const translateToastDetail = (detail: string) => exactToastTranslations.get(detail.trim()) || detail
const normalizeToastMessage = (message: unknown): string => {
if (typeof message === 'string') return message
return String(message ?? '')
}
const translateToastMessage = (message: unknown, locale: string) => {
const normalizedMessage = normalizeToastMessage(message)
if (locale === 'zh-CN') return normalizedMessage
const normalized = normalizedMessage.trim()
const withoutPunctuation = normalized.replace(/[。!]$/, '')
const exactMatch = exactToastTranslations.get(normalized)
if (exactMatch) return exactMatch
const punctuationlessMatch = exactToastTranslations.get(withoutPunctuation)
if (punctuationlessMatch) return punctuationlessMatch
const dynamicRules: Array<[RegExp, (match: RegExpMatchArray) => string]> = [
[/^已取消对歌曲《(.+)》的投票$/, (match) => `Vote for "${match[1]}" canceled`],
[/^为歌曲《(.+)》投票成功!?$/, (match) => `Voted for "${match[1]}" successfully`],
[/^备份文件已下载[::]\s*(.+)$/, (match) => `Backup file downloaded: ${match[1]}`],
[/^文件下载失败[::]\s*(.+)$/, (match) => `File download failed: ${translateToastDetail(match[1])}`],
[/^成功下载\s*(\d+)\s*首歌曲$/, (match) => `Downloaded ${match[1]} songs successfully`],
[/^已完成分段导出,共\s*(\d+)\s*张图片$/, (match) => `Sectioned export completed with ${match[1]} images`],
[/^部分更新成功,(\d+)\s*个用户因权限或状态等原因跳过$/, (match) => `Partially updated successfully. ${match[1]} users were skipped due to permissions or status.`],
[/^成功更新\s*(\d+)\s*个用户的年级$/, (match) => `Updated grades for ${match[1]} users successfully`],
[/^(.+?)失败[::]\s*(.+)$/, (match) => {
const operation = toastOperationTranslations.get(match[1]) || match[1]
return `${operation} failed: ${translateToastDetail(match[2])}`
}]
]
for (const [pattern, translate] of dynamicRules) {
const match = normalized.match(pattern)
if (match) return translate(match)
}
return normalizedMessage
}
export const useToast = () => {
const { currentLocale } = useLocale()
const showToast = (
message: unknown,
type: 'success' | 'error' | 'info' | 'warning' = 'info',
duration = 3000
) => {
const id = ++toastId
const toast: ToastMessage = {
id,
message: translateToastMessage(message, currentLocale.value),
type,
duration
}
toasts.value.push(toast)
// 自动移除toast
setTimeout(() => {
removeToast(id)
}, duration)
return id
}
const removeToast = (id: number) => {
const index = toasts.value.findIndex((toast) => toast.id === id)
if (index > -1) {
toasts.value.splice(index, 1)
}
}
const success = (message: unknown, duration?: number) => showToast(message, 'success', duration)
const error = (message: unknown, duration?: number) => showToast(message, 'error', duration)
const info = (message: unknown, duration?: number) => showToast(message, 'info', duration)
const warning = (message: unknown, duration?: number) => showToast(message, 'warning', duration)
return {
toasts,
showToast,
removeToast,
success,
error,
info,
warning
}
}