Compare commits

...

121 Commits
dev ... v4.9.0

Author SHA1 Message Date
alger
88e1c2cd81 feat: 更新至4.9.0 2025-08-07 23:26:28 +08:00
alger
18853d401f feat: 替换Token设置弹窗为Cookie设置弹窗,并优化Token保存逻辑 2025-08-07 23:24:43 +08:00
alger
6b27116584 feat: 添加登录类型条件渲染 2025-08-07 23:11:25 +08:00
alger
283a123590 feat: 更新文本 2025-08-07 23:05:33 +08:00
alger
1597fbf108 feat: 修改Cookie文字 2025-08-07 22:57:37 +08:00
alger
3ba85f34ed feat: 优化类型处理 2025-08-07 22:57:17 +08:00
alger
daa8e7514d feat: 优化登录功能 添加UID登录功能 2025-08-07 22:57:02 +08:00
alger
aeb7f0361d feat: 播放速度设置弹窗标题添加速度显示 2025-08-07 22:56:41 +08:00
alger
16aeaf2948 feat: 添加Cookie登录功能及自动获取等相关管理设置
feat: #413 #424
2025-08-06 22:36:30 +08:00
alger
09ccd9f2a6 fix: 修复菜单显示不全的问题 添加滚动条
fixed(#328): 页面左侧菜单行高问题
2025-07-29 22:57:09 +08:00
alger
679089eda9 fix: 修复音量调整不同步的问题
fixed(#331):  软件存在两套音量调整逻辑,快捷键调整和音量条调整音量不一致
2025-07-29 22:19:34 +08:00
Alger
f2eba9a6d0 Merge pull request #403 from Hellodwadawd12312312/main
feat: 使用滑块增强播放速度控制并改进空安全
2025-07-26 23:51:06 +08:00
algerkong
306215669d feat: 添加日语和韩语国际化,并且优化语言相关代码 2025-07-26 23:32:58 +08:00
algerkong
b7a58a0073 fix: 修复歌曲初始化问题 2025-07-26 23:29:53 +08:00
Qumo
8fb382e21f feat: enhance playback speed controls with slider and improve null safety for playMusic 2025-07-24 08:22:03 +02:00
alger
c08c2cbf19 refactor: 更新 eslint 和 prettier 配置 格式化代码 2025-07-23 23:54:35 +08:00
alger
d1f5c8af84 feat: 桌面歌词添加主题颜色面板组件 2025-07-23 22:47:22 +08:00
alger
d5ba218b10 feat: 添加主题根据系统切换功能
feat: #387
2025-07-23 22:45:47 +08:00
alger
e489ab46b5 fix: 修复歌单列表页面翻页类型问题
fixed: #398
2025-07-23 22:43:59 +08:00
alger
9b3019d04b fix: 修复mini窗口恢复时导致的应用窗口变小问题 2025-07-22 00:40:34 +08:00
Alger
1e213388c1 Merge pull request #396 from algerkong/fix/mac-window-close
🐞 fix: 修复mac快捷键关闭窗口报错的问题
2025-07-21 23:51:43 +08:00
alger
67ef4d7221 🐞 fix: 修复mac快捷键关闭窗口报错的问题 2025-07-21 23:49:21 +08:00
alger
62a504e7d3 feat: 托盘菜单添加繁体中文设置 2025-07-21 23:35:48 +08:00
Alger
386db7384d Merge pull request #392 from dongguacute/main
feat(i18n): add Traditional Chinese (zh-Hant) localization support
2025-07-21 23:33:50 +08:00
dongguacute
2cc03cb080 feat(i18n): add Traditional Chinese (zh-Hant) localization support
add Traditional Chinese translations for all application strings
include new language files for artist, history, donation, favorite, login, search, user, common, download, player, settings, comp components
update main i18n configuration to include zh-Hant language
add zh-Hant option to language switcher component
2025-07-20 22:53:25 +08:00
algerkong
7891bf45fd feat: 更新版本至 4.8.2 2025-06-28 18:16:19 +08:00
algerkong
2f339b1373 🐞 fix: 修复在歌词界面添加到歌单抽屉被遮挡问题 2025-06-28 17:46:24 +08:00
algerkong
749a2a69c4 feat: 重新设计歌词页面的迷你播放栏 2025-06-28 17:40:57 +08:00
algerkong
5b97010b32 🐞 fix: 修复解析错误问题, 优化播放效果 2025-06-28 17:31:37 +08:00
algerkong
694dff425b feat: 添加清除自定义音源功能 2025-06-28 17:26:07 +08:00
algerkong
e8cf253567 feat: 更新版本至 4.8.1 2025-06-27 18:59:40 +08:00
algerkong
4d831777f1 🔧 chore: 移除统计,更新支持的音乐源列表 2025-06-27 18:49:37 +08:00
algerkong
95c255d2ba 🐞 fix: 修复网页端和快捷键无法调整音量的问题 2025-06-23 20:58:08 +08:00
algerkong
d739a6701b feat: 添加通知抽屉组件,优化用户体验并支持多平台提示 2025-06-23 20:56:50 +08:00
alger
d7169efcf5 feat: 更新版本至 4.8.0 2025-06-20 23:32:29 +08:00
alger
a7cce28050 feat: 在构建配置中添加对 Linux RPM 包的支持 2025-06-20 23:32:08 +08:00
alger
e99385c512 feat: 添加版本号比较函数,优化更新检查逻辑 2025-06-20 23:17:16 +08:00
alger
72c11eef6c 🎨 style: 更新歌词区域按钮样式,修复歌词时间调整按钮不显示问题 2025-06-20 23:06:57 +08:00
alger
facb03d3e1 feat: 添加 linux rpm 构建目标支持 2025-06-20 22:27:48 +08:00
alger
902feff2fb 🔧 chore: 降级 electron 版本至 35.2.0 2025-06-20 22:23:56 +08:00
alger
426cafd54c feat: 更新 AppMenu 组件,增加移动端适配,优化工具提示的禁用条件 2025-06-20 21:15:13 +08:00
alger
e4ed089085 chore: 更新 .gitignore 文件,添加 android/app/release 目录以排除构建文件 2025-06-20 21:08:43 +08:00
alger
7f6e11e508 feat: 移除代理配置,简化构建命令,更新环境变量设置 2025-06-20 21:08:26 +08:00
alger
81b61e4575 feat: 增强移动端播放页面效果,优化横屏效果,添加播放列表功能 2025-06-20 21:07:17 +08:00
Alger
66aa6b7aff Merge pull request #326 from hecai84/main
添加任务栏缩略图控制按钮
2025-06-20 17:49:09 +08:00
hecai
58ab9906cc 启动默认显示缩略图控制按钮。
(cherry picked from commit 1f438e391ab7bb37e38a31ec571724d33f35310b)
2025-06-19 09:26:28 +08:00
hecai
9bec67ebf9 添加任务栏缩略图控制按钮
(cherry picked from commit e0ddb7cb4821b5b48ed3ffb99a44c00c8cb4d46e)
2025-06-18 15:52:37 +08:00
Alger
386c9c7067 Merge pull request #320 from Hellodwadawd12312312/feature
修复音频初始化音量问题,完善翻译
2025-06-17 11:34:26 +08:00
Felix
b95f5e1b2f small fix 2025-06-16 08:44:50 +02:00
Qumo
090103bf1a Update audioService.ts 2025-06-16 07:47:40 +02:00
Qumo
5ee60d751e Update audioService.ts 2025-06-16 07:39:35 +02:00
Qumo
a85b5ff58b Merge branch 'algerkong:main' into feature 2025-06-16 07:28:28 +02:00
alger
58922dc91b feat: 更新类型定义文件路径,移除旧的 auto-imports 和 components 定义文件 2025-06-12 22:58:01 +08:00
alger
0d89e15e01 feat: 添加横屏模式支持,优化歌词和播放控制布局 2025-06-12 22:57:24 +08:00
algerkong
b9c38d257a feat: 重构播放控制逻辑,添加播放进度恢复功能并清理无用代码 2025-06-11 20:12:52 +08:00
algerkong
d227ac8b34 feat: 优化播放栏无法控制隐藏问题 2025-06-11 20:10:33 +08:00
algerkong
f9d85f11ad feat: 去除无用代码 2025-06-11 20:05:43 +08:00
Felix
49595ef57f more translation 2025-06-10 13:31:33 +02:00
Alger
cceb1de3fb Merge pull request #304 from Hellodwadawd12312312/feature
添加搜索类型的翻译
2025-06-09 10:41:25 +08:00
Felix
f59b5d5602 translation 2025-06-08 14:49:24 +02:00
alger
fabcf289dc feat: 优化歌曲列表组件布局,添加底部间距以提升视觉效果 2025-06-07 22:47:34 +08:00
alger
934580552d feat: 优化歌词组件和移动端界面设计 2025-06-07 22:30:39 +08:00
alger
6f1909a028 🐞 fix: 修复刷新后第一次播放出现的无法播放问题 2025-06-07 22:10:55 +08:00
alger
c5d71cf53c feat: 添加 Vite 配置文件并更新 package.json,支持开发模式下的 Web 预览 2025-06-07 21:34:19 +08:00
alger
21b2fc08be feat: 优化移动端界面设计以及歌词界面设计 添加播放模式选择 2025-06-07 10:48:54 +08:00
alger
155bdf246c feat: 优化提示组件,支持位置和图标显示选项 2025-06-06 23:37:24 +08:00
alger
e46df8a04e feat: 优化窗口大小管理功能,优化窗口状态保存与恢复逻辑
- 引入窗口大小管理器,初始化窗口大小管理
- 优化窗口状态保存与恢复,确保在迷你模式下正确应用窗口大小
- 移除不必要的代码,简化窗口管理逻辑
- 更新窗口创建逻辑,确保窗口大小和位置的正确性
2025-06-06 23:37:06 +08:00
alger
b203077cad feat: 添加下载设置功能,支持自定义文件名格式和下载路径配置
- 新增下载设置抽屉,允许用户设置下载路径和文件名格式
- 支持多种文件名格式预设和自定义格式
- 实现下载项的显示名称格式化
- 优化下载管理逻辑,避免重复通知
2025-06-05 23:02:41 +08:00
alger
a08fbf1ec8 style: 优化播放列表抽屉样式,调整标题和按钮颜色以提升可读性 2025-06-05 22:19:55 +08:00
alger
edd393c8ac feat: 新增歌单导入功能
添加歌单导入功能,支持通过链接、文本和元数据三种方式导入歌单
- 实现链接导入、文本导入和元数据导入三种方式
- 添加导入状态检查和显示功能
2025-06-04 22:53:49 +08:00
alger
8988cdb082 feat: 在音乐列表页面中添加 Electron 环境判断,优化多选下载操作的显示逻辑 2025-06-04 22:46:35 +08:00
alger
1221101821 feat: 列表添加多选下载功能,支持批量选择和下载音乐 2025-06-04 20:19:44 +08:00
alger
3ac3159058 feat: 添加下载管理页面, 引入文件类型检测库以支持多种音频格式 2025-06-03 22:35:04 +08:00
Alger
bfaa06b0d5 Merge pull request #281 from algerkong/feat/window-auto-size
feat: 添加主窗口自适应大小功能,页面缩放功能,支持缩放因子的调整和重置,并在搜索栏中提供缩放控制
2025-05-28 22:12:29 +08:00
alger
61700473b9 feat: 添加主窗口自适应大小功能,页面缩放功能,支持缩放因子的调整和重置,并在搜索栏中提供缩放控制 2025-05-28 22:08:17 +08:00
alger
bf4bcfcde6 chore: 更新 .gitignore 文件 2025-05-28 22:06:13 +08:00
Alger
475d7d2595 Merge pull request #280 from algerkong/fix/day-list
feat: 重构每日推荐数据加载逻辑,提取为独立函数并优化用户状态判断
2025-05-28 22:04:14 +08:00
alger
5e704a1f3c feat: 重构每日推荐数据加载逻辑,提取为独立函数并优化用户状态判断 2025-05-28 22:02:59 +08:00
Alger
6faab820da Merge pull request #279 from algerkong/fix/volume-color
feat: 添加mini播放栏鼠标滚轮调整音量 并优化音量滑块数字不展示问题
2025-05-28 22:01:07 +08:00
alger
5c7278544a feat: 添加mini播放栏鼠标滚轮调整音量 并优化音量滑块数字不展示问题 2025-05-28 21:58:32 +08:00
Alger
4c24bb9257 feat: Update README.md 2025-05-27 15:18:42 +08:00
alger
c975344dd0 feat: 添加歌词矫正功能,支持增加和减少矫正时间 2025-05-26 22:58:42 +08:00
Alger
08a14359a5 Merge pull request #268 from algerkong/fix/modal-zindex
fix: 修复更多设置弹窗被歌词窗口遮挡问题 并优化为互斥弹窗, 优化样式
2025-05-25 19:28:54 +08:00
alger
62e5166953 fix: 修复更多设置弹窗被歌词窗口遮挡问题 并优化为互斥弹窗, 优化样式 2025-05-25 19:26:24 +08:00
alger
7685ad3939 feat: 在配置中添加 publicDir 选项以指定资源目录 2025-05-25 11:35:16 +08:00
alger
d7c06586d6 feat: 在 EQControl 组件标题中添加桌面版可用提示标签 2025-05-25 11:07:06 +08:00
alger
5070a085e9 feat: 优化收藏和历史列表组件,添加加载状态管理和动画效果 2025-05-24 19:23:38 +08:00
alger
e5adb8aa72 fix: 修复设置页面动画速度滑块样式和文本错误 2025-05-24 19:23:13 +08:00
alger
d08439c99e feat:v4.7.1 2025-05-24 10:11:50 +08:00
alger
dee4515cb3 fix: 修复切换收藏和不喜欢状态时事件处理逻辑 2025-05-24 10:11:29 +08:00
alger
53bc1774ff fix: 修复下载请求中的音乐 URL 处理逻辑 2025-05-24 10:02:15 +08:00
alger
589540be29 feat: 优化歌曲组件事件处理,使用展开运算符简化事件传递 2025-05-23 22:43:01 +08:00
alger
2bcae85419 feat: 更新应用ico图标 2025-05-23 21:57:34 +08:00
alger
6e68927eec feat: 更新应用图标 2025-05-23 21:46:18 +08:00
alger
a4eea18fa5 feat:更新 4.7.0 2025-05-23 21:33:58 +08:00
alger
fe5b1d5de8 feat: 添加主窗口失去焦点时禁用最大化功能 2025-05-23 21:29:43 +08:00
alger
c8e6db11c9 feat: 更新应用图标,替换为新版本的图标文件 2025-05-23 20:43:13 +08:00
alger
5bef0e44a0 feat: 为歌曲下拉菜单添加圆角样式,优化歌曲预览布局 2025-05-23 20:08:57 +08:00
alger
d56a25eb3c feat: 在用户歌单中添加“我创建的”标签,优化获取用户歌单的逻辑 2025-05-23 20:08:40 +08:00
alger
a449b74ef2 feat: 添加 husky 预提交和预推送钩子,运行类型检查以确保代码质量 2025-05-23 20:07:29 +08:00
alger
ad7b504eef 🦄 refactor: 重构歌曲组件,添加基础组件和多种样式,优化播放列表抽屉功能 2025-05-23 19:39:46 +08:00
alger
6048e243c7 feat: 在歌手详情页添加歌曲操作工具栏,支持播放全部、添加到播放列表和搜索功能,布局切换功能 2025-05-23 19:39:32 +08:00
alger
0c74291a34 feat: 添加所有用户的关注和粉丝列表点击 优化播放排行获取和无权限展示 2025-05-23 19:39:26 +08:00
alger
7fa0fa5221 feat: 添加 macOS 下点击 Dock 图标激活主窗口的功能 2025-05-23 19:39:21 +08:00
alger
95af222da7 feat: 添加鼠标滚轮调整音量功能,并显示音量百分比 2025-05-23 19:39:16 +08:00
alger
9eefe62fba refactor: 移除未使用的导入和格式问题 2025-05-22 22:21:53 +08:00
Alger
b621995e24 Merge pull request #256 from algerkong/fix/downloadurl
fix: 修复并优化下载功能,重构添加 hook
2025-05-22 22:15:58 +08:00
Alger
91f97ff76b Merge branch 'main' into fix/downloadurl 2025-05-22 22:15:51 +08:00
Alger
cce2b96d29 Merge pull request #255 from algerkong/feat/nolike
feat: 歌曲右键 添加不喜欢功能以过滤每日推荐歌曲
2025-05-22 22:12:46 +08:00
alger
a0935c74fe feat: 歌曲右键 添加不喜欢功能以过滤每日推荐歌曲 2025-05-22 22:11:10 +08:00
alger
df5ecb6eb5 feat: 添加 tab监听以刷新下载列表 2025-05-22 20:59:06 +08:00
alger
ca51020602 refactor: 将下载逻辑提取到useDownload hook中 2025-05-22 20:58:47 +08:00
alger
258828ffbd feat: 双击播放由双击歌曲名改为双击整个组件都可以 2025-05-22 20:12:55 +08:00
alger
91b1ff7df9 fix: 修复播放音乐时URL未正确更新的问题 2025-05-22 20:12:47 +08:00
alger
8cc617a5f6 docs: 更新文档图片和README内容 2025-05-20 22:45:45 +08:00
alger
170ac45115 style: 顶部定时 添加悬停缩放效果和光标指针样式 2025-05-20 21:22:41 +08:00
alger
2dd45351e5 feat: 添加定时器过期检查功能 优化顶部定时点击 2025-05-20 20:57:16 +08:00
alger
f5f0dbb222 feat: 优化播放栏,整合高级控制菜单,将定时、均衡器、速度控制改为更多设置按钮显示, 添加定时关闭顶部显示功能 2025-05-19 23:13:06 +08:00
Alger
7fca6db2a3 Merge pull request #241 from Java-wyx/speed-up
feat: 添加播放速度控制功能
2025-05-19 19:17:26 +08:00
Java-wyx
655473699a feat: 添加播放速度控制功能
现有播放器不支持改变播放速度,用户无法实现 0.5×、1.5×、2.0× 等快进/慢放需求。为了提升可用性和灵活性,决定在播放栏增加速度选择菜单,并支持 Media Session API 同步速率
2025-05-19 17:59:20 +08:00
Alger
4d371df510 Merge pull request #236 from algerkong/dev
feat: 优化页面效果 音源解析优化
2025-05-18 13:12:18 +08:00
243 changed files with 20029 additions and 5865 deletions

View File

@@ -1,92 +0,0 @@
---
description: 这个规则是项目描述
globs:
alwaysApply: false
---
您是 TypeScript、Node.js、Vue3、Electron、naive-ui、VueUse 和 Tailwind 方面的专家。
项目结构
- 这是 Electron 项目,使用 Vue3 和 Pinia 进行开发的第三方网易云音乐播放器。
- 使用 Vue3 和 Pinia 进行开发。
- 使用 Pinia 进行状态管理。
- 使用 VueUse 进行状态管理。
- 使用 naive-ui 进行 UI 设计。
- 使用 Tailwind 进行样式设计。
- 使用 remixicon 进行图标设计。
- 使用 vite 进行项目构建。
- 使用 electron-builder 进行项目打包。
- 使用 electron-vite 进行项目开发。
- 使用 netease-cloud-music-api 进行网易云音乐接口调用。
- 使用 electron-store 进行本地数据存储。
- 使用 axios 进行网络请求。
- 使用 @unblockneteasemusic/server 进行网易云音乐解锁。
- 使用 vue-i18n 进行国际化。目录为 src/i18n
代码风格和结构
- 编写简洁、技术性的 TypeScript 代码,并提供准确示例。
- 使用组合 API 和声明性编程模式;避免使用选项 API。
- 优先使用迭代和模块化,而不是代码重复。
- 使用带有助动词的描述性变量名称(例如 isLoading、hasError
- 结构文件:导出的组件、可组合项、帮助程序、静态内容、类型。
命名约定
- 使用带破折号的小写字母表示目录(例如 components/auth-wizard
- 使用 PascalCase 表示组件名称(例如 AuthWizard.vue
- 使用 camelCase 表示可组合项(例如 useAuthState.ts
TypeScript 用法
- 对所有代码使用 TypeScript优先使用类型而不是接口。
- 避免使用枚举;改用 const 对象。
- 将 Vue 3 与 TypeScript 结合使用,利用 defineComponent 和 PropType。
语法和格式
- 对方法和计算属性使用箭头函数。
- 避免在条件中使用不必要的花括号;对简单语句使用简洁的语法。
- 使用模板语法进行声明式渲染。
UI 和样式
- 使用 naive-ui 和 Tailwind 进行组件和样式设计。
- 使用 Tailwind CSS 实现响应式设计;采用移动优先方法。
图标
- 使用 remixicon 作为图标库。
性能优化
- 对异步组件使用 Suspense。
- 为路由和组件实现延迟加载。
关键约定
- 对常见可组合项和实用函数使用 VueUse。
- 使用 Pinia 进行状态管理。
- 优化 Web VitalsLCP、CLS、FID
Vue 3 和 Composition API 最佳实践
- 使用 <script setup lang="ts"> 语法进行简洁的组件定义。
- 利用 ref、reactive 和 computed 进行反应状态管理。
- 在适当的情况下使用 provide/inject 进行依赖注入。
- 实现自定义可组合项以实现可重用逻辑。
Electron 最佳实践
- 使用 Electron 和 Vue.js 进行跨平台桌面应用程序开发。
- 使用 Electron 的 API 和 Vue.js 的组合 API 进行开发。
- 实现自定义可组合项以实现可重用逻辑。
组件导入
- 使用 auto-import 进行组件导入。
- naive-ui 组件自动导入 不需要手动导入。
关注官方 Electron 和 Vue.js 文档,了解有关数据获取、渲染和路由的最新最佳实践。
问题修复
- 思考 5-7 种可能导致问题的来源,并根据可能性、对功能的影响以及在类似问题中的出现频率进行优先排序。仅考虑与错误日志、最近代码变更和系统约束相匹配的来源。忽略外部依赖,除非日志明确指向它们。
- 一旦缩小到 1-2 个最可能的来源,将其与历史错误日志、相关系统状态和预期行为进行交叉验证。如果发现不一致,调整你的假设。
- 在添加日志时,确保它们被策略性地放置,以便同时确认或排除多个潜在原因。如果日志不支持你的假设,请先提出替代的调试策略,再继续深入分析。
- 在实施修复之前,先总结问题现象、经过验证的假设,以及预期的日志输出,以确认问题是否真正得到解决。

View File

@@ -1,148 +0,0 @@
---
description: 这个规则是项目结构
globs:
alwaysApply: false
---
# AlgerMusicPlayer 项目结构
AlgerMusicPlayer 是一个基于 Electron、Vue 3、TypeScript 开发的网易云音乐第三方播放器。
## 技术栈
- **前端框架**Vue 3 + TypeScript
- **UI 组件库**naive-ui
- **样式框架**Tailwind CSS
- **图标库**remixicon
- **状态管理**Pinia
- **工具库**VueUse
- **构建工具**Vite, electron-vite
- **打包工具**electron-builder
- **国际化**vue-i18n
- **HTTP 客户端**axios
- **本地存储**electron-store localstorage
- **网易云音乐 API**netease-cloud-music-api
- **音乐解锁**@unblockneteasemusic/server
## 项目结构
```
AlgerMusicPlayer/
├── build/ # 构建相关文件
├── docs/ # 项目文档
├── node_modules/ # 依赖包
├── out/ # 构建输出目录
├── resources/ # 资源文件
├── src/ # 源代码
│ ├── i18n/ # 国际化配置
│ │ ├── lang/ # 语言包
│ │ ├── main.ts # 主进程国际化入口
│ │ └── renderer.ts # 渲染进程国际化入口
│ ├── main/ # Electron 主进程
│ │ ├── modules/ # 主进程模块
│ │ ├── index.ts # 主进程入口
│ │ ├── lyric.ts # 歌词处理
│ │ ├── server.ts # 服务器
│ │ ├── set.json # 设置
│ │ └── unblockMusic.ts # 音乐解锁
│ ├── preload/ # 预加载脚本
│ │ ├── index.ts # 预加载脚本入口
│ │ └── index.d.ts # 预加载脚本类型声明
│ └── renderer/ # Vue 渲染进程
│ ├── api/ # API 请求
│ ├── assets/ # 静态资源
│ ├── components/ # 组件
│ │ ├── common/ # 通用组件
│ │ ├── home/ # 首页组件
│ │ ├── lyric/ # 歌词组件
│ │ ├── settings/ # 设置组件
│ │ └── ... # 其他组件
│ ├── const/ # 常量定义
│ ├── directive/ # 自定义指令
│ ├── hooks/ # 自定义 Hooks
│ ├── layout/ # 布局组件
│ ├── router/ # 路由配置
│ ├── services/ # 服务
│ ├── store/ # Pinia 状态管理
│ │ ├── modules/ # Pinia 模块
│ │ └── index.ts # Pinia 入口
│ ├── type/ # 类型定义
│ ├── types/ # 更多类型定义
│ ├── utils/ # 工具函数
│ ├── views/ # 页面视图
│ ├── App.vue # 根组件
│ ├── index.css # 全局样式
│ ├── index.html # HTML 模板
│ ├── main.ts # 渲染进程入口
│ └── ... # 其他文件
├── .env.development # 开发环境变量
├── .env.development.local # 本地开发环境变量
├── .env.production.local # 本地生产环境变量
├── .eslintrc.cjs # ESLint 配置
├── .gitignore # Git 忽略文件
├── .prettierrc.yaml # Prettier 配置
├── electron-builder.yml # electron-builder 配置
├── electron.vite.config.ts # electron-vite 配置
├── package.json # 项目配置
├── postcss.config.js # PostCSS 配置
├── tailwind.config.js # Tailwind 配置
├── tsconfig.json # TypeScript 配置
├── tsconfig.node.json # 节点 TypeScript 配置
└── tsconfig.web.json # Web TypeScript 配置
```
## 主要组件说明
### 主进程 (src/main)
主进程负责创建窗口、处理系统层面的交互以及与渲染进程的通信。
- **index.ts**: 应用主入口,负责创建窗口和应用生命周期管理
- **lyric.ts**: 歌词解析和处理
- **unblockMusic.ts**: 网易云音乐解锁功能
- **server.ts**: 本地服务器
### 预加载脚本 (src/preload)
预加载脚本在渲染进程加载前执行,提供了渲染进程和主进程之间的桥接功能。
### 渲染进程 (src/renderer)
渲染进程是基于 Vue 3 的前端应用,负责 UI 渲染和用户交互。
- **components/**: 包含各种 UI 组件
- **common/**: 通用组件
- **home/**: 首页相关组件
- **lyric/**: 歌词显示组件
- **settings/**: 设置界面组件
- **MusicList.vue**: 音乐列表组件
- **MvPlayer.vue**: MV 播放器
- **EQControl.vue**: 均衡器控制
- **...**: 其他组件
- **store/**: Pinia 状态管理
- **modules/**: 各功能模块的状态管理
- **index.ts**: 状态管理入口
- **views/**: 页面视图组件
- **router/**: 路由配置
- **api/**: API 请求封装
- **utils/**: 工具函数
## 开发指南
### 命名约定
- 目录使用 kebab-case (如: components/auth-wizard)
- 组件文件名使用 PascalCase (如: AuthWizard.vue)
- 可组合式函数使用 camelCase (如: useAuthState.ts)
### 代码风格
- 使用 Composition API 和 `<script setup>` 语法
- 使用 TypeScript 类型系统
- 优先使用类型而非接口
- 避免使用枚举,使用 const 对象代替
- 使用 tailwind 实现响应式设计

View File

@@ -1,12 +1,4 @@
# 你的接口地址 (必填)
VITE_API_LOCAL = ***
# 音乐破解接口地址
VITE_API_MUSIC = ***
# 代理地址
VITE_API_PROXY = ***
# 本地运行代理地址
VITE_API_PROXY = /api
VITE_API_MUSIC_PROXY = /music
VITE_API_PROXY_MUSIC = /music_proxy
VITE_API = http://127.0.0.1:30488
# 音乐破解接口地址 web端
VITE_API_MUSIC = ***

View File

@@ -1,4 +0,0 @@
node_modules
dist
out
.gitignore

View File

@@ -1,137 +0,0 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'@vue/typescript/recommended',
'plugin:vue/vue3-recommended',
'plugin:vue-scoped-css/base',
'@electron-toolkit',
'@electron-toolkit/eslint-config-ts/eslint-recommended',
'plugin:prettier/recommended'
],
env: {
browser: true,
node: true,
jest: true,
es6: true
},
globals: {
defineProps: 'readonly',
defineEmits: 'readonly'
},
plugins: ['vue', '@typescript-eslint', 'simple-import-sort'],
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
allowImportExportEverywhere: true,
ecmaFeatures: {
jsx: true
}
},
settings: {
'import/extensions': ['.js', '.jsx', '.ts', '.tsx']
},
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off',
'no-underscore-dangle': 'off',
'no-nested-ternary': 'off',
'no-console': 'off',
'no-await-in-loop': 'off',
'no-continue': 'off',
'no-restricted-syntax': 'off',
'no-return-assign': 'off',
'no-unused-expressions': 'off',
'no-return-await': 'off',
'no-plusplus': 'off',
'no-param-reassign': 'off',
'no-shadow': 'off',
'guard-for-in': 'off',
'import/extensions': 'off',
'import/no-unresolved': 'off',
'import/no-extraneous-dependencies': 'off',
'import/prefer-default-export': 'off',
'import/first': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'vue/first-attribute-linebreak': 0,
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
],
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'class-methods-use-this': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error'
},
overrides: [
{
files: ['*.vue'],
rules: {
'vue/component-name-in-template-casing': [2, 'kebab-case'],
'vue/require-default-prop': 0,
'vue/multi-word-component-names': 0,
'vue/no-reserved-props': 0,
'vue/no-v-html': 0,
'vue-scoped-css/enforce-style-type': [
'error',
{
allows: ['scoped']
}
],
'@typescript-eslint/explicit-function-return-type': 'off',
// 需要行尾分号
'prettier/prettier': ['error', { endOfLine: 'auto' }]
}
},
{
files: ['*.ts', '*.tsx'],
rules: {
'max-classes-per-file': 'off',
'no-await-in-loop': 'off',
'dot-notation': 'off',
'constructor-super': 'off',
'getter-return': 'off',
'no-const-assign': 'off',
'no-dupe-args': 'off',
'no-dupe-class-members': 'off',
'no-dupe-keys': 'off',
'no-func-assign': 'off',
'no-import-assign': 'off',
'no-new-symbol': 'off',
'no-obj-calls': 'off',
'no-redeclare': 'off',
'no-setter-return': 'off',
'no-this-before-super': 'off',
'no-undef': 'off',
'no-unreachable': 'off',
'no-unsafe-negation': 'off',
'no-var': 'error',
'prefer-const': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
'valid-typeof': 'off',
'consistent-return': 'off',
'no-promise-executor-return': 'off',
'prefer-promise-reject-errors': 'off',
'@typescript-eslint/explicit-function-return-type': 'off'
}
}
]
};

View File

@@ -1,6 +1,6 @@
name: 反馈 Bug
description: 通过 github 模板进行 Bug 反馈。
title: "描述问题的标题"
title: '描述问题的标题'
body:
- type: markdown
attributes:

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: true
contact_links:
- name:
url:
about:
- name:
url:
about:

View File

@@ -1,6 +1,6 @@
name: 反馈新功能
description: 通过 github 模板进行新功能反馈。
title: "描述问题的标题"
title: '描述问题的标题'
body:
- type: markdown
attributes:

View File

@@ -1,4 +1,5 @@
## IssueShoot
- 预估时长: {{ .duration }}
- 期望完成时间: {{ .deadline }}
- 开发难度: {{ .level }}

View File

@@ -8,11 +8,11 @@ on:
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v4
@@ -77,6 +77,7 @@ jobs:
dist/*.dmg
dist/*.exe
dist/*.deb
dist/*.rpm
dist/*.AppImage
dist/latest*.yml
dist/*.blockmap
@@ -84,4 +85,4 @@ jobs:
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: '18'
- name: 创建环境变量文件
run: |
echo "VITE_API=${{ secrets.VITE_API }}" > .env.production.local
@@ -36,7 +36,7 @@ jobs:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
source: "out/renderer/*"
source: 'out/renderer/*'
target: ${{ secrets.DEPLOY_PATH }}
strip_components: 2
@@ -48,4 +48,4 @@ jobs:
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd ${{ secrets.DEPLOY_PATH }}
echo "部署完成于 $(date)"
echo "部署完成于 $(date)"

11
.gitignore vendored
View File

@@ -26,4 +26,13 @@ out
.github/deploy_keys
resources/android/**/*
resources/android/**/*
android/app/release
.cursor
.auto-imports.d.ts
.components.d.ts
src/renderer/auto-imports.d.ts
src/renderer/components.d.ts

2
.husky/pre-commit Normal file
View File

@@ -0,0 +1,2 @@
echo "运行类型检查..."
npm run typecheck

2
.husky/pre-push Executable file
View File

@@ -0,0 +1,2 @@
echo "运行类型检查..."
npm run typecheck

View File

@@ -1,5 +0,0 @@
singleQuote: true
semi: true
printWidth: 100
trailingComma: none
endOfLine: auto

View File

@@ -1,27 +1,54 @@
# 更新日志
## v4.6.0
> 如果更新遇到问题,请前往 <a href="http://donate.alger.fun/download" target="_blank">下载 AlgerMusicPlayer</a>
> 请我喝咖啡(支持作者) ☕️ <a href="http://donate.alger.fun/donate" target="_blank" style="color: red; font-weight: bold;">赏你</a>
> 帮我点个 star <a href="https://github.com/algerkong/AlgerMusicPlayer" target="_blank">github star</a>
> QQ群 976962720
## v4.9.0
### ✨ 新功能
- 增加音源重新解析功能 ([82a69d0](https://github.com/algerkong/AlgerMusicPlayer/commit/82a69d0))
- 搜索列表添加下一首播放功能,修改播放逻辑搜索的歌曲点击播放不重新覆盖播放列表,添加全部播放功能 ([31640bb](https://github.com/algerkong/AlgerMusicPlayer/commit/31640bb)) (#216)
- 增加windows和linux对arm64架构的支持([9f125f8](https://github.com/algerkong/AlgerMusicPlayer/commit/9f125f8))
- 添加"收藏"功能至托盘菜单 ([3c1a144](https://github.com/algerkong/AlgerMusicPlayer/commit/3c1a144))
- 修改将歌单列表改为页面 ([e2527c3](https://github.com/algerkong/AlgerMusicPlayer/commit/e2527c3))
- 重新设计pc端歌词页面Mini播放栏
- 添加清除歌曲自定义解析功能
- 添加Cookie登录功能及自动获取等相关管理设置 ([16aeaf2](https://github.com/algerkong/AlgerMusicPlayer/commit/16aeaf2)) - 支持通过Cookie方式登录提供更便捷的登录体验
- 添加UID登录功能优化登录流程 ([daa8e75](https://github.com/algerkong/AlgerMusicPlayer/commit/daa8e75)) - 新增用户ID直接登录方式
- 添加主题根据系统切换功能 ([d5ba218](https://github.com/algerkong/AlgerMusicPlayer/commit/d5ba218)) - 支持跟随系统主题自动切换明暗模式
- 桌面歌词添加主题颜色面板组件 ([d1f5c8a](https://github.com/algerkong/AlgerMusicPlayer/commit/d1f5c8a)) - 为桌面歌词提供丰富的主题颜色自定义选项
- 增强播放速度控制,添加滑块控制并改善播放安全性 ([8fb382e](https://github.com/algerkong/AlgerMusicPlayer/commit/8fb382e)) 感谢[Qumo](https://github.com/Hellodwadawd12312312)的pr
- 添加日语和韩语国际化支持,并优化语言相关代码 ([3062156](https://github.com/algerkong/AlgerMusicPlayer/commit/3062156))
- 添加繁体中文本地化支持 ([2cc03cb](https://github.com/algerkong/AlgerMusicPlayer/commit/2cc03cb)) 感谢[dongguacute](https://github.com/dongguacute)的pr
- 播放速度设置弹窗标题添加速度显示 ([aeb7f03](https://github.com/algerkong/AlgerMusicPlayer/commit/aeb7f03))
### 🐛 Bug 修复
- 修复歌曲加入歌单失败问题 ([8045034](https://github.com/algerkong/AlgerMusicPlayer/commit/8045034))
- 修复mac快捷键关闭窗口报错的问题 ([67ef4d7](https://github.com/algerkong/AlgerMusicPlayer/commit/67ef4d7))
- 修复mini窗口恢复时导致的应用窗口变小问题 ([9b3019d](https://github.com/algerkong/AlgerMusicPlayer/commit/9b3019d))
- 修复歌单列表页面翻页类型问题 ([e489ab4](https://github.com/algerkong/AlgerMusicPlayer/commit/e489ab4))
- 修复歌曲初始化问题 ([b7a58a0](https://github.com/algerkong/AlgerMusicPlayer/commit/b7a58a0))
- 修复音量调整不同步的问题 ([679089e](https://github.com/algerkong/AlgerMusicPlayer/commit/679089e))
- 修复菜单显示不全的问题,添加滚动条 ([09ccd9f](https://github.com/algerkong/AlgerMusicPlayer/commit/09ccd9f))
### 🎨 优化
- 优化播放器逻辑,改进播放失败处理,支持保持当前索引并增加重试机制,优化操作锁逻辑,添加超时检查机制,确保操作锁在超时后自动释放,增加超时处理([8ed13d4](https://github.com/algerkong/AlgerMusicPlayer/commit/8ed13d4))、([cb58abb](https://github.com/algerkong/AlgerMusicPlayer/commit/cb58abb)) ([9cc064c](https://github.com/algerkong/AlgerMusicPlayer/commit/9cc064c))
- 更新 Electron 版本至 36.2.0,优化歌词视图的悬停效果 ([44f9709](https://github.com/algerkong/AlgerMusicPlayer/commit/44f9709))
- 更新音乐源设置([618c345](https://github.com/algerkong/AlgerMusicPlayer/commit/618c345))
- 修改 MiniPlayBar 组件,调整音量滑块的样式和交互方式,优化悬停效果 ([31ea3b7](https://github.com/algerkong/AlgerMusicPlayer/commit/31ea3b7))
- 优化 MvPlayer 组件的关闭逻辑,简化音频暂停处理 ([b3de2ae](https://github.com/algerkong/AlgerMusicPlayer/commit/b3de2ae))、([15f4ea4](https://github.com/algerkong/AlgerMusicPlayer/commit/15f4ea4))
- 更新 eslint 和 prettier 配置,格式化代码 ([c08c2cb](https://github.com/algerkong/AlgerMusicPlayer/commit/c08c2cb))
- 优化类型处理和登录功能 ([3ba85f3](https://github.com/algerkong/AlgerMusicPlayer/commit/3ba85f3))
- 优化Cookie相关文字描述 ([1597fbf](https://github.com/algerkong/AlgerMusicPlayer/commit/1597fbf))
## 赞赏支持☕️
[赞赏列表](http://donate.alger.fun/)
<table>
<tr>
<th style="text-align:center">微信赞赏</th>
<th style="width:100px"></th>
<th style="text-align:center">支付宝赞赏</th>
</tr>
<tr>
<td align="center">
<img src="https://github.com/algerkong/algerkong/blob/main/wechat.jpg?raw=true" alt="WeChat QRcode" width="200"><br>
<h6>☕️喝点咖啡继续干</h6>
</td>
<td></td>
<td align="center">
<img src="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" alt="Alipay QRcode" width="200"><br>
<h6>🍔来个汉堡</h6>
</td>
</tr>
</table>

198
DEV.md Normal file
View File

@@ -0,0 +1,198 @@
# Alger Music Player 开发文档
## 项目结构
### 技术栈
- **前端框架**Vue 3 + TypeScript
- **UI 组件库**naive-ui
- **样式框架**Tailwind CSS
- **图标库**remixicon
- **状态管理**Pinia
- **工具库**VueUse
- **构建工具**Vite, electron-vite
- **打包工具**electron-builder
- **国际化**vue-i18n
- **HTTP 客户端**axios
- **本地存储**electron-store localstorage
- **网易云音乐 API**netease-cloud-music-api
- **音乐解锁**@unblockneteasemusic/server
### 项目结构
```
AlgerMusicPlayer/
├── build/                  # 构建相关文件
├── docs/                   # 项目文档
├── node_modules/           # 依赖包
├── out/                    # 构建输出目录
├── resources/              # 资源文件
├── src/                    # 源代码
  ├── i18n/               # 国际化配置
   ├── lang/           # 语言包
   ├── main.ts         # 主进程国际化入口
   └── renderer.ts     # 渲染进程国际化入口
  ├── main/               # Electron 主进程
   ├── modules/        # 主进程模块
   ├── index.ts        # 主进程入口
   ├── lyric.ts        # 歌词处理
   ├── server.ts       # 服务器
   ├── set.json        # 设置
   └── unblockMusic.ts # 音乐解锁
  ├── preload/            # 预加载脚本
   ├── index.ts        # 预加载脚本入口
   └── index.d.ts      # 预加载脚本类型声明
  └── renderer/           # Vue 渲染进程
      ├── api/            # API 请求
      ├── assets/         # 静态资源
      ├── components/     # 组件
       ├── common/     # 通用组件
       ├── home/       # 首页组件
       ├── lyric/      # 歌词组件
       ├── settings/   # 设置组件
       └── ...         # 其他组件
      ├── const/          # 常量定义
      ├── directive/      # 自定义指令
      ├── hooks/          # 自定义 Hooks
      ├── layout/         # 布局组件
      ├── router/         # 路由配置
      ├── services/       # 服务
      ├── store/          # Pinia 状态管理
       ├── modules/    # Pinia 模块
       └── index.ts    # Pinia 入口
      ├── type/           # 类型定义
      ├── types/          # 更多类型定义
      ├── utils/          # 工具函数
      ├── views/          # 页面视图
      ├── App.vue         # 根组件
      ├── index.css       # 全局样式
      ├── index.html      # HTML 模板
      ├── main.ts         # 渲染进程入口
      └── ...             # 其他文件
├── .env.development        # 开发环境变量
├── .env.development.local  # 本地开发环境变量
├── .env.production.local   # 本地生产环境变量
├── .eslintrc.cjs           # ESLint 配置
├── .gitignore              # Git 忽略文件
├── .prettierrc.yaml        # Prettier 配置
├── electron-builder.yml    # electron-builder 配置
├── electron.vite.config.ts # electron-vite 配置
├── package.json            # 项目配置
├── postcss.config.js       # PostCSS 配置
├── tailwind.config.js      # Tailwind 配置
├── tsconfig.json           # TypeScript 配置
├── tsconfig.node.json      # 节点 TypeScript 配置
└── tsconfig.web.json       # Web TypeScript 配置
```
### 主要组件说明
#### 主进程 (src/main)
主进程负责创建窗口、处理系统层面的交互以及与渲染进程的通信。
- **index.ts**: 应用主入口,负责创建窗口和应用生命周期管理
- **lyric.ts**: 歌词解析和处理
- **unblockMusic.ts**: 网易云音乐解锁功能
- **server.ts**: 本地服务器
#### 预加载脚本 (src/preload)
预加载脚本在渲染进程加载前执行,提供了渲染进程和主进程之间的桥接功能。
#### 渲染进程 (src/renderer)
渲染进程是基于 Vue 3 的前端应用,负责 UI 渲染和用户交互。
- **components/**: 包含各种 UI 组件
  - **common/**: 通用组件
  - **home/**: 首页相关组件
  - **lyric/**: 歌词显示组件
  - **settings/**: 设置界面组件
  - **MusicList.vue**: 音乐列表组件
  - **MvPlayer.vue**: MV 播放器
  - **EQControl.vue**: 均衡器控制
  - **...**: 其他组件
- **store/**: Pinia 状态管理
  - **modules/**: 各功能模块的状态管理
  - **index.ts**: 状态管理入口
- **views/**: 页面视图组件
- **router/**: 路由配置
- **api/**: API 请求封装
- **utils/**: 工具函数
### 开发指南
#### 命名约定
- 目录使用 kebab-case (如: components/auth-wizard)
- 组件文件名使用 PascalCase (如: AuthWizard.vue)
- 可组合式函数使用 camelCase (如: useAuthState.ts)
#### 代码风格
- 使用 Composition API 和 `<script setup>` 语法
- 使用 TypeScript 类型系统
- 优先使用类型而非接口
- 避免使用枚举,使用 const 对象代替
- 使用 tailwind 实现响应式设计
### 如何启动?
安装依赖最好使用node18+
```
npm install
```
#### 桌面端开发
启动桌面端开发:
```
npm run dev
```
#### 网页端开发
如果只启动网页端开发,需要自己部署服务 netease-cloud-music-api
需要复制一份 `.env.development.local``src/renderer`
```
# .env.development.local
# 你的接口地址 (必填)
VITE_API = ***
# 音乐破解接口地址
VITE_API_MUSIC = ***
```
启动web端开发
```
npm run dev:web
```
### 打包
打包桌面端:
```
npm run build:win
```
打包后的文件在 /dist 下
打包网页端:
```
npm run build
```
打包后的文件在 /out/renderer 下

View File

@@ -1,4 +1,3 @@
<h2 align="center">🎵 Alger Music Player</h2>
<div align="center">
<div align="center">
@@ -9,79 +8,94 @@
<img src="https://img.shields.io/github/v/release/algerkong/AlgerMusicPlayer?style=for-the-badge&logo=github&label=Release&logoColor=white&color=1a67af" alt="GitHub release">
</a>
<a href="https://pd.qq.com/s/cs056n33q?b=5">
<img src="https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-algermusic-blue?style=for-the-badge" alt="QQ频道">
<img src="https://img.shields.io/badge/QQ频道-algermusic-blue?style=for-the-badge&color=yellow" alt="加入频道">
</a>
<a href="https://t.me/+9efsKRuvKBk2NWVl">
<img src="https://img.shields.io/badge/AlgerMusic-blue?style=for-the-badge&logo=telegram&logoColor=white&label=Telegram" alt="Telegram">
</a>
<a href="https://donate.alger.fun/">
<img src="https://img.shields.io/badge/%E9%A1%B9%E7%9B%AE%E6%8D%90%E8%B5%A0-blue?style=for-the-badge&logo=telegram&logoColor=pink&color=pink&label=%E8%B5%9E%E5%8A%A9" alt="赞助">
</a>
</div>
</div>
<div align="center">
<a href="https://hellogithub.com/repository/607b849c598d48e08fe38789d156ebdc" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=607b849c598d48e08fe38789d156ebdc&claim_uid=ObuMXUfeHBmk9TI&theme=neutral" alt="FeaturedHelloGitHub" width="160" height="32" /></a>
</div>
[项目下安装以及常用问题文档](https://www.yuque.com/alger-pfg5q/ip4f1a/bmgmfmghnhgwghkm?singleDoc#)
主要功能如下
- 🎵 音乐推荐
- 🔐 网易云账号登录与同步
- 📝 功能
- 播放历史记录
- 歌曲收藏管理
- 歌单 MV 排行榜 每日推荐
- 自定义快捷键配置(全局或应用内)
- 🎨 界面与交互
- 沉浸式歌词显示(点击左下角封面进入)
- 独立桌面歌词窗口
- 明暗主题切换
- 可远程控制播放
- 迷你模式
- 状态栏控制
- 多语言支持
- 🎼 音乐功能
- 支持歌单、MV、专辑等完整音乐服务
- 灰色音乐资源解析(基于 @unblockneteasemusic/server
- 音乐单独解析
- 音乐资源解析(基于 @unblockneteasemusic/server
- EQ均衡器
- 定时播放
- 高品质音乐试听需网易云VIP
- 音乐文件下载(支持右键下载和批量下载, 附带歌词封面等信息)
- 定时播放 远程控制播放 倍速播放
- 高品质音乐
- 音乐文件下载
- 搜索 MV 音乐 专辑 歌单 bilibili
- 音乐单独选择音源解析
- 🚀 技术特性
- 本地化服务无需依赖在线API (基于 netease-cloud-music-api)
- 自动更新检测
- 全平台适配Desktop & Web & Mobile Web & Android<测试> & ios<后续>
## 项目简介
一个第三方音乐播放器、本地服务、桌面歌词、音乐下载、最高音质
一个第三方音乐播放器、本地服务、桌面歌词、音乐下载、最高音质
## 预览地址
[http://music.alger.fun/](http://music.alger.fun/)
## 软件截图
![首页白](./docs/image.png)
![首页黑](./docs/image3.png)
![歌词](./docs/image1.png)
![歌词](./docs/image6.png)
![桌面歌词](./docs/image2.png)
![设置页面](./docs/image4.png)
![音乐远程控制](./docs/image5.png)
## 技术栈
## 项目启动
### 主要框架
- Vue 3 - 渐进式 JavaScript 框架
- TypeScript - JavaScript 的超集,添加了类型系统
- Electron - 跨平台桌面应用开发框架
- Vite - 下一代前端构建工具
- Naive UI - 基于 Vue 3 的组件库
```bash
npm install
npm run dev
```
## 开发文档
点击这里[开发文档](./DEV.md)
## 赞赏☕️
[赞赏列表](http://donate.alger.fun/)
| 微信赞赏 | 支付宝赞赏 |
| 微信赞赏 | 支付宝赞赏 |
| :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: |
| <img src="https://github.com/algerkong/algerkong/blob/main/wechat.jpg?raw=true" alt="WeChat QRcode" width=200> <br><small>喝点咖啡继续干</small> | <img src="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" alt="Wechat QRcode" width=200> <br><small>来包辣条吧~</small> |
## 项目统计
[![Stargazers over time](https://starchart.cc/algerkong/AlgerMusicPlayer.svg?variant=adaptive)](https://starchart.cc/algerkong/AlgerMusicPlayer)
![Alt](https://repobeats.axiom.co/api/embed/c4d01b3632e241c90cdec9508dfde86a7f54c9f5.svg "Repobeats analytics image")
![Alt](https://repobeats.axiom.co/api/embed/c4d01b3632e241c90cdec9508dfde86a7f54c9f5.svg 'Repobeats analytics image')
## 欢迎提Issues
## 免责声明
## 声明
本软件仅用于学习交流,禁止用于商业用途,否则后果自负。
希望大家还是要多多支持官方正版,此软件仅用作开发教学。

90
auto-imports.d.ts vendored
View File

@@ -1,90 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: (typeof import('vue'))['EffectScope'];
const computed: (typeof import('vue'))['computed'];
const createApp: (typeof import('vue'))['createApp'];
const customRef: (typeof import('vue'))['customRef'];
const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'];
const defineComponent: (typeof import('vue'))['defineComponent'];
const effectScope: (typeof import('vue'))['effectScope'];
const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'];
const getCurrentScope: (typeof import('vue'))['getCurrentScope'];
const h: (typeof import('vue'))['h'];
const inject: (typeof import('vue'))['inject'];
const isProxy: (typeof import('vue'))['isProxy'];
const isReactive: (typeof import('vue'))['isReactive'];
const isReadonly: (typeof import('vue'))['isReadonly'];
const isRef: (typeof import('vue'))['isRef'];
const markRaw: (typeof import('vue'))['markRaw'];
const nextTick: (typeof import('vue'))['nextTick'];
const onActivated: (typeof import('vue'))['onActivated'];
const onBeforeMount: (typeof import('vue'))['onBeforeMount'];
const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount'];
const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate'];
const onDeactivated: (typeof import('vue'))['onDeactivated'];
const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'];
const onMounted: (typeof import('vue'))['onMounted'];
const onRenderTracked: (typeof import('vue'))['onRenderTracked'];
const onRenderTriggered: (typeof import('vue'))['onRenderTriggered'];
const onScopeDispose: (typeof import('vue'))['onScopeDispose'];
const onServerPrefetch: (typeof import('vue'))['onServerPrefetch'];
const onUnmounted: (typeof import('vue'))['onUnmounted'];
const onUpdated: (typeof import('vue'))['onUpdated'];
const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'];
const provide: (typeof import('vue'))['provide'];
const reactive: (typeof import('vue'))['reactive'];
const readonly: (typeof import('vue'))['readonly'];
const ref: (typeof import('vue'))['ref'];
const resolveComponent: (typeof import('vue'))['resolveComponent'];
const shallowReactive: (typeof import('vue'))['shallowReactive'];
const shallowReadonly: (typeof import('vue'))['shallowReadonly'];
const shallowRef: (typeof import('vue'))['shallowRef'];
const toRaw: (typeof import('vue'))['toRaw'];
const toRef: (typeof import('vue'))['toRef'];
const toRefs: (typeof import('vue'))['toRefs'];
const toValue: (typeof import('vue'))['toValue'];
const triggerRef: (typeof import('vue'))['triggerRef'];
const unref: (typeof import('vue'))['unref'];
const useAttrs: (typeof import('vue'))['useAttrs'];
const useCssModule: (typeof import('vue'))['useCssModule'];
const useCssVars: (typeof import('vue'))['useCssVars'];
const useDialog: (typeof import('naive-ui'))['useDialog'];
const useId: (typeof import('vue'))['useId'];
const useLoadingBar: (typeof import('naive-ui'))['useLoadingBar'];
const useMessage: (typeof import('naive-ui'))['useMessage'];
const useModel: (typeof import('vue'))['useModel'];
const useNotification: (typeof import('naive-ui'))['useNotification'];
const useSlots: (typeof import('vue'))['useSlots'];
const useTemplateRef: (typeof import('vue'))['useTemplateRef'];
const watch: (typeof import('vue'))['watch'];
const watchEffect: (typeof import('vue'))['watchEffect'];
const watchPostEffect: (typeof import('vue'))['watchPostEffect'];
const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'];
}
// for type re-export
declare global {
// @ts-ignore
export type {
Component,
ComponentPublicInstance,
ComputedRef,
DirectiveBinding,
ExtractDefaultPropTypes,
ExtractPropTypes,
ExtractPublicPropTypes,
InjectionKey,
PropType,
Ref,
MaybeRef,
MaybeRefOrGetter,
VNode,
WritableComputedRef
} from 'vue';
import('vue');
}

33
components.d.ts vendored
View File

@@ -1,33 +0,0 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {};
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
NAvatar: typeof import('naive-ui')['NAvatar']
NButton: typeof import('naive-ui')['NButton']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDrawer: typeof import('naive-ui')['NDrawer']
NDropdown: typeof import('naive-ui')['NDropdown']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NImage: typeof import('naive-ui')['NImage']
NInput: typeof import('naive-ui')['NInput']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLayout: typeof import('naive-ui')['NLayout']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NPopover: typeof import('naive-ui')['NPopover']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 897 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 900 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 406 KiB

BIN
docs/image5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
docs/image6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@@ -18,7 +18,8 @@ export default defineConfig({
resolve: {
alias: {
'@': resolve('src/renderer'),
'@renderer': resolve('src/renderer')
'@renderer': resolve('src/renderer'),
'@i18n': resolve('src/i18n')
}
},
plugins: [
@@ -37,25 +38,9 @@ export default defineConfig({
resolvers: [NaiveUiResolver()]
})
],
publicDir: resolve('resources'),
server: {
proxy: {
// with options
[process.env.VITE_API_LOCAL as string]: {
target: process.env.VITE_API,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${process.env.VITE_API_LOCAL}`), '')
},
[process.env.VITE_API_MUSIC_PROXY as string]: {
target: process.env.VITE_API_MUSIC,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${process.env.VITE_API_MUSIC_PROXY}`), '')
},
[process.env.VITE_API_PROXY_MUSIC as string]: {
target: process.env.VITE_API_PROXY,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(`^${process.env.VITE_API_PROXY_MUSIC}`), '')
}
}
host: '0.0.0.0'
}
}
});

237
eslint.config.mjs Normal file
View File

@@ -0,0 +1,237 @@
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import vue from 'eslint-plugin-vue';
import vueParser from 'vue-eslint-parser';
import prettier from 'eslint-plugin-prettier';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import vueScopedCss from 'eslint-plugin-vue-scoped-css';
import globals from 'globals';
export default [
// 忽略文件配置
{
ignores: ['node_modules/**', 'dist/**', 'out/**', '.gitignore']
},
// 基础 JavaScript 配置
js.configs.recommended,
// JavaScript 文件配置
{
files: ['**/*.js', '**/*.cjs', '**/*.mjs'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.node,
...globals.browser
}
},
rules: {
'no-console': 'off',
'no-undef': 'error'
}
},
// TypeScript 文件配置
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
allowImportExportEverywhere: true,
ecmaFeatures: {
jsx: true
}
},
globals: {
...globals.node,
...globals.browser,
// Vue 3 特定全局变量
defineProps: 'readonly',
defineEmits: 'readonly',
// TypeScript 全局类型
NodeJS: 'readonly',
ScrollBehavior: 'readonly'
}
},
plugins: {
'@typescript-eslint': typescript,
'simple-import-sort': simpleImportSort
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' }
],
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'no-console': 'off',
'no-unused-vars': [
'error',
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' }
],
'no-use-before-define': 'off',
'max-classes-per-file': 'off',
'no-await-in-loop': 'off',
'dot-notation': 'off',
'constructor-super': 'off',
'getter-return': 'off',
'no-const-assign': 'off',
'no-dupe-args': 'off',
'no-dupe-class-members': 'off',
'no-dupe-keys': 'off',
'no-func-assign': 'off',
'no-import-assign': 'off',
'no-new-symbol': 'off',
'no-obj-calls': 'off',
'no-redeclare': 'off',
'no-setter-return': 'off',
'no-this-before-super': 'off',
'no-undef': 'off',
'no-unreachable': 'off',
'no-unsafe-negation': 'off',
'no-var': 'error',
'prefer-const': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
'valid-typeof': 'off',
'consistent-return': 'off',
'no-promise-executor-return': 'off',
'prefer-promise-reject-errors': 'off'
}
},
// Vue 文件配置
{
files: ['**/*.vue'],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: typescriptParser,
ecmaVersion: 'latest',
sourceType: 'module',
allowImportExportEverywhere: true
},
globals: {
...globals.browser,
// Vue 3 特定全局变量
defineProps: 'readonly',
defineEmits: 'readonly',
// Vue 3 Composition API (如果使用了 unplugin-auto-import)
ref: 'readonly',
reactive: 'readonly',
computed: 'readonly',
watch: 'readonly',
watchEffect: 'readonly',
onMounted: 'readonly',
onUnmounted: 'readonly',
onBeforeUnmount: 'readonly',
nextTick: 'readonly',
inject: 'readonly',
provide: 'readonly',
// Naive UI (如果使用了 unplugin-auto-import)
useDialog: 'readonly',
useMessage: 'readonly',
// TypeScript 全局类型
NodeJS: 'readonly',
ScrollBehavior: 'readonly'
}
},
plugins: {
vue,
'@typescript-eslint': typescript,
prettier,
'simple-import-sort': simpleImportSort,
'vue-scoped-css': vueScopedCss
},
rules: {
// Vue 3 推荐规则
'vue/no-unused-vars': 'error',
'vue/no-unused-components': 'error',
'vue/no-multiple-template-root': 'off',
'vue/no-v-model-argument': 'off',
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off',
'vue/component-name-in-template-casing': ['error', 'kebab-case'],
'vue/no-reserved-props': 'off',
'vue/no-v-html': 'off',
'vue/first-attribute-linebreak': 'off',
'vue-scoped-css/enforce-style-type': [
'error',
{
allows: ['scoped']
}
],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'prettier/prettier': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error'
}
},
// TypeScript 类型定义文件配置
{
files: ['**/*.d.ts'],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-interface': 'off'
}
},
// JavaScript 第三方库文件配置
{
files: ['**/assets/**/*.js', '**/vendor/**/*.js', '**/lib/**/*.js'],
rules: {
'no-unused-vars': 'off',
'no-redeclare': 'off',
'no-self-assign': 'off',
'no-undef': 'off'
}
},
// 通用规则
{
files: ['**/*.js', '**/*.ts', '**/*.vue'],
rules: {
'no-console': 'off',
'no-underscore-dangle': 'off',
'no-nested-ternary': 'off',
'no-await-in-loop': 'off',
'no-continue': 'off',
'no-restricted-syntax': 'off',
'no-return-assign': 'off',
'no-unused-expressions': 'off',
'no-return-await': 'off',
'no-plusplus': 'off',
'no-param-reassign': 'off',
'no-shadow': 'off',
'guard-for-in': 'off',
'class-methods-use-this': 'off',
'no-case-declarations': 'off',
'no-unused-vars': [
'error',
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' }
]
}
}
];

View File

@@ -1,19 +1,21 @@
{
"name": "AlgerMusicPlayer",
"version": "4.6.0",
"version": "4.9.0",
"description": "Alger Music Player",
"author": "Alger <algerkc@qq.com>",
"main": "./out/main/index.js",
"homepage": "https://github.com/algerkong/AlgerMusicPlayer",
"scripts": {
"prepare": "husky",
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
"lint": "eslint . --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "npm run typecheck && electron-vite build",
"dev:web": "vite dev",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
@@ -26,9 +28,13 @@
"@unblockneteasemusic/server": "^0.27.8-patch.1",
"cors": "^2.8.5",
"electron-store": "^8.1.0",
"express": "^4.18.2",
"electron-updater": "^6.6.2",
"electron-window-state": "^5.0.3",
"express": "^4.18.2",
"file-type": "^21.0.0",
"font-list": "^1.5.1",
"husky": "^9.1.7",
"music-metadata": "^11.2.3",
"netease-cloud-music-api-alger": "^4.26.1",
"node-id3": "^0.2.9",
"node-machine-id": "^1.1.12",
@@ -38,6 +44,7 @@
"@electron-toolkit/eslint-config": "^2.1.0",
"@electron-toolkit/eslint-config-ts": "^3.1.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@eslint/js": "^9.31.0",
"@rushstack/eslint-patch": "^1.10.3",
"@tailwindcss/postcss7-compat": "^2.2.4",
"@types/howler": "^2.2.12",
@@ -56,16 +63,17 @@
"autoprefixer": "^10.4.20",
"axios": "^1.7.7",
"cross-env": "^7.0.3",
"electron": "^36.2.0",
"electron": "^35.2.0",
"electron-builder": "^25.1.8",
"electron-vite": "^3.1.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-vue": "^10.0.0",
"eslint-plugin-vue-scoped-css": "^2.9.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-vue": "^10.3.0",
"eslint-plugin-vue-scoped-css": "^2.11.0",
"globals": "^16.3.0",
"howler": "^2.2.4",
"lodash": "^4.17.21",
"marked": "^15.0.4",
@@ -73,7 +81,7 @@
"pinia": "^3.0.1",
"pinyin-match": "^1.2.6",
"postcss": "^8.5.3",
"prettier": "^3.3.2",
"prettier": "^3.6.2",
"remixicon": "^4.6.0",
"sass": "^1.86.0",
"tailwindcss": "^3.4.17",
@@ -103,7 +111,9 @@
{
"from": "resources/html",
"to": "html",
"filter": ["**/*"]
"filter": [
"**/*"
]
}
],
"mac": {
@@ -160,6 +170,13 @@
"x64",
"arm64"
]
},
{
"target": "rpm",
"arch": [
"x64",
"arm64"
]
}
],
"artifactName": "${productName}-${version}-linux-${arch}.${ext}",

10
prettier.config.js Normal file
View File

@@ -0,0 +1,10 @@
/**
* @type {import('prettier').Config}
*/
module.exports = {
singleQuote: true,
semi: true,
printWidth: 100,
trailingComma: 'none',
endOfLine: 'auto'
};

View File

@@ -1,486 +1,505 @@
<!DOCTYPE html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>AlgerMusicPlayer 远程控制</title>
<style>
:root {
--primary-color: #007AFF;
--secondary-color: #5AC8FA;
--success-color: #4CD964;
--danger-color: #FF3B30;
--warning-color: #FF9500;
--light-gray: #F2F2F7;
--medium-gray: #E5E5EA;
--dark-gray: #8E8E93;
--text-color: #000000;
--text-secondary: #6C6C6C;
--background-color: #FFFFFF;
}
@media (prefers-color-scheme: dark) {
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>AlgerMusicPlayer 远程控制</title>
<style>
:root {
--primary-color: #0A84FF;
--secondary-color: #64D2FF;
--success-color: #30D158;
--danger-color: #FF453A;
--warning-color: #FF9F0A;
--light-gray: #1C1C1E;
--medium-gray: #2C2C2E;
--dark-gray: #8E8E93;
--text-color: #FFFFFF;
--text-secondary: #AEAEB2;
--background-color: #000000;
--primary-color: #007aff;
--secondary-color: #5ac8fa;
--success-color: #4cd964;
--danger-color: #ff3b30;
--warning-color: #ff9500;
--light-gray: #f2f2f7;
--medium-gray: #e5e5ea;
--dark-gray: #8e8e93;
--text-color: #000000;
--text-secondary: #6c6c6c;
--background-color: #ffffff;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--light-gray);
display: flex;
flex-direction: column;
min-height: 100vh;
padding: 0;
margin: 0;
}
.header {
position: sticky;
top: 0;
z-index: 100;
background-color: var(--background-color);
padding: 12px 16px;
text-align: center;
border-bottom: 1px solid var(--medium-gray);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.header h1 {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.container {
max-width: 540px;
margin: 0 auto;
padding: 16px;
width: 100%;
flex: 1;
}
.card {
background-color: var(--background-color);
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.song-info {
display: flex;
align-items: center;
padding-bottom: 16px;
}
.song-cover {
width: 72px;
height: 72px;
border-radius: 8px;
object-fit: cover;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
background-color: var(--medium-gray);
}
.song-details {
margin-left: 16px;
flex: 1;
}
.song-details h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--text-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.song-details p {
margin: 4px 0 0;
font-size: 15px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.play-state {
font-size: 14px;
color: var(--primary-color);
margin-top: 4px;
display: flex;
align-items: center;
}
.play-state::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
}
.playing .play-state::before {
background-color: var(--success-color);
}
.paused .play-state::before {
background-color: var(--warning-color);
}
.controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.extra-controls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--background-color);
color: var(--primary-color);
border: 1px solid var(--medium-gray);
padding: 16px 0;
border-radius: 12px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
user-select: none;
position: relative;
overflow: hidden;
}
.btn:active {
transform: scale(0.97);
opacity: 0.7;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--primary-color);
opacity: 0;
transition: opacity 0.2s ease;
z-index: -1;
}
.btn:active::before {
opacity: 0.1;
}
.btn svg {
margin-bottom: 8px;
width: 24px;
height: 24px;
fill: var(--primary-color);
}
.btn-play svg {
width: 28px;
height: 28px;
margin-bottom: 6px;
}
.status-bar {
text-align: center;
padding: 8px 16px;
font-size: 14px;
color: var(--text-secondary);
background-color: var(--background-color);
border-top: 1px solid var(--medium-gray);
position: sticky;
bottom: 0;
}
.status-message {
display: inline-block;
transition: opacity 0.3s ease;
}
.status-message.fade {
opacity: 0;
}
.refresh-button {
color: var(--primary-color);
background: none;
border: none;
font-size: 14px;
cursor: pointer;
padding: 0;
margin-left: 8px;
}
@media (max-width: 350px) {
.controls, .extra-controls {
gap: 8px;
@media (prefers-color-scheme: dark) {
:root {
--primary-color: #0a84ff;
--secondary-color: #64d2ff;
--success-color: #30d158;
--danger-color: #ff453a;
--warning-color: #ff9f0a;
--light-gray: #1c1c1e;
--medium-gray: #2c2c2e;
--dark-gray: #8e8e93;
--text-color: #ffffff;
--text-secondary: #aeaeb2;
--background-color: #000000;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family:
-apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--light-gray);
display: flex;
flex-direction: column;
min-height: 100vh;
padding: 0;
margin: 0;
}
.header {
position: sticky;
top: 0;
z-index: 100;
background-color: var(--background-color);
padding: 12px 16px;
text-align: center;
border-bottom: 1px solid var(--medium-gray);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.header h1 {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.container {
max-width: 540px;
margin: 0 auto;
padding: 16px;
width: 100%;
flex: 1;
}
.card {
background-color: var(--background-color);
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.song-info {
display: flex;
align-items: center;
padding-bottom: 16px;
}
.song-cover {
width: 72px;
height: 72px;
border-radius: 8px;
object-fit: cover;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
background-color: var(--medium-gray);
}
.song-details {
margin-left: 16px;
flex: 1;
}
.song-details h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--text-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.song-details p {
margin: 4px 0 0;
font-size: 15px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.play-state {
font-size: 14px;
color: var(--primary-color);
margin-top: 4px;
display: flex;
align-items: center;
}
.play-state::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
}
.playing .play-state::before {
background-color: var(--success-color);
}
.paused .play-state::before {
background-color: var(--warning-color);
}
.controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.extra-controls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.btn {
padding: 12px 0;
font-size: 12px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--background-color);
color: var(--primary-color);
border: 1px solid var(--medium-gray);
padding: 16px 0;
border-radius: 12px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
user-select: none;
position: relative;
overflow: hidden;
}
.btn:active {
transform: scale(0.97);
opacity: 0.7;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--primary-color);
opacity: 0;
transition: opacity 0.2s ease;
z-index: -1;
}
.btn:active::before {
opacity: 0.1;
}
.btn svg {
width: 20px;
height: 20px;
margin-bottom: 8px;
width: 24px;
height: 24px;
fill: var(--primary-color);
}
.btn-play svg {
width: 28px;
height: 28px;
margin-bottom: 6px;
}
}
</style>
</head>
<body>
<div class="header">
<h1>AlgerMusicPlayer 远程控制</h1>
</div>
<div class="container">
<div class="card" id="songInfoCard">
<div class="song-info">
<img id="songCover" class="song-cover" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E" alt="封面">
<div class="song-details">
<h2 id="songTitle">未在播放</h2>
<p id="songArtist">--</p>
<div class="play-state" id="playState">未播放</div>
.status-bar {
text-align: center;
padding: 8px 16px;
font-size: 14px;
color: var(--text-secondary);
background-color: var(--background-color);
border-top: 1px solid var(--medium-gray);
position: sticky;
bottom: 0;
}
.status-message {
display: inline-block;
transition: opacity 0.3s ease;
}
.status-message.fade {
opacity: 0;
}
.refresh-button {
color: var(--primary-color);
background: none;
border: none;
font-size: 14px;
cursor: pointer;
padding: 0;
margin-left: 8px;
}
@media (max-width: 350px) {
.controls,
.extra-controls {
gap: 8px;
}
.btn {
padding: 12px 0;
font-size: 12px;
}
.btn svg {
width: 20px;
height: 20px;
margin-bottom: 6px;
}
}
</style>
</head>
<body>
<div class="header">
<h1>AlgerMusicPlayer 远程控制</h1>
</div>
<div class="container">
<div class="card" id="songInfoCard">
<div class="song-info">
<img
id="songCover"
class="song-cover"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E"
alt="封面"
/>
<div class="song-details">
<h2 id="songTitle">未在播放</h2>
<p id="songArtist">--</p>
<div class="play-state" id="playState">未播放</div>
</div>
</div>
</div>
<div class="card">
<div class="controls">
<button id="prevBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M6 6h2v12H6zm3.5 6l8.5 6V6z" />
</svg>
上一首
</button>
<button id="playBtn" class="btn btn-play">
<svg id="playIcon" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z" />
</svg>
播放/暂停
</button>
<button id="nextBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z" />
</svg>
下一首
</button>
</div>
<div class="extra-controls">
<button id="volumeDownBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path
d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"
/>
</svg>
音量-
</button>
<button id="volumeUpBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path
d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"
/>
</svg>
音量+
</button>
</div>
</div>
<div class="card">
<div class="extra-controls">
<button id="favoriteBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
/>
</svg>
收藏
</button>
<button id="refreshBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path
d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
/>
</svg>
刷新
</button>
</div>
</div>
</div>
<div class="card">
<div class="controls">
<button id="prevBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/>
</svg>
上一首
</button>
<button id="playBtn" class="btn btn-play">
<svg id="playIcon" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z"/>
</svg>
播放/暂停
</button>
<button id="nextBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/>
</svg>
下一首
</button>
</div>
<div class="extra-controls">
<button id="volumeDownBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/>
</svg>
音量-
</button>
<button id="volumeUpBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
</svg>
音量+
</button>
</div>
<div class="status-bar">
<span id="status" class="status-message">准备就绪</span>
</div>
<div class="card">
<div class="extra-controls">
<button id="favoriteBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
收藏
</button>
<button id="refreshBtn" class="btn">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
</svg>
刷新
</button>
</div>
</div>
</div>
<script>
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', () => {
// 获取DOM元素
const songInfoCard = document.getElementById('songInfoCard');
const songTitle = document.getElementById('songTitle');
const songArtist = document.getElementById('songArtist');
const songCover = document.getElementById('songCover');
const playState = document.getElementById('playState');
const playBtn = document.getElementById('playBtn');
const playIcon = document.getElementById('playIcon');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const favoriteBtn = document.getElementById('favoriteBtn');
const volumeUpBtn = document.getElementById('volumeUpBtn');
const volumeDownBtn = document.getElementById('volumeDownBtn');
const refreshBtn = document.getElementById('refreshBtn');
const status = document.getElementById('status');
<div class="status-bar">
<span id="status" class="status-message">准备就绪</span>
</div>
let isPlaying = false;
<script>
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', () => {
// 获取DOM元素
const songInfoCard = document.getElementById('songInfoCard');
const songTitle = document.getElementById('songTitle');
const songArtist = document.getElementById('songArtist');
const songCover = document.getElementById('songCover');
const playState = document.getElementById('playState');
const playBtn = document.getElementById('playBtn');
const playIcon = document.getElementById('playIcon');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const favoriteBtn = document.getElementById('favoriteBtn');
const volumeUpBtn = document.getElementById('volumeUpBtn');
const volumeDownBtn = document.getElementById('volumeDownBtn');
const refreshBtn = document.getElementById('refreshBtn');
const status = document.getElementById('status');
let isPlaying = false;
// 显示状态消息并淡出
function showStatus(message, autoClear = true) {
status.textContent = message;
status.classList.remove('fade');
if (autoClear) {
setTimeout(() => {
status.classList.add('fade');
}, 2000);
}
}
// 更新播放/暂停图标
function updatePlayIcon() {
if (isPlaying) {
playIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>';
} else {
playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
}
}
// 更新状态的函数
async function updateStatus() {
try {
showStatus('获取播放状态...', false);
const response = await fetch('/api/status');
const data = await response.json();
// 更新播放状态
isPlaying = data.isPlaying;
updatePlayIcon();
// 更新UI
if (data.currentSong) {
songTitle.textContent = data.currentSong.name || '未知歌曲';
if (data.currentSong.ar && data.currentSong.ar.length) {
songArtist.textContent = data.currentSong.ar.map(a => a.name).join(', ');
} else if (data.currentSong.artists && data.currentSong.artists.length) {
songArtist.textContent = data.currentSong.artists.map(a => a.name).join(', ');
} else {
songArtist.textContent = '未知艺术家';
}
if (data.currentSong.picUrl) {
songCover.src = data.currentSong.picUrl;
}
} else {
songTitle.textContent = '未在播放';
songArtist.textContent = '--';
songCover.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E";
// 显示状态消息并淡出
function showStatus(message, autoClear = true) {
status.textContent = message;
status.classList.remove('fade');
if (autoClear) {
setTimeout(() => {
status.classList.add('fade');
}, 2000);
}
// 更新播放状态
playState.textContent = isPlaying ? '正在播放' : '已暂停';
songInfoCard.className = isPlaying ? 'card playing' : 'card paused';
showStatus('已更新', true);
} catch (error) {
console.error('获取状态失败:', error);
showStatus('获取状态失败');
}
}
// 发送命令的函数
async function sendCommand(endpoint) {
try {
showStatus('发送命令中...', false);
const response = await fetch('/api/' + endpoint, { method: 'POST' });
const data = await response.json();
showStatus(data.message || '命令已发送');
// 稍等后更新状态
setTimeout(updateStatus, 500);
} catch (error) {
console.error('发送命令失败:', error);
showStatus('发送命令失败');
}
}
// 绑定按钮事件
playBtn.addEventListener('click', () => sendCommand('toggle-play'));
prevBtn.addEventListener('click', () => sendCommand('prev'));
nextBtn.addEventListener('click', () => sendCommand('next'));
favoriteBtn.addEventListener('click', () => sendCommand('toggle-favorite'));
volumeUpBtn.addEventListener('click', () => sendCommand('volume-up'));
volumeDownBtn.addEventListener('click', () => sendCommand('volume-down'));
refreshBtn.addEventListener('click', updateStatus);
// 初始加载状态
updateStatus();
// 每1秒更新一次状态
setInterval(updateStatus, 1000);
// 添加触摸反馈
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => {
btn.addEventListener('touchstart', function() {
this.style.transform = 'scale(0.97)';
this.style.opacity = '0.7';
});
btn.addEventListener('touchend', function() {
this.style.transform = 'scale(1)';
this.style.opacity = '1';
});
});
// 检测深色模式变化
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
// 更新播放/暂停图标
function updatePlayIcon() {
if (isPlaying) {
playIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>';
} else {
playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
}
}
// 更新状态的函数
async function updateStatus() {
try {
showStatus('获取播放状态...', false);
const response = await fetch('/api/status');
const data = await response.json();
// 更新播放状态
isPlaying = data.isPlaying;
updatePlayIcon();
// 更新UI
if (data.currentSong) {
songTitle.textContent = data.currentSong.name || '未知歌曲';
if (data.currentSong.ar && data.currentSong.ar.length) {
songArtist.textContent = data.currentSong.ar.map((a) => a.name).join(', ');
} else if (data.currentSong.artists && data.currentSong.artists.length) {
songArtist.textContent = data.currentSong.artists.map((a) => a.name).join(', ');
} else {
songArtist.textContent = '未知艺术家';
}
if (data.currentSong.picUrl) {
songCover.src = data.currentSong.picUrl;
}
} else {
songTitle.textContent = '未在播放';
songArtist.textContent = '--';
songCover.src =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E";
}
// 更新播放状态
playState.textContent = isPlaying ? '正在播放' : '已暂停';
songInfoCard.className = isPlaying ? 'card playing' : 'card paused';
showStatus('已更新', true);
} catch (error) {
console.error('获取状态失败:', error);
showStatus('获取状态失败');
}
}
// 发送命令的函数
async function sendCommand(endpoint) {
try {
showStatus('发送命令中...', false);
const response = await fetch('/api/' + endpoint, { method: 'POST' });
const data = await response.json();
showStatus(data.message || '命令已发送');
// 稍等后更新状态
setTimeout(updateStatus, 500);
} catch (error) {
console.error('发送命令失败:', error);
showStatus('发送命令失败');
}
}
// 绑定按钮事件
playBtn.addEventListener('click', () => sendCommand('toggle-play'));
prevBtn.addEventListener('click', () => sendCommand('prev'));
nextBtn.addEventListener('click', () => sendCommand('next'));
favoriteBtn.addEventListener('click', () => sendCommand('toggle-favorite'));
volumeUpBtn.addEventListener('click', () => sendCommand('volume-up'));
volumeDownBtn.addEventListener('click', () => sendCommand('volume-down'));
refreshBtn.addEventListener('click', updateStatus);
// 初始加载状态
updateStatus();
// 每1秒更新一次状态
setInterval(updateStatus, 1000);
// 添加触摸反馈
const buttons = document.querySelectorAll('.btn');
buttons.forEach((btn) => {
btn.addEventListener('touchstart', function () {
this.style.transform = 'scale(0.97)';
this.style.opacity = '0.7';
});
btn.addEventListener('touchend', function () {
this.style.transform = 'scale(1)';
this.style.opacity = '1';
});
});
// 检测深色模式变化
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
updateStatus();
});
});
});
</script>
</body>
</html>
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -27,6 +27,7 @@ export default {
refresh: 'Refresh',
retry: 'Retry',
reset: 'Reset',
back: 'Back',
copySuccess: 'Copied to clipboard',
copyFailed: 'Copy failed',
validation: {

View File

@@ -60,7 +60,7 @@ export default {
wechatQR: 'Wechat QR code',
coffeeDesc: 'A cup of coffee, a support',
coffeeDescLinkText: 'View more',
qqGroup: 'QQ group: algermusic',
groupText: 'Wechat Public Account: AlgerMusic',
messages: {
copySuccess: 'Copied to clipboard'
},
@@ -85,12 +85,16 @@ export default {
login: 'Login',
toLogin: 'To Login',
logout: 'Logout',
set: 'Set',
set: 'Settings',
theme: 'Theme',
restart: 'Restart',
refresh: 'Refresh',
currentVersion: 'Current Version',
searchPlaceholder: 'Search for something...'
searchPlaceholder: 'Search for something...',
zoom: 'Zoom',
zoom100: 'Zoom 100%',
resetZoom: 'Reset Zoom',
zoomDefault: 'Default Zoom'
},
titleBar: {
closeTitle: 'Choose how to close',
@@ -116,5 +120,74 @@ export default {
addToPlaylistSuccess: 'Add to Playlist Success',
operationFailed: 'Operation Failed',
songsAlreadyInPlaylist: 'Songs already in playlist'
}
},
playlist: {
import: {
button: 'Import Playlist',
title: 'Import Playlist',
description: 'Import playlists via metadata, text, or links',
linkTab: 'Import by Link',
textTab: 'Import by Text',
localTab: 'Import by Metadata',
linkPlaceholder: 'Enter playlist links, one per line',
textPlaceholder: 'Enter song information in format: Song Name Artist Name',
localPlaceholder: 'Enter song metadata in JSON format',
linkTips: 'Supported link sources:',
linkTip1: 'Copy links after sharing playlists to WeChat/Weibo/QQ',
linkTip2: 'Directly copy playlist/profile links',
linkTip3: 'Directly copy article links',
textTips: 'Enter song information, one song per line',
textFormat: 'Format: Song Name Artist Name',
localTips: 'Add song metadata',
localFormat: 'Format example:',
songNamePlaceholder: 'Song Name',
artistNamePlaceholder: 'Artist Name',
albumNamePlaceholder: 'Album Name',
addSongButton: 'Add Song',
addLinkButton: 'Add Link',
importToStarPlaylist: 'Import to My Favorite Music',
playlistNamePlaceholder: 'Enter playlist name',
importButton: 'Start Import',
emptyLinkWarning: 'Please enter playlist links',
emptyTextWarning: 'Please enter song information',
emptyLocalWarning: 'Please enter song metadata',
invalidJsonFormat: 'Invalid JSON format',
importSuccess: 'Import task created successfully',
importFailed: 'Import failed',
importStatus: 'Import Status',
refresh: 'Refresh',
taskId: 'Task ID',
status: 'Status',
successCount: 'Success Count',
failReason: 'Failure Reason',
unknownError: 'Unknown error',
statusPending: 'Pending',
statusProcessing: 'Processing',
statusSuccess: 'Success',
statusFailed: 'Failed',
statusUnknown: 'Unknown',
taskList: 'Task List',
taskListTitle: 'Import Task List',
action: 'Action',
select: 'Select',
fetchTaskListFailed: 'Failed to fetch task list',
noTasks: 'No import tasks',
clearTasks: 'Clear Tasks',
clearTasksConfirmTitle: 'Confirm Clear',
clearTasksConfirmContent:
'Are you sure you want to clear all import task records? This action cannot be undone.',
confirm: 'Confirm',
cancel: 'Cancel',
clearTasksSuccess: 'Task list cleared',
clearTasksFailed: 'Failed to clear task list'
}
},
settings: 'Settings',
user: 'User',
toplist: 'Toplist',
history: 'History',
list: 'Playlist',
mv: 'MV',
home: 'Home',
search: 'Search'
};

View File

@@ -3,17 +3,20 @@ export default {
localMusic: 'Local Music',
count: '{count} songs in total',
clearAll: 'Clear All',
settings: 'Settings',
tabs: {
downloading: 'Downloading',
downloaded: 'Downloaded'
},
empty: {
noTasks: 'No download tasks',
noDownloaded: 'No downloaded songs'
noDownloaded: 'No downloaded songs',
noDownloadedHint: 'Download your favorite songs to listen offline'
},
progress: {
total: 'Total Progress: {progress}%'
},
items: 'items',
status: {
downloading: 'Downloading',
completed: 'Completed',
@@ -43,7 +46,46 @@ export default {
},
message: {
downloadComplete: '{filename} download completed',
downloadFailed: '{filename} download failed: {error}'
downloadFailed: '{filename} download failed: {error}',
alreadyDownloading: '{filename} is already downloading'
},
loading: 'Loading...'
loading: 'Loading...',
playStarted: 'Play started: {name}',
playFailed: 'Play failed: {name}',
path: {
copied: 'Path copied to clipboard',
copyFailed: 'Failed to copy path'
},
settingsPanel: {
title: 'Download Settings',
path: 'Download Location',
pathDesc: 'Set where your music files will be saved',
pathPlaceholder: 'Please select download path',
noPathSelected: 'Please select download path first',
select: 'Select Folder',
open: 'Open Folder',
fileFormat: 'Filename Format',
fileFormatDesc: 'Set how downloaded music files will be named',
customFormat: 'Custom Format',
separator: 'Separator',
separators: {
dash: 'Space-dash-space',
underscore: 'Underscore',
space: 'Space'
},
dragToArrange: 'Sort or use arrow buttons to arrange:',
formatVariables: 'Available variables',
preview: 'Preview:',
saveSuccess: 'Download settings saved',
presets: {
songArtist: 'Song - Artist',
artistSong: 'Artist - Song',
songOnly: 'Song only'
},
components: {
songName: 'Song name',
artistName: 'Artist name',
albumName: 'Album name'
}
}
};

View File

@@ -1,29 +0,0 @@
import artist from './artist';
import common from './common';
import comp from './comp';
import donation from './donation';
import download from './download';
import favorite from './favorite';
import history from './history';
import login from './login';
import player from './player';
import search from './search';
import settings from './settings';
import songItem from './songItem';
import user from './user';
export default {
common,
donation,
favorite,
history,
login,
player,
search,
settings,
songItem,
user,
download,
comp,
artist
};

View File

@@ -1,22 +1,62 @@
export default {
title: {
qr: 'QR Code Login',
phone: 'Phone Login'
phone: 'Phone Login',
cookie: 'Cookie Login',
uid: 'UID Login'
},
qrTip: 'Scan with NetEase Cloud Music APP',
phoneTip: 'Login with NetEase Cloud account',
tokenTip: 'Enter a valid NetEase Cloud Music Cookie to login',
uidTip: 'Enter User ID for quick login',
placeholder: {
phone: 'Phone Number',
password: 'Password'
password: 'Password',
cookie: 'Please enter NetEase Cloud Music Cookie (token)',
uid: 'Please enter User ID (UID)'
},
button: {
login: 'Login',
switchToQr: 'QR Code Login',
switchToPhone: 'Phone Login'
switchToPhone: 'Phone Login',
switchToToken: 'Use Cookie Login',
switchToUid: 'UID Login',
backToQr: 'Back to QR Code Login',
cookieLogin: 'Cookie Login',
autoGetCookie: 'Auto Get Cookie',
refresh: 'Click to Refresh',
refreshing: 'Refreshing...',
refreshQr: 'Refresh QR Code'
},
message: {
loginSuccess: 'Login successful',
loginFailed: 'Login failed',
tokenLoginSuccess: 'Cookie login successful',
uidLoginSuccess: 'UID login successful',
loadError: 'Error loading login information',
qrCheckError: 'Error checking QR code status'
}
qrCheckError: 'Error checking QR code status',
tokenRequired: 'Please enter Cookie',
tokenInvalid: 'Invalid Cookie, please check and try again',
uidRequired: 'Please enter User ID',
uidInvalid: 'Invalid User ID or user does not exist',
uidLoginFailed: 'UID login failed, please check if User ID is correct',
phoneRequired: 'Please enter phone number',
passwordRequired: 'Please enter password',
phoneLoginFailed: 'Phone login failed, please check if phone number and password are correct',
autoGetCookieSuccess: 'Auto get Cookie successful',
autoGetCookieFailed: 'Auto get Cookie failed',
autoGetCookieTip: 'Will open NetEase Cloud Music login page, please complete login and close the window',
qrCheckFailed: 'Failed to check QR code status, please refresh and try again',
qrLoading: 'Loading QR code...',
qrExpired: 'QR code has expired, please click to refresh',
qrExpiredShort: 'QR code expired',
qrExpiredWarning: 'QR code has expired, please click to refresh for a new one',
qrScanned: 'QR code scanned, please confirm login on your phone',
qrScannedShort: 'Scanned',
qrScannedInfo: 'QR code scanned, please confirm login on your phone',
qrConfirmed: 'Login successful, redirecting...',
qrGenerating: 'Generating QR code...'
},
qrTitle: 'NetEase Cloud Music QR Code Login',
uidWarning: 'Note: UID login is only for viewing user public information and cannot access features that require login permissions.'
};

View File

@@ -11,6 +11,8 @@ export default {
mute: 'Mute',
unmute: 'Unmute',
songNum: 'Song Number: {num}',
addCorrection: 'Add {num} seconds',
subtractCorrection: 'Subtract {num} seconds',
playFailed: 'Play Failed, Play Next Song',
playMode: {
sequence: 'Sequence',
@@ -36,7 +38,8 @@ export default {
failed: 'Reparse failed',
warning: 'Please select a music source',
bilibiliNotSupported: 'Bilibili videos do not support reparsing',
processing: 'Processing...'
processing: 'Processing...',
clear: 'Clear Custom Source'
},
playBar: {
expand: 'Expand Lyrics',
@@ -58,7 +61,9 @@ export default {
next: 'Next',
volume: 'Volume',
favorite: 'Favorite {name}',
unFavorite: 'Unfavorite {name}'
unFavorite: 'Unfavorite {name}',
playbackSpeed: 'Playback Speed',
advancedControls: 'Advanced Controls'
},
eq: {
title: 'Equalizer',
@@ -112,6 +117,7 @@ export default {
cleared: 'Playlist cleared',
empty: 'Playlist is empty',
clearConfirmTitle: 'Clear Playlist',
clearConfirmContent: 'This will clear all songs in the playlist and stop the current playback. Continue?'
clearConfirmContent:
'This will clear all songs in the playlist and stop the current playback. Continue?'
}
};

View File

@@ -16,5 +16,12 @@ export default {
noMore: 'No more results',
error: {
searchFailed: 'Search failed'
},
search: {
single: 'Single',
album: 'Album',
playlist: 'Playlist',
mv: 'MV',
bilibili: 'Bilibili'
}
};

View File

@@ -15,8 +15,18 @@ export default {
basic: {
themeMode: 'Theme Mode',
themeModeDesc: 'Switch between light/dark theme',
autoTheme: 'Follow System',
manualTheme: 'Manual Switch',
language: 'Language Settings',
languageDesc: 'Change display language',
tokenManagement: 'Cookie Management',
tokenManagementDesc: 'Manage NetEase Cloud Music login Cookie',
tokenStatus: 'Current Cookie Status',
tokenSet: 'Set',
tokenNotSet: 'Not Set',
setToken: 'Set Cookie',
modifyToken: 'Modify Cookie',
clearToken: 'Clear Cookie',
font: 'Font Settings',
fontDesc: 'Select fonts, prioritize fonts in order',
fontScope: {
@@ -64,11 +74,13 @@ export default {
configureMusicSources: 'Configure Sources',
selectedMusicSources: 'Selected sources:',
noMusicSources: 'No sources selected',
gdmusicInfo: 'GD Music Station intelligently resolves music from multiple platforms automatically',
gdmusicInfo:
'GD Music Station intelligently resolves music from multiple platforms automatically',
autoPlay: 'Auto Play',
autoPlayDesc: 'Auto resume playback when reopening the app',
showStatusBar: "Show Status Bar",
showStatusBarContent: "You can display the music control function in your mac status bar (effective after a restart)"
showStatusBar: 'Show Status Bar',
showStatusBarContent:
'You can display the music control function in your mac status bar (effective after a restart)'
},
application: {
closeAction: 'Close Action',
@@ -168,42 +180,78 @@ export default {
},
lyricSettings: {
title: 'Lyric Settings',
tabs: {
display: 'Display',
interface: 'Interface',
typography: 'Typography',
mobile: 'Mobile'
},
pureMode: 'Pure Mode',
hideCover: 'Hide Cover',
centerDisplay: 'Center Display',
showTranslation: 'Show Translation',
hideLyrics: 'Hide Lyrics',
hidePlayBar: 'Hide Play Bar',
fontSize: 'Font Size',
letterSpacing: 'Letter Spacing',
lineHeight: 'Line Height',
hideMiniPlayBar: 'Hide Mini Play Bar',
showMiniPlayBar: 'Show Mini Play Bar',
backgroundTheme: 'Background Theme',
fontSizeMarks: {
small: 'Small',
medium: 'Medium',
large: 'Large'
},
letterSpacingMarks: {
compact: 'Compact',
default: 'Default',
loose: 'Loose'
},
lineHeightMarks: {
compact: 'Compact',
default: 'Default',
loose: 'Loose'
},
themeOptions: {
default: 'Default',
light: 'Light',
dark: 'Dark'
},
hideMiniPlayBar: 'Hide Mini Play Bar',
hideLyrics: 'Hide Lyrics',
tabs: {
interface: 'Interface',
display: 'Display',
typography: 'Typography'
}
fontSize: 'Font Size',
fontSizeMarks: {
small: 'Small',
medium: 'Medium',
large: 'Large'
},
letterSpacing: 'Letter Spacing',
letterSpacingMarks: {
compact: 'Compact',
default: 'Default',
loose: 'Loose'
},
lineHeight: 'Line Height',
lineHeightMarks: {
compact: 'Compact',
default: 'Default',
loose: 'Loose'
},
mobileLayout: 'Mobile Layout',
layoutOptions: {
default: 'Default',
ios: 'iOS Style',
android: 'Android Style'
},
mobileCoverStyle: 'Cover Style',
coverOptions: {
record: 'Record',
square: 'Square',
full: 'Full Screen'
},
lyricLines: 'Lyric Lines',
mobileUnavailable: 'This setting is only available on mobile devices'
},
themeColor: {
title: 'Lyric Theme Color',
presetColors: 'Preset Colors',
customColor: 'Custom Color',
preview: 'Preview',
previewText: 'Lyric Effect',
colorNames: {
'spotify-green': 'Spotify Green',
'apple-blue': 'Apple Blue',
'youtube-red': 'YouTube Red',
orange: 'Vibrant Orange',
purple: 'Mystic Purple',
pink: 'Cherry Pink'
},
tooltips: {
openColorPicker: 'Open Color Picker',
closeColorPicker: 'Close Color Picker'
},
placeholder: '#1db954'
},
shortcutSettings: {
title: 'Shortcut Settings',
@@ -243,6 +291,34 @@ export default {
addIp: 'Add IP',
emptyListHint: 'Empty list means allow all IPs',
saveSuccess: 'Remote control settings saved',
accessInfo: 'Remote control access address:',
accessInfo: 'Remote control access address:'
},
cookie: {
title: 'Cookie Settings',
description: 'Please enter NetEase Cloud Music Cookie:',
placeholder: 'Please paste the complete Cookie...',
help: {
format: 'Cookie usually starts with "MUSIC_U="',
source: 'Can be obtained from browser developer tools network requests',
storage: 'Cookie will be automatically saved to local storage after setting'
},
action: {
save: 'Save Cookie',
paste: 'Paste',
clear: 'Clear'
},
validation: {
required: 'Please enter Cookie',
format: 'Cookie format may be incorrect, please check if it contains MUSIC_U'
},
message: {
saveSuccess: 'Cookie saved successfully',
saveError: 'Failed to save Cookie',
pasteSuccess: 'Pasted successfully',
pasteError: 'Paste failed, please copy manually'
},
info: {
length: 'Current length: {length} characters'
}
}
};

View File

@@ -6,7 +6,9 @@ export default {
addToPlaylist: 'Add to Playlist',
favorite: 'Like',
unfavorite: 'Unlike',
removeFromPlaylist: 'Remove from Playlist'
removeFromPlaylist: 'Remove from Playlist',
dislike: 'Dislike',
undislike: 'Undislike'
},
message: {
downloading: 'Downloading, please wait...',
@@ -14,5 +16,13 @@ export default {
downloadQueued: 'Added to download queue',
addedToNextPlay: 'Added to play next',
getUrlFailed: 'Failed to get music download URL, please check if logged in'
},
dialog: {
dislike: {
title: 'Dislike',
content: 'Are you sure you want to dislike this song?',
positiveText: 'Dislike',
negativeText: 'Cancel'
}
}
};

View File

@@ -6,6 +6,7 @@ export default {
},
playlist: {
created: 'Created Playlists',
mine: 'Mine',
trackCount: '{count} tracks',
playCount: 'Played {count} times'
},
@@ -18,12 +19,16 @@ export default {
viewPlaylist: 'View Playlist',
noFollowings: 'No Followings',
loadMore: 'Load More',
noSignature: 'This guy is lazy, nothing left'
noSignature: 'This guy is lazy, nothing left',
userFollowsTitle: "'s Followings",
myFollowsTitle: 'My Followings'
},
follower: {
title: 'Follower List',
noFollowers: 'No Followers',
loadMore: 'Load More'
loadMore: 'Load More',
userFollowersTitle: "'s Followers",
myFollowersTitle: 'My Followers'
},
detail: {
playlists: 'Playlists',
@@ -32,7 +37,8 @@ export default {
noRecords: 'No Listening History',
artist: 'Artist',
noSignature: 'This guy is lazy, nothing left',
invalidUserId: 'Invalid User ID'
invalidUserId: 'Invalid User ID',
noRecordPermission: "{name} doesn't let you see your listening history"
},
message: {
loadFailed: 'Failed to load user page',

View File

@@ -0,0 +1,38 @@
# 日本語翻訳 (Japanese Translation)
このディレクトリには、AlgerMusicPlayerの日本語翻訳ファイルが含まれています。
## ファイル構成
- `artist.ts` - アーティスト関連の翻訳
- `common.ts` - 共通の翻訳(ボタン、メッセージなど)
- `comp.ts` - コンポーネント関連の翻訳
- `donation.ts` - 寄付関連の翻訳
- `download.ts` - ダウンロード管理の翻訳
- `favorite.ts` - お気に入り機能の翻訳
- `history.ts` - 履歴機能の翻訳
- `login.ts` - ログイン関連の翻訳
- `player.ts` - プレイヤー機能の翻訳
- `search.ts` - 検索機能の翻訳
- `settings.ts` - 設定画面の翻訳
- `songItem.ts` - 楽曲アイテムの翻訳
- `user.ts` - ユーザー関連の翻訳
- `index.ts` - すべての翻訳をエクスポートするメインファイル
## 使用方法
アプリケーション内で言語を日本語に切り替えるには:
1. 設定画面を開く
2. 「言語設定」セクションを見つける
3. ドロップダウンメニューから「日本語」を選択
## 翻訳の改善
翻訳の改善や修正がある場合は、該当するファイルを編集してプルリクエストを送信してください。
## 注意事項
- すべての翻訳キーは中国語版と英語版に対応しています
- 新しい機能が追加された場合は、対応する日本語翻訳も追加する必要があります
- 文字化けを避けるため、ファイルはUTF-8エンコーディングで保存してください

View File

@@ -0,0 +1,5 @@
export default {
hotSongs: '人気楽曲',
albums: 'アルバム',
description: 'アーティスト紹介'
};

View File

@@ -0,0 +1,56 @@
export default {
play: '再生',
next: '次の曲',
previous: '前の曲',
volume: '音量',
settings: '設定',
search: '検索',
loading: '読み込み中...',
loadingMore: 'さらに読み込み中...',
alipay: 'Alipay',
wechat: 'WeChat Pay',
on: 'オン',
off: 'オフ',
show: '表示',
hide: '非表示',
confirm: '確認',
cancel: 'キャンセル',
configure: '設定',
open: '開く',
modify: '変更',
success: '操作成功',
error: '操作失敗',
warning: '警告',
info: 'お知らせ',
save: '保存',
delete: '削除',
refresh: '更新',
retry: '再試行',
reset: 'リセット',
back: '戻る',
copySuccess: 'クリップボードにコピーしました',
copyFailed: 'コピーに失敗しました',
validation: {
required: 'この項目は必須です',
invalidInput: '無効な入力です',
selectRequired: 'オプションを選択してください',
numberRange: '{min}から{max}の間の数値を入力してください'
},
viewMore: 'もっと見る',
noMore: 'これ以上ありません',
selectAll: '全選択',
expand: '展開',
collapse: '折りたたみ',
songCount: '{count}曲',
language: '言語',
tray: {
show: '表示',
quit: '終了',
playPause: '再生/一時停止',
prev: '前の曲',
next: '次の曲',
pause: '一時停止',
play: '再生',
favorite: 'お気に入り'
}
};

190
src/i18n/lang/ja-JP/comp.ts Normal file
View File

@@ -0,0 +1,190 @@
export default {
installApp: {
description: 'アプリをインストールして、より良い体験を',
noPrompt: '今後表示しない',
install: '今すぐインストール',
cancel: '後でインストール',
download: 'ダウンロード',
downloadFailed: 'ダウンロード失敗',
downloadComplete: 'ダウンロード完了',
downloadProblem: 'ダウンロードに問題がありますか?',
downloadProblemLinkText: '最新版をダウンロード'
},
playlistDrawer: {
title: 'プレイリストに追加',
createPlaylist: '新しいプレイリストを作成',
cancelCreate: '作成をキャンセル',
create: '作成',
playlistName: 'プレイリスト名',
privatePlaylist: 'プライベートプレイリスト',
publicPlaylist: 'パブリックプレイリスト',
createSuccess: 'プレイリストの作成に成功しました',
createFailed: 'プレイリストの作成に失敗しました',
addSuccess: '楽曲の追加に成功しました',
addFailed: '楽曲の追加に失敗しました',
private: 'プライベート',
public: 'パブリック',
count: '曲',
loginFirst: 'まずログインしてください',
getPlaylistFailed: 'プレイリストの取得に失敗しました',
inputPlaylistName: 'プレイリスト名を入力してください'
},
update: {
title: '新しいバージョンが見つかりました',
currentVersion: '現在のバージョン',
cancel: '後で更新',
prepareDownload: 'ダウンロード準備中...',
downloading: 'ダウンロード中...',
nowUpdate: '今すぐ更新',
downloadFailed: 'ダウンロードに失敗しました。再試行するか手動でダウンロードしてください',
startFailed: 'ダウンロードの開始に失敗しました。再試行するか手動でダウンロードしてください',
noDownloadUrl: '現在のシステムに適したインストールパッケージが見つかりません。手動でダウンロードしてください',
installConfirmTitle: '更新をインストール',
installConfirmContent: 'アプリを閉じて更新をインストールしますか?',
manualInstallTip: 'アプリを閉じた後にインストーラーが正常に起動しない場合は、ダウンロードフォルダでファイルを見つけて手動で開いてください。',
yesInstall: '今すぐインストール',
noThanks: '後でインストール',
fileLocation: 'ファイルの場所',
copy: 'パスをコピー',
copySuccess: 'パスをクリップボードにコピーしました',
copyFailed: 'コピーに失敗しました',
backgroundDownload: 'バックグラウンドダウンロード'
},
coffee: {
title: 'コーヒーをおごる',
alipay: 'Alipay',
wechat: 'WeChat Pay',
alipayQR: 'Alipay QRコード',
wechatQR: 'WeChat QRコード',
coffeeDesc: '一杯のコーヒー、一つのサポート',
coffeeDescLinkText: 'もっと見る',
groupText: '微信公众号AlgerMusic',
messages: {
copySuccess: 'クリップボードにコピーしました'
},
donateList: 'コーヒーをおごる'
},
playlistType: {
title: 'プレイリストカテゴリ',
showAll: 'すべて表示',
hide: '一部を非表示'
},
recommendAlbum: {
title: '最新アルバム'
},
recommendSinger: {
title: '毎日のおすすめ',
songlist: '毎日のおすすめリスト'
},
recommendSonglist: {
title: '今週の人気音楽'
},
searchBar: {
login: 'ログイン',
toLogin: 'ログインへ',
logout: 'ログアウト',
set: '設定',
theme: 'テーマ',
restart: '再起動',
refresh: '更新',
currentVersion: '現在のバージョン',
searchPlaceholder: '何かを検索してみましょう...',
zoom: 'ページズーム',
zoom100: '標準ズーム100%',
resetZoom: 'クリックしてズームをリセット',
zoomDefault: '標準ズーム'
},
titleBar: {
closeTitle: '閉じる方法を選択してください',
minimizeToTray: 'トレイに最小化',
exitApp: 'アプリを終了',
rememberChoice: '選択を記憶する',
closeApp: 'アプリを閉じる'
},
userPlayList: {
title: '{name}のよく聞く音楽'
},
musicList: {
searchSongs: '楽曲を検索',
noSearchResults: '関連する楽曲が見つかりませんでした',
switchToNormal: 'デフォルトレイアウトに切り替え',
switchToCompact: 'コンパクトレイアウトに切り替え',
playAll: 'すべて再生',
collect: 'お気に入り',
collectSuccess: 'お気に入りに追加しました',
cancelCollectSuccess: 'お気に入りから削除しました',
operationFailed: '操作に失敗しました',
cancelCollect: 'お気に入りから削除',
addToPlaylist: 'プレイリストに追加',
addToPlaylistSuccess: 'プレイリストに追加しました',
songsAlreadyInPlaylist: '楽曲は既にプレイリストに存在します'
},
playlist: {
import: {
button: 'プレイリストインポート',
title: 'プレイリストインポート',
description: 'メタデータ/テキスト/リンクの3つの方法でプレイリストをインポートできます',
linkTab: 'リンクインポート',
textTab: 'テキストインポート',
localTab: 'メタデータインポート',
linkPlaceholder: 'プレイリストのリンクを入力してください1行に1つ',
textPlaceholder: '楽曲情報を入力してください。形式:楽曲名 アーティスト名',
localPlaceholder: 'JSON形式の楽曲メタデータを入力してください',
linkTips: 'サポートされているリンクソース:',
linkTip1: 'プレイリストをWeChat/Weibo/QQでシェアした後、リンクをコピー',
linkTip2: 'プレイリスト/個人ページのリンクを直接コピー',
linkTip3: '記事のリンクを直接コピー',
textTips: '楽曲情報を入力してください1行に1曲',
textFormat: '形式:楽曲名 アーティスト名',
localTips: '楽曲メタデータを追加してください',
localFormat: '形式例:',
songNamePlaceholder: '楽曲名',
artistNamePlaceholder: 'アーティスト名',
albumNamePlaceholder: 'アルバム名',
addSongButton: '楽曲を追加',
addLinkButton: 'リンクを追加',
importToStarPlaylist: 'お気に入りの音楽にインポート',
playlistNamePlaceholder: 'プレイリスト名を入力してください',
importButton: 'インポート開始',
emptyLinkWarning: 'プレイリストのリンクを入力してください',
emptyTextWarning: '楽曲情報を入力してください',
emptyLocalWarning: '楽曲メタデータを入力してください',
invalidJsonFormat: 'JSON形式が正しくありません',
importSuccess: 'インポートタスクの作成に成功しました',
importFailed: 'インポートに失敗しました',
importStatus: 'インポート状況',
refresh: '更新',
taskId: 'タスクID',
status: 'ステータス',
successCount: '成功数',
failReason: '失敗理由',
unknownError: '不明なエラー',
statusPending: '処理待ち',
statusProcessing: '処理中',
statusSuccess: 'インポート成功',
statusFailed: 'インポート失敗',
statusUnknown: '不明なステータス',
taskList: 'タスクリスト',
taskListTitle: 'インポートタスクリスト',
action: '操作',
select: '選択',
fetchTaskListFailed: 'タスクリストの取得に失敗しました',
noTasks: 'インポートタスクがありません',
clearTasks: 'タスクをクリア',
clearTasksConfirmTitle: 'クリア確認',
clearTasksConfirmContent: 'すべてのインポートタスク記録をクリアしますか?この操作は元に戻せません。',
confirm: '確認',
cancel: 'キャンセル',
clearTasksSuccess: 'タスクリストをクリアしました',
clearTasksFailed: 'タスクリストのクリアに失敗しました'
}
},
settings: '設定',
user: 'ユーザー',
toplist: 'ランキング',
history: 'お気に入り履歴',
list: 'プレイリスト',
mv: 'MV',
home: 'ホーム',
search: '検索'
};

View File

@@ -0,0 +1,8 @@
export default {
description: 'あなたの寄付は開発・保守作業をサポートするために使用され、サーバー保守、ドメイン更新などが含まれます。',
message: 'メッセージを残す際は、メールアドレスやGitHubユーザー名を記載してください。',
refresh: 'リストを更新',
toDonateList: 'コーヒーをおごる',
noMessage: 'メッセージがありません',
title: '寄付リスト'
};

View File

@@ -0,0 +1,87 @@
export default {
title: 'ダウンロード管理',
localMusic: 'ローカル音楽',
count: '合計{count}曲',
clearAll: '記録をクリア',
settings: '設定',
tabs: {
downloading: 'ダウンロード中',
downloaded: 'ダウンロード済み'
},
empty: {
noTasks: 'ダウンロードタスクがありません',
noDownloaded: 'ダウンロード済みの楽曲がありません'
},
progress: {
total: '全体の進行状況: {progress}%'
},
status: {
downloading: 'ダウンロード中',
completed: '完了',
failed: '失敗',
unknown: '不明'
},
artist: {
unknown: '不明なアーティスト'
},
delete: {
title: '削除確認',
message: '楽曲「{filename}」を削除しますか?この操作は元に戻せません。',
confirm: '削除確認',
cancel: 'キャンセル',
success: '削除成功',
failed: '削除失敗',
fileNotFound: 'ファイルが存在しないか移動されました。記録から削除しました',
recordRemoved: 'ファイルの削除に失敗しましたが、記録から削除しました'
},
clear: {
title: 'ダウンロード記録をクリア',
message: 'すべてのダウンロード記録をクリアしますか?この操作はダウンロード済みの音楽ファイルを削除しませんが、すべての記録をクリアします。',
confirm: 'クリア確認',
cancel: 'キャンセル',
success: 'ダウンロード記録をクリアしました'
},
message: {
downloadComplete: '{filename}のダウンロードが完了しました',
downloadFailed: '{filename}のダウンロードに失敗しました: {error}'
},
loading: '読み込み中...',
playStarted: '再生開始: {name}',
playFailed: '再生失敗: {name}',
path: {
copied: 'パスをクリップボードにコピーしました',
copyFailed: 'パスのコピーに失敗しました'
},
settingsPanel: {
title: 'ダウンロード設定',
path: 'ダウンロード場所',
pathDesc: '音楽ファイルのダウンロード保存場所を設定',
pathPlaceholder: 'ダウンロードパスを選択してください',
noPathSelected: 'まずダウンロードパスを選択してください',
select: 'フォルダを選択',
open: 'フォルダを開く',
fileFormat: 'ファイル名形式',
fileFormatDesc: '音楽ダウンロード時のファイル命名形式を設定',
customFormat: 'カスタム形式',
separator: '区切り文字',
separators: {
dash: 'スペース-スペース',
underscore: 'アンダースコア',
space: 'スペース'
},
dragToArrange: 'ドラッグで並び替えまたは矢印ボタンで順序を調整:',
formatVariables: '使用可能な変数',
preview: 'プレビュー効果:',
saveSuccess: 'ダウンロード設定を保存しました',
presets: {
songArtist: '楽曲名 - アーティスト名',
artistSong: 'アーティスト名 - 楽曲名',
songOnly: '楽曲名のみ'
},
components: {
songName: '楽曲名',
artistName: 'アーティスト名',
albumName: 'アルバム名'
}
}
};

View File

@@ -0,0 +1,13 @@
export default {
title: 'お気に入り',
count: '合計{count}曲',
batchDownload: '一括ダウンロード',
download: 'ダウンロード ({count})',
emptyTip: 'まだお気に入りの楽曲がありません',
downloadSuccess: 'ダウンロード完了',
downloadFailed: 'ダウンロード失敗',
downloading: 'ダウンロード中です。しばらくお待ちください...',
selectSongsFirst: 'まずダウンロードする楽曲を選択してください',
descending: '降順',
ascending: '昇順'
};

View File

@@ -0,0 +1,5 @@
export default {
title: '再生履歴',
playCount: '{count}',
getHistoryFailed: '履歴の取得に失敗しました'
};

View File

@@ -0,0 +1,48 @@
export default {
title: {
qr: 'QRコードログイン',
phone: '電話番号ログイン',
cookie: 'Cookieログイン',
uid: 'UIDログイン'
},
qrTip: 'NetEase Cloudアプリでログイン',
phoneTip: 'NetEase Cloudアカウントでログイン',
tokenTip: '有効なNetEase Cloud MusicのCookieを入力してログイン',
uidTip: 'ユーザーIDを入力してクイックログイン',
placeholder: {
phone: '電話番号',
password: 'パスワード',
cookie: 'NetEase Cloud MusicのCookietokenを入力してください',
uid: 'ユーザーIDUIDを入力してください'
},
button: {
login: 'ログイン',
switchToQr: 'QRコードログイン',
switchToPhone: '電話番号ログイン',
switchToToken: 'Cookieログインを使用',
switchToUid: 'UIDログイン',
backToQr: 'QRコードログインに戻る',
cookieLogin: 'Cookieログイン',
autoGetCookie: 'Cookie自動取得',
refresh: 'クリックしてリフレッシュ',
refreshing: 'リフレッシュ中...',
refreshQr: 'QRコードをリフレッシュ'
},
message: {
loginSuccess: 'ログイン成功',
tokenLoginSuccess: 'Cookieログイン成功',
uidLoginSuccess: 'UIDログイン成功',
loadError: 'ログイン情報の読み込み中にエラーが発生しました',
qrCheckError: 'QRコードの状態確認中にエラーが発生しました',
tokenRequired: 'Cookieを入力してください',
tokenInvalid: 'Cookieが無効です。確認して再試行してください',
uidRequired: 'ユーザーIDを入力してください',
uidInvalid: 'ユーザーIDが無効またはユーザーが存在しません',
uidLoginFailed: 'UIDログインに失敗しました。ユーザーIDが正しいか確認してください',
autoGetCookieSuccess: 'Cookie自動取得成功',
autoGetCookieFailed: 'Cookie自動取得失敗',
autoGetCookieTip: 'NetEase Cloud Musicのログインページを開きます。ログイン完了後、ウィンドウを閉じてください'
},
qrTitle: 'NetEase Cloud Music QRコードログイン',
uidWarning: '注意UIDログインはユーザーの公開情報を表示するためのみ使用でき、ログイン権限が必要な機能にはアクセスできません。'
};

View File

@@ -0,0 +1,123 @@
export default {
nowPlaying: '再生中',
playlist: 'プレイリスト',
lyrics: '歌詞',
previous: '前へ',
play: '再生',
pause: '一時停止',
next: '次へ',
volumeUp: '音量を上げる',
volumeDown: '音量を下げる',
mute: 'ミュート',
unmute: 'ミュート解除',
songNum: '楽曲総数:{num}',
addCorrection: '{num}秒早める',
subtractCorrection: '{num}秒遅らせる',
playFailed: '現在の楽曲の再生に失敗しました。次の曲を再生します',
playMode: {
sequence: '順次再生',
loop: 'リピート再生',
random: 'ランダム再生'
},
fullscreen: {
enter: 'フルスクリーン',
exit: 'フルスクリーン終了'
},
close: '閉じる',
modeHint: {
single: 'リピート再生',
list: '自動で次の曲を再生'
},
lrc: {
noLrc: '歌詞がありません。お楽しみください'
},
reparse: {
title: '解析音源を選択',
desc: '音源をクリックして直接解析します。次回この楽曲を再生する際は選択した音源を使用します',
success: '再解析成功',
failed: '再解析失敗',
warning: '音源を選択してください',
bilibiliNotSupported: 'Bilibili動画は再解析をサポートしていません',
processing: '解析中...',
clear: 'カスタム音源をクリア'
},
playBar: {
expand: '歌詞を展開',
collapse: '歌詞を折りたたみ',
like: 'いいね',
lyric: '歌詞',
noSongPlaying: '再生中の楽曲がありません',
eq: 'イコライザー',
playList: 'プレイリスト',
reparse: '再解析',
playMode: {
sequence: '順次再生',
loop: 'ループ再生',
random: 'ランダム再生'
},
play: '再生開始',
pause: '再生一時停止',
prev: '前の曲',
next: '次の曲',
volume: '音量',
favorite: '{name}をお気に入りに追加しました',
unFavorite: '{name}をお気に入りから削除しました',
miniPlayBar: 'ミニ再生バー',
playbackSpeed: '再生速度',
advancedControls: 'その他の設定'
},
eq: {
title: 'イコライザー',
reset: 'リセット',
on: 'オン',
off: 'オフ',
bass: '低音',
midrange: '中音',
treble: '高音',
presets: {
flat: 'フラット',
pop: 'ポップ',
rock: 'ロック',
classical: 'クラシック',
jazz: 'ジャズ',
electronic: 'エレクトロニック',
hiphop: 'ヒップホップ',
rb: 'R&B',
metal: 'メタル',
vocal: 'ボーカル',
dance: 'ダンス',
acoustic: 'アコースティック',
custom: 'カスタム'
}
},
// タイマー機能関連
sleepTimer: {
title: 'スリープタイマー',
cancel: 'タイマーをキャンセル',
timeMode: '時間で停止',
songsMode: '楽曲数で停止',
playlistEnd: 'プレイリスト終了後に停止',
afterPlaylist: 'プレイリスト終了後に停止',
activeUntilEnd: 'リスト終了まで再生',
minutes: '分',
hours: '時間',
songs: '曲',
set: '設定',
timerSetSuccess: '{minutes}分後に停止するよう設定しました',
songsSetSuccess: '{songs}曲再生後に停止するよう設定しました',
playlistEndSetSuccess: 'プレイリスト終了後に停止するよう設定しました',
timerCancelled: 'スリープタイマーをキャンセルしました',
timerEnded: 'スリープタイマーが作動しました',
playbackStopped: '音楽再生を停止しました',
minutesRemaining: '残り{minutes}分',
songsRemaining: '残り{count}曲'
},
playList: {
clearAll: 'プレイリストをクリア',
alreadyEmpty: 'プレイリストは既に空です',
cleared: 'プレイリストをクリアしました',
empty: 'プレイリストが空です',
clearConfirmTitle: 'プレイリストをクリア',
clearConfirmContent: 'これによりプレイリスト内のすべての楽曲がクリアされ、現在の再生が停止されます。続行しますか?'
}
};

View File

@@ -0,0 +1,27 @@
export default {
title: {
hotSearch: '人気検索リスト',
searchList: '検索リスト',
searchHistory: '検索履歴'
},
button: {
clear: 'クリア',
back: '戻る',
playAll: 'リストを再生'
},
loading: {
more: '読み込み中...',
failed: '検索に失敗しました'
},
noMore: 'これ以上ありません',
error: {
searchFailed: '検索に失敗しました'
},
search: {
single: '楽曲',
album: 'アルバム',
playlist: 'プレイリスト',
mv: 'MV',
bilibili: 'Bilibili'
}
};

View File

@@ -0,0 +1,322 @@
export default {
theme: 'テーマ',
language: '言語',
regard: 'について',
logout: 'ログアウト',
sections: {
basic: '基本設定',
playback: '再生設定',
application: 'アプリケーション設定',
network: 'ネットワーク設定',
system: 'システム管理',
donation: '寄付サポート',
regard: 'について'
},
basic: {
themeMode: 'テーマモード',
themeModeDesc: 'ライト/ダークテーマの切り替え',
autoTheme: 'システムに従う',
manualTheme: '手動切り替え',
language: '言語設定',
languageDesc: '表示言語を切り替え',
tokenManagement: 'Cookie管理',
tokenManagementDesc: 'NetEase Cloud MusicログインCookieを管理',
tokenStatus: '現在のCookieステータス',
tokenSet: '設定済み',
tokenNotSet: '未設定',
setToken: 'Cookieを設定',
modifyToken: 'Cookieを変更',
clearToken: 'Cookieをクリア',
font: 'フォント設定',
fontDesc: 'フォントを選択します。前に配置されたフォントが優先されます',
fontScope: {
global: 'グローバル',
lyric: '歌詞のみ'
},
animation: 'アニメーション速度',
animationDesc: 'アニメーションを有効にするかどうか',
animationSpeed: {
slow: '非常に遅い',
normal: '通常',
fast: '非常に速い'
},
fontPreview: {
title: 'フォントプレビュー',
chinese: '中国語',
english: 'English',
japanese: '日本語',
korean: '韓国語',
chineseText: '静夜思 床前明月光 疑是地上霜',
englishText: 'The quick brown fox jumps over the lazy dog',
japaneseText: 'あいうえお かきくけこ さしすせそ',
koreanText: '가나다라마 바사아자차 카타파하'
}
},
playback: {
quality: '音質設定',
qualityDesc: '音楽再生の音質を選択NetEase Cloud VIP',
qualityOptions: {
standard: '標準',
higher: '高音質',
exhigh: '超高音質',
lossless: 'ロスレス',
hires: 'Hi-Res',
jyeffect: 'HD サラウンド',
sky: 'イマーシブサラウンド',
dolby: 'Dolby Atmos',
jymaster: '超高解像度マスター'
},
musicSources: '音源設定',
musicSourcesDesc: '音楽解析に使用する音源プラットフォームを選択',
musicSourcesWarning: '少なくとも1つの音源プラットフォームを選択する必要があります',
musicUnblockEnable: '音楽解析を有効にする',
musicUnblockEnableDesc: '有効にすると、再生できない音楽の解析を試みます',
configureMusicSources: '音源を設定',
selectedMusicSources: '選択された音源:',
noMusicSources: '音源が選択されていません',
gdmusicInfo: 'GD音楽台は複数のプラットフォーム音源を自動解析し、最適な結果を自動選択できます',
autoPlay: '自動再生',
autoPlayDesc: 'アプリを再起動した際に自動的に再生を継続するかどうか',
showStatusBar: 'ステータスバーコントロール機能を表示するかどうか',
showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます再起動後に有効'
},
application: {
closeAction: '閉じる動作',
closeActionDesc: 'ウィンドウを閉じる際の動作を選択',
closeOptions: {
ask: '毎回確認',
minimize: 'トレイに最小化',
close: '直接終了'
},
shortcut: 'ショートカット設定',
shortcutDesc: 'グローバルショートカットをカスタマイズ',
download: 'ダウンロード管理',
downloadDesc: 'ダウンロードリストボタンを常に表示するかどうか',
unlimitedDownload: '無制限ダウンロード',
unlimitedDownloadDesc: '有効にすると音楽を無制限でダウンロードしますダウンロード失敗の可能性があります。デフォルトは300曲制限',
downloadPath: 'ダウンロードディレクトリ',
downloadPathDesc: '音楽ファイルのダウンロード場所を選択',
remoteControl: 'リモートコントロール',
remoteControlDesc: 'リモートコントロール機能を設定'
},
network: {
apiPort: '音楽APIポート',
apiPortDesc: '変更後はアプリの再起動が必要です',
proxy: 'プロキシ設定',
proxyDesc: '音楽にアクセスできない場合はプロキシを有効にできます',
proxyHost: 'プロキシアドレス',
proxyHostPlaceholder: 'プロキシアドレスを入力してください',
proxyPort: 'プロキシポート',
proxyPortPlaceholder: 'プロキシポートを入力してください',
realIP: 'realIP設定',
realIPDesc: '制限により、このプロジェクトは海外での使用が制限されます。realIPパラメータを使用して国内IPを渡すことで解決できます',
messages: {
proxySuccess: 'プロキシ設定を保存しました。アプリ再起動後に有効になります',
proxyError: '入力が正しいかどうか確認してください',
realIPSuccess: '実IPアドレス設定を保存しました',
realIPError: '有効なIPアドレスを入力してください'
}
},
system: {
cache: 'キャッシュ管理',
cacheDesc: 'キャッシュをクリア',
cacheClearTitle: 'クリアするキャッシュタイプを選択してください:',
cacheTypes: {
history: {
label: '再生履歴',
description: '再生した楽曲の記録をクリア'
},
favorite: {
label: 'お気に入り記録',
description: 'ローカルのお気に入り楽曲記録をクリア(クラウドのお気に入りには影響しません)'
},
user: {
label: 'ユーザーデータ',
description: 'ログイン情報とユーザー関連データをクリア'
},
settings: {
label: 'アプリ設定',
description: 'アプリのすべてのカスタム設定をクリア'
},
downloads: {
label: 'ダウンロード記録',
description: 'ダウンロード履歴をクリア(ダウンロード済みファイルは削除されません)'
},
resources: {
label: '音楽リソース',
description: '読み込み済みの音楽ファイル、歌詞などのリソースキャッシュをクリア'
},
lyrics: {
label: '歌詞リソース',
description: '読み込み済みの歌詞リソースキャッシュをクリア'
}
},
restart: '再起動',
restartDesc: 'アプリを再起動',
messages: {
clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります'
}
},
about: {
version: 'バージョン',
checkUpdate: '更新を確認',
checking: '確認中...',
latest: '現在最新バージョンです',
hasUpdate: '新しいバージョンが見つかりました',
gotoUpdate: '更新へ',
gotoGithub: 'Githubへ',
author: '作者',
authorDesc: 'algerkong スターを付けてください🌟',
messages: {
checkError: '更新確認に失敗しました。後でもう一度お試しください'
}
},
validation: {
selectProxyProtocol: 'プロキシプロトコルを選択してください',
proxyHost: 'プロキシアドレスを入力してください',
portNumber: '有効なポート番号を入力してください1-65535'
},
lyricSettings: {
title: '歌詞設定',
tabs: {
display: '表示',
interface: 'インターフェース',
typography: 'テキスト',
mobile: 'モバイル'
},
pureMode: 'ピュアモード',
hideCover: 'カバーを非表示',
centerDisplay: '中央表示',
showTranslation: '翻訳を表示',
hideLyrics: '歌詞を非表示',
hidePlayBar: '再生バーを非表示',
hideMiniPlayBar: 'ミニ再生バーを非表示',
showMiniPlayBar: 'ミニ再生バーを表示',
backgroundTheme: '背景テーマ',
themeOptions: {
default: 'デフォルト',
light: 'ライト',
dark: 'ダーク'
},
fontSize: 'フォントサイズ',
fontSizeMarks: {
small: '小',
medium: '中',
large: '大'
},
letterSpacing: '文字間隔',
letterSpacingMarks: {
compact: 'コンパクト',
default: 'デフォルト',
loose: 'ゆったり'
},
lineHeight: '行の高さ',
lineHeightMarks: {
compact: 'コンパクト',
default: 'デフォルト',
loose: 'ゆったり'
},
mobileLayout: 'モバイルレイアウト',
layoutOptions: {
default: 'デフォルト',
ios: 'iOSスタイル',
android: 'Androidスタイル'
},
mobileCoverStyle: 'カバースタイル',
coverOptions: {
record: 'レコード',
square: '正方形',
full: 'フルスクリーン'
},
lyricLines: '歌詞行数',
mobileUnavailable: 'この設定はモバイルでのみ利用可能です'
},
themeColor: {
title: '歌詞テーマカラー',
presetColors: 'プリセットカラー',
customColor: 'カスタムカラー',
preview: 'プレビュー効果',
previewText: '歌詞効果',
colorNames: {
'spotify-green': 'Spotify グリーン',
'apple-blue': 'Apple ブルー',
'youtube-red': 'YouTube レッド',
orange: 'バイタルオレンジ',
purple: 'ミステリアスパープル',
pink: 'サクラピンク'
},
tooltips: {
openColorPicker: 'カラーパレットを開く',
closeColorPicker: 'カラーパレットを閉じる'
},
placeholder: '#1db954'
},
shortcutSettings: {
title: 'ショートカット設定',
shortcut: 'ショートカット',
shortcutDesc: 'ショートカットをカスタマイズ',
shortcutConflict: 'ショートカットの競合',
inputPlaceholder: 'クリックしてショートカットを入力',
resetShortcuts: 'デフォルトに戻す',
disableAll: 'すべて無効',
enableAll: 'すべて有効',
togglePlay: '再生/一時停止',
prevPlay: '前の曲',
nextPlay: '次の曲',
volumeUp: '音量を上げる',
volumeDown: '音量を下げる',
toggleFavorite: 'お気に入り/お気に入り解除',
toggleWindow: 'ウィンドウ表示/非表示',
scopeGlobal: 'グローバル',
scopeApp: 'アプリ内',
enabled: '有効',
disabled: '無効',
messages: {
resetSuccess: 'デフォルトのショートカットに戻しました。保存を忘れずに',
conflict: '競合するショートカットがあります。再設定してください',
saveSuccess: 'ショートカット設定を保存しました',
saveError: 'ショートカットの保存に失敗しました。再試行してください',
cancelEdit: '変更をキャンセルしました',
disableAll: 'すべてのショートカットを無効にしました。保存を忘れずに',
enableAll: 'すべてのショートカットを有効にしました。保存を忘れずに'
}
},
remoteControl: {
title: 'リモートコントロール',
enable: 'リモートコントロールを有効にする',
port: 'サービスポート',
allowedIps: '許可されたIPアドレス',
addIp: 'IPを追加',
emptyListHint: '空のリストはすべてのIPアクセスを許可することを意味します',
saveSuccess: 'リモートコントロール設定を保存しました',
accessInfo: 'リモートコントロールアクセスアドレス:'
},
cookie: {
title: 'Cookie設定',
description: 'NetEase Cloud MusicのCookieを入力してください',
placeholder: '完全なCookieを貼り付けてください...',
help: {
format: 'Cookieは通常「MUSIC_U=」で始まります',
source: 'ブラウザの開発者ツールのネットワークリクエストから取得できます',
storage: 'Cookie設定後、自動的にローカルストレージに保存されます'
},
action: {
save: 'Cookieを保存',
paste: '貼り付け',
clear: 'クリア'
},
validation: {
required: 'Cookieを入力してください',
format: 'Cookie形式が正しくない可能性があります。MUSIC_Uが含まれているか確認してください'
},
message: {
saveSuccess: 'Cookieの保存に成功しました',
saveError: 'Cookieの保存に失敗しました',
pasteSuccess: '貼り付けに成功しました',
pasteError: '貼り付けに失敗しました。手動でコピーしてください'
},
info: {
length: '現在の長さ:{length} 文字'
}
}
};

View File

@@ -0,0 +1,28 @@
export default {
menu: {
play: '再生',
playNext: '次に再生',
download: '楽曲をダウンロード',
addToPlaylist: 'プレイリストに追加',
favorite: 'いいね',
unfavorite: 'いいね解除',
removeFromPlaylist: 'プレイリストから削除',
dislike: '嫌い',
undislike: '嫌い解除'
},
message: {
downloading: 'ダウンロード中です。しばらくお待ちください...',
downloadFailed: 'ダウンロードに失敗しました',
downloadQueued: 'ダウンロードキューに追加しました',
addedToNextPlay: '次の再生に追加しました',
getUrlFailed: '音楽ダウンロードアドレスの取得に失敗しました。ログインしているか確認してください'
},
dialog: {
dislike: {
title: 'お知らせ!',
content: 'この楽曲を嫌いにしますか?再度アクセスすると毎日のおすすめから除外されます。',
positiveText: '嫌い',
negativeText: 'キャンセル'
}
}
};

View File

@@ -0,0 +1,48 @@
export default {
profile: {
followers: 'フォロワー',
following: 'フォロー中',
level: 'レベル'
},
playlist: {
created: '作成したプレイリスト',
mine: '私が作成した',
trackCount: '{count}曲',
playCount: '{count}回再生'
},
ranking: {
title: '聴取ランキング',
playCount: '{count}回'
},
follow: {
title: 'フォローリスト',
viewPlaylist: 'プレイリストを見る',
noFollowings: 'フォローがありません',
loadMore: 'さらに読み込み',
noSignature: 'この人は怠け者で、何も残していません',
userFollowsTitle: 'のフォロー',
myFollowsTitle: '私のフォロー'
},
follower: {
title: 'フォロワーリスト',
noFollowers: 'フォロワーがいません',
loadMore: 'さらに読み込み',
userFollowersTitle: 'のフォロワー',
myFollowersTitle: '私のフォロワー'
},
detail: {
playlists: 'プレイリスト',
records: '聴取ランキング',
noPlaylists: 'プレイリストがありません',
noRecords: '聴取記録がありません',
artist: 'アーティスト',
noSignature: 'この人は怠け者で、何も残していません',
invalidUserId: '無効なユーザーID',
noRecordPermission: '{name}は聴取ランキングを見せてくれません'
},
message: {
loadFailed: 'ユーザーページの読み込みに失敗しました',
deleteSuccess: '削除成功',
deleteFailed: '削除失敗'
}
};

View File

@@ -0,0 +1,5 @@
export default {
hotSongs: '인기 곡',
albums: '앨범',
description: '아티스트 소개'
};

View File

@@ -0,0 +1,56 @@
export default {
play: '재생',
next: '다음 곡',
previous: '이전 곡',
volume: '볼륨',
settings: '설정',
search: '검색',
loading: '로딩 중...',
loadingMore: '더 불러오기...',
alipay: '알리페이',
wechat: '위챗 페이',
on: '켜기',
off: '끄기',
show: '표시',
hide: '숨기기',
confirm: '확인',
cancel: '취소',
configure: '구성',
open: '열기',
modify: '수정',
success: '작업 성공',
error: '작업 실패',
warning: '경고',
info: '알림',
save: '저장',
delete: '삭제',
refresh: '새로고침',
retry: '다시 시도',
reset: '재설정',
back: '뒤로',
copySuccess: '클립보드에 복사됨',
copyFailed: '복사 실패',
validation: {
required: '이 항목은 필수입니다',
invalidInput: '잘못된 입력',
selectRequired: '옵션을 선택해주세요',
numberRange: '{min}에서 {max} 사이의 숫자를 입력해주세요'
},
viewMore: '더 보기',
noMore: '더 이상 없음',
selectAll: '전체 선택',
expand: '펼치기',
collapse: '접기',
songCount: '{count}곡',
language: '언어',
tray: {
show: '표시',
quit: '종료',
playPause: '재생/일시정지',
prev: '이전 곡',
next: '다음 곡',
pause: '일시정지',
play: '재생',
favorite: '즐겨찾기'
}
};

190
src/i18n/lang/ko-KR/comp.ts Normal file
View File

@@ -0,0 +1,190 @@
export default {
installApp: {
description: '앱을 설치하여 더 나은 경험을 얻으세요',
noPrompt: '다시 묻지 않기',
install: '지금 설치',
cancel: '나중에 설치',
download: '다운로드',
downloadFailed: '다운로드 실패',
downloadComplete: '다운로드 완료',
downloadProblem: '다운로드에 문제가 있나요?',
downloadProblemLinkText: '최신 버전 다운로드'
},
playlistDrawer: {
title: '플레이리스트에 추가',
createPlaylist: '새 플레이리스트 만들기',
cancelCreate: '만들기 취소',
create: '만들기',
playlistName: '플레이리스트 이름',
privatePlaylist: '비공개 플레이리스트',
publicPlaylist: '공개 플레이리스트',
createSuccess: '플레이리스트 생성 성공',
createFailed: '플레이리스트 생성 실패',
addSuccess: '곡 추가 성공',
addFailed: '곡 추가 실패',
private: '비공개',
public: '공개',
count: '곡',
loginFirst: '먼저 로그인해주세요',
getPlaylistFailed: '플레이리스트 가져오기 실패',
inputPlaylistName: '플레이리스트 이름을 입력해주세요'
},
update: {
title: '새 버전 발견',
currentVersion: '현재 버전',
cancel: '나중에 업데이트',
prepareDownload: '다운로드 준비 중...',
downloading: '다운로드 중...',
nowUpdate: '지금 업데이트',
downloadFailed: '다운로드 실패, 다시 시도하거나 수동으로 다운로드해주세요',
startFailed: '다운로드 시작 실패, 다시 시도하거나 수동으로 다운로드해주세요',
noDownloadUrl: '현재 시스템에 적합한 설치 패키지를 찾을 수 없습니다. 수동으로 다운로드해주세요',
installConfirmTitle: '업데이트 설치',
installConfirmContent: '앱을 닫고 업데이트를 설치하시겠습니까?',
manualInstallTip: '앱을 닫은 후 설치 프로그램이 정상적으로 나타나지 않으면 다운로드 폴더에서 파일을 찾아 수동으로 열어주세요.',
yesInstall: '지금 설치',
noThanks: '나중에 설치',
fileLocation: '파일 위치',
copy: '경로 복사',
copySuccess: '경로가 클립보드에 복사됨',
copyFailed: '복사 실패',
backgroundDownload: '백그라운드 다운로드'
},
coffee: {
title: '커피 한 잔 사주세요',
alipay: '알리페이',
wechat: '위챗 페이',
alipayQR: '알리페이 결제 QR코드',
wechatQR: '위챗 결제 QR코드',
coffeeDesc: '커피 한 잔, 하나의 지원',
coffeeDescLinkText: '더 보기',
groupText: '微信公众号AlgerMusic',
messages: {
copySuccess: '클립보드에 복사됨'
},
donateList: '커피 한 잔 사주세요'
},
playlistType: {
title: '플레이리스트 분류',
showAll: '모두 표시',
hide: '일부 숨기기'
},
recommendAlbum: {
title: '최신 앨범'
},
recommendSinger: {
title: '일일 추천',
songlist: '일일 추천 목록'
},
recommendSonglist: {
title: '이번 주 인기 음악'
},
searchBar: {
login: '로그인',
toLogin: '로그인하기',
logout: '로그아웃',
set: '설정',
theme: '테마',
restart: '재시작',
refresh: '새로고침',
currentVersion: '현재 버전',
searchPlaceholder: '검색해보세요...',
zoom: '페이지 확대/축소',
zoom100: '표준 확대/축소 100%',
resetZoom: '클릭하여 확대/축소 재설정',
zoomDefault: '표준 확대/축소'
},
titleBar: {
closeTitle: '닫기 방법을 선택해주세요',
minimizeToTray: '트레이로 최소화',
exitApp: '앱 종료',
rememberChoice: '선택 기억하기',
closeApp: '앱 닫기'
},
userPlayList: {
title: '{name}의 자주 듣는 음악'
},
musicList: {
searchSongs: '곡 검색',
noSearchResults: '관련 곡을 찾을 수 없습니다',
switchToNormal: '기본 레이아웃으로 전환',
switchToCompact: '컴팩트 레이아웃으로 전환',
playAll: '모두 재생',
collect: '수집',
collectSuccess: '수집 성공',
cancelCollectSuccess: '수집 취소 성공',
operationFailed: '작업 실패',
cancelCollect: '수집 취소',
addToPlaylist: '재생 목록에 추가',
addToPlaylistSuccess: '재생 목록에 추가 성공',
songsAlreadyInPlaylist: '곡이 이미 재생 목록에 있습니다'
},
playlist: {
import: {
button: '플레이리스트 가져오기',
title: '플레이리스트 가져오기',
description: '메타데이터/텍스트/링크 세 가지 방법으로 플레이리스트 가져오기 지원',
linkTab: '링크 가져오기',
textTab: '텍스트 가져오기',
localTab: '메타데이터 가져오기',
linkPlaceholder: '플레이리스트 링크를 입력하세요. 한 줄에 하나씩',
textPlaceholder: '곡 정보를 입력하세요. 형식: 곡명 가수명',
localPlaceholder: 'JSON 형식의 곡 메타데이터를 입력하세요',
linkTips: '지원되는 링크 소스:',
linkTip1: '플레이리스트를 위챗/웨이보/QQ로 공유한 후 링크 복사',
linkTip2: '플레이리스트/개인 홈페이지 링크 직접 복사',
linkTip3: '기사 링크 직접 복사',
textTips: '곡 정보를 입력하세요. 한 줄에 한 곡씩',
textFormat: '형식: 곡명 가수명',
localTips: '곡 메타데이터를 추가해주세요',
localFormat: '형식 예시:',
songNamePlaceholder: '곡명',
artistNamePlaceholder: '아티스트명',
albumNamePlaceholder: '앨범명',
addSongButton: '곡 추가',
addLinkButton: '링크 추가',
importToStarPlaylist: '내가 좋아하는 음악으로 가져오기',
playlistNamePlaceholder: '플레이리스트 이름을 입력하세요',
importButton: '가져오기 시작',
emptyLinkWarning: '플레이리스트 링크를 입력해주세요',
emptyTextWarning: '곡 정보를 입력해주세요',
emptyLocalWarning: '곡 메타데이터를 입력해주세요',
invalidJsonFormat: 'JSON 형식이 올바르지 않습니다',
importSuccess: '가져오기 작업 생성 성공',
importFailed: '가져오기 실패',
importStatus: '가져오기 상태',
refresh: '새로고침',
taskId: '작업 ID',
status: '상태',
successCount: '성공 수',
failReason: '실패 이유',
unknownError: '알 수 없는 오류',
statusPending: '처리 대기 중',
statusProcessing: '처리 중',
statusSuccess: '가져오기 성공',
statusFailed: '가져오기 실패',
statusUnknown: '알 수 없는 상태',
taskList: '작업 목록',
taskListTitle: '가져오기 작업 목록',
action: '작업',
select: '선택',
fetchTaskListFailed: '작업 목록 가져오기 실패',
noTasks: '가져오기 작업이 없습니다',
clearTasks: '작업 지우기',
clearTasksConfirmTitle: '지우기 확인',
clearTasksConfirmContent: '모든 가져오기 작업 기록을 지우시겠습니까? 이 작업은 되돌릴 수 없습니다.',
confirm: '확인',
cancel: '취소',
clearTasksSuccess: '작업 목록이 지워졌습니다',
clearTasksFailed: '작업 목록 지우기 실패'
}
},
settings: '설정',
user: '사용자',
toplist: '순위',
history: '수집 기록',
list: '플레이리스트',
mv: 'MV',
home: '홈',
search: '검색'
};

View File

@@ -0,0 +1,8 @@
export default {
description: '귀하의 기부는 서버 유지보수, 도메인 갱신 등을 포함한 개발 및 유지보수 작업을 지원하는 데 사용됩니다.',
message: '메시지를 남길 때 이메일이나 GitHub 이름을 남겨주세요.',
refresh: '목록 새로고침',
toDonateList: '커피 한 잔 사주세요',
noMessage: '메시지가 없습니다',
title: '기부 목록'
};

View File

@@ -0,0 +1,87 @@
export default {
title: '다운로드 관리',
localMusic: '로컬 음악',
count: '총 {count}곡',
clearAll: '기록 지우기',
settings: '설정',
tabs: {
downloading: '다운로드 중',
downloaded: '다운로드 완료'
},
empty: {
noTasks: '다운로드 작업이 없습니다',
noDownloaded: '다운로드된 곡이 없습니다'
},
progress: {
total: '전체 진행률: {progress}%'
},
status: {
downloading: '다운로드 중',
completed: '완료',
failed: '실패',
unknown: '알 수 없음'
},
artist: {
unknown: '알 수 없는 가수'
},
delete: {
title: '삭제 확인',
message: '곡 "{filename}"을(를) 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',
confirm: '삭제 확인',
cancel: '취소',
success: '삭제 성공',
failed: '삭제 실패',
fileNotFound: '파일이 존재하지 않거나 이동되었습니다. 기록에서 제거되었습니다',
recordRemoved: '파일 삭제 실패, 하지만 기록에서 제거되었습니다'
},
clear: {
title: '다운로드 기록 지우기',
message: '모든 다운로드 기록을 지우시겠습니까? 이 작업은 다운로드된 음악 파일을 삭제하지 않지만 모든 기록을 지웁니다.',
confirm: '지우기 확인',
cancel: '취소',
success: '다운로드 기록이 지워졌습니다'
},
message: {
downloadComplete: '{filename} 다운로드 완료',
downloadFailed: '{filename} 다운로드 실패: {error}'
},
loading: '로딩 중...',
playStarted: '재생 시작: {name}',
playFailed: '재생 실패: {name}',
path: {
copied: '경로가 클립보드에 복사됨',
copyFailed: '경로 복사 실패'
},
settingsPanel: {
title: '다운로드 설정',
path: '다운로드 위치',
pathDesc: '음악 파일 다운로드 저장 위치 설정',
pathPlaceholder: '다운로드 경로를 선택해주세요',
noPathSelected: '먼저 다운로드 경로를 선택해주세요',
select: '폴더 선택',
open: '폴더 열기',
fileFormat: '파일명 형식',
fileFormatDesc: '음악 다운로드 시 파일 이름 형식 설정',
customFormat: '사용자 정의 형식',
separator: '구분자',
separators: {
dash: '공백-공백',
underscore: '밑줄',
space: '공백'
},
dragToArrange: '드래그하여 정렬하거나 화살표 버튼을 사용하여 순서 조정:',
formatVariables: '사용 가능한 변수',
preview: '미리보기 효과:',
saveSuccess: '다운로드 설정이 저장됨',
presets: {
songArtist: '곡명 - 가수명',
artistSong: '가수명 - 곡명',
songOnly: '곡명만'
},
components: {
songName: '곡명',
artistName: '가수명',
albumName: '앨범명'
}
}
};

View File

@@ -0,0 +1,13 @@
export default {
title: '내 수집',
count: '총 {count}곡',
batchDownload: '일괄 다운로드',
download: '다운로드 ({count})',
emptyTip: '아직 수집한 곡이 없습니다',
downloadSuccess: '다운로드 완료',
downloadFailed: '다운로드 실패',
downloading: '다운로드 중입니다. 잠시만 기다려주세요...',
selectSongsFirst: '먼저 다운로드할 곡을 선택해주세요',
descending: '내림차순',
ascending: '오름차순'
};

View File

@@ -0,0 +1,5 @@
export default {
title: '재생 기록',
playCount: '{count}',
getHistoryFailed: '기록 가져오기 실패'
};

View File

@@ -0,0 +1,48 @@
export default {
title: {
qr: 'QR코드 로그인',
phone: '휴대폰 번호 로그인',
cookie: 'Cookie 로그인',
uid: 'UID 로그인'
},
qrTip: '넷이즈 클라우드 뮤직 앱으로 QR코드를 스캔하여 로그인',
phoneTip: '넷이즈 클라우드 계정으로 로그인',
tokenTip: '유효한 넷이즈 클라우드 뮤직 Cookie을 입력하여 로그인',
uidTip: '사용자 ID를 입력하여 빠른 로그인',
placeholder: {
phone: '휴대폰 번호',
password: '비밀번호',
cookie: '넷이즈 클라우드 뮤직 Cookie(token)을 입력하세요',
uid: '사용자 ID(UID)를 입력하세요'
},
button: {
login: '로그인',
switchToQr: 'QR코드 로그인',
switchToPhone: '휴대폰 번호 로그인',
switchToToken: 'Cookie 로그인 사용',
switchToUid: 'UID 로그인',
backToQr: 'QR코드 로그인으로 돌아가기',
cookieLogin: 'Cookie 로그인',
autoGetCookie: 'Cookie 자동 가져오기',
refresh: '새로고침',
refreshing: '새로고침 중...',
refreshQr: 'QR코드 새로고침'
},
message: {
loginSuccess: '로그인 성공',
tokenLoginSuccess: 'Cookie 로그인 성공',
uidLoginSuccess: 'UID 로그인 성공',
loadError: '로그인 정보 로드 중 오류 발생',
qrCheckError: 'QR코드 상태 확인 중 오류 발생',
tokenRequired: 'Cookie을 입력하세요',
tokenInvalid: 'Cookie이 유효하지 않습니다. 확인 후 다시 시도하세요',
uidRequired: '사용자 ID를 입력하세요',
uidInvalid: '사용자 ID가 유효하지 않거나 사용자가 존재하지 않습니다',
uidLoginFailed: 'UID 로그인에 실패했습니다. 사용자 ID가 올바른지 확인하세요',
autoGetCookieSuccess: 'Cookie 자동 가져오기 성공',
autoGetCookieFailed: 'Cookie 자동 가져오기 실패',
autoGetCookieTip: '넷이즈 클라우드 뮤직 로그인 페이지를 열겠습니다. 로그인 완료 후 창을 닫아주세요'
},
qrTitle: '넷이즈 클라우드 뮤직 QR코드 로그인',
uidWarning: '주의: UID 로그인은 사용자 공개 정보를 확인하는 데만 사용할 수 있으며, 로그인 권한이 필요한 기능에 액세스할 수 없습니다.'
};

View File

@@ -0,0 +1,122 @@
export default {
nowPlaying: '현재 재생 중',
playlist: '재생 목록',
lyrics: '가사',
previous: '이전',
play: '재생',
pause: '일시정지',
next: '다음',
volumeUp: '볼륨 증가',
volumeDown: '볼륨 감소',
mute: '음소거',
unmute: '음소거 해제',
songNum: '총 곡 수: {num}',
addCorrection: '{num}초 앞당기기',
subtractCorrection: '{num}초 지연',
playFailed: '현재 곡 재생 실패, 다음 곡 재생',
playMode: {
sequence: '순차 재생',
loop: '한 곡 반복',
random: '랜덤 재생'
},
fullscreen: {
enter: '전체화면',
exit: '전체화면 종료'
},
close: '닫기',
modeHint: {
single: '한 곡 반복',
list: '자동으로 다음 곡 재생'
},
lrc: {
noLrc: '가사가 없습니다. 음악을 감상해주세요'
},
reparse: {
title: '음원 선택',
desc: '음원을 클릭하여 직접 분석하세요. 다음에 이 곡을 재생할 때 선택한 음원을 사용합니다',
success: '재분석 성공',
failed: '재분석 실패',
warning: '음원을 선택해주세요',
bilibiliNotSupported: 'B站 비디오는 재분석을 지원하지 않습니다',
processing: '분석 중...',
clear: '사용자 정의 음원 지우기'
},
playBar: {
expand: '가사 펼치기',
collapse: '가사 접기',
like: '좋아요',
lyric: '가사',
noSongPlaying: '재생 중인 곡이 없습니다',
eq: '이퀄라이저',
playList: '재생 목록',
reparse: '재분석',
playMode: {
sequence: '순차 재생',
loop: '반복 재생',
random: '랜덤 재생'
},
play: '재생 시작',
pause: '재생 일시정지',
prev: '이전 곡',
next: '다음 곡',
volume: '볼륨',
favorite: '{name} 즐겨찾기 추가됨',
unFavorite: '{name} 즐겨찾기 해제됨',
miniPlayBar: '미니 재생바',
playbackSpeed: '재생 속도',
advancedControls: '고급 설정'
},
eq: {
title: '이퀄라이저',
reset: '재설정',
on: '켜기',
off: '끄기',
bass: '저음',
midrange: '중음',
treble: '고음',
presets: {
flat: '플랫',
pop: '팝',
rock: '록',
classical: '클래식',
jazz: '재즈',
electronic: '일렉트로닉',
hiphop: '힙합',
rb: 'R&B',
metal: '메탈',
vocal: '보컬',
dance: '댄스',
acoustic: '어쿠스틱',
custom: '사용자 정의'
}
},
sleepTimer: {
title: '타이머 종료',
cancel: '타이머 취소',
timeMode: '시간으로 종료',
songsMode: '곡 수로 종료',
playlistEnd: '재생 목록 완료 후 종료',
afterPlaylist: '재생 목록 완료 후 종료',
activeUntilEnd: '목록 끝까지 재생',
minutes: '분',
hours: '시간',
songs: '곡',
set: '설정',
timerSetSuccess: '{minutes}분 후 종료로 설정됨',
songsSetSuccess: '{songs}곡 재생 후 종료로 설정됨',
playlistEndSetSuccess: '재생 목록 완료 후 종료로 설정됨',
timerCancelled: '타이머 종료 취소됨',
timerEnded: '타이머 종료 실행됨',
playbackStopped: '음악 재생이 중지됨',
minutesRemaining: '남은 시간 {minutes}분',
songsRemaining: '남은 곡 수 {count}곡'
},
playList: {
clearAll: '재생 목록 비우기',
alreadyEmpty: '재생 목록이 이미 비어있습니다',
cleared: '재생 목록이 비워졌습니다',
empty: '재생 목록이 비어있습니다',
clearConfirmTitle: '재생 목록 비우기',
clearConfirmContent: '재생 목록의 모든 곡을 삭제하고 현재 재생을 중지합니다. 계속하시겠습니까?'
}
};

View File

@@ -0,0 +1,27 @@
export default {
title: {
hotSearch: '인기 검색',
searchList: '검색 목록',
searchHistory: '검색 기록'
},
button: {
clear: '지우기',
back: '뒤로',
playAll: '재생 목록'
},
loading: {
more: '로딩 중...',
failed: '검색 실패'
},
noMore: '더 이상 없음',
error: {
searchFailed: '검색 실패'
},
search: {
single: '단일곡',
album: '앨범',
playlist: '플레이리스트',
mv: 'MV',
bilibili: 'B站'
}
};

View File

@@ -0,0 +1,322 @@
export default {
theme: '테마',
language: '언어',
regard: '정보',
logout: '로그아웃',
sections: {
basic: '기본 설정',
playback: '재생 설정',
application: '애플리케이션 설정',
network: '네트워크 설정',
system: '시스템 관리',
donation: '후원 지원',
regard: '정보'
},
basic: {
themeMode: '테마 모드',
themeModeDesc: '낮/밤 테마 전환',
autoTheme: '시스템 따라가기',
manualTheme: '수동 전환',
language: '언어 설정',
languageDesc: '표시 언어 전환',
tokenManagement: 'Cookie 관리',
tokenManagementDesc: '넷이즈 클라우드 뮤직 로그인 Cookie 관리',
tokenStatus: '현재 Cookie 상태',
tokenSet: '설정됨',
tokenNotSet: '설정되지 않음',
setToken: 'Cookie 설정',
modifyToken: 'Cookie 수정',
clearToken: 'Cookie 지우기',
font: '폰트 설정',
fontDesc: '폰트 선택, 앞에 있는 폰트를 우선 사용',
fontScope: {
global: '전역',
lyric: '가사만'
},
animation: '애니메이션 속도',
animationDesc: '애니메이션 활성화 여부',
animationSpeed: {
slow: '매우 느림',
normal: '보통',
fast: '매우 빠름'
},
fontPreview: {
title: '폰트 미리보기',
chinese: '中文',
english: 'English',
japanese: '日本語',
korean: '한국어',
chineseText: '静夜思 床前明月光 疑是地上霜',
englishText: 'The quick brown fox jumps over the lazy dog',
japaneseText: 'あいうえお かきくけこ さしすせそ',
koreanText: '가나다라마 바사아자차 카타파하'
}
},
playback: {
quality: '음질 설정',
qualityDesc: '음악 재생 음질 선택 (넷이즈 클라우드 VIP)',
qualityOptions: {
standard: '표준',
higher: '높음',
exhigh: '매우 높음',
lossless: '무손실',
hires: 'Hi-Res',
jyeffect: 'HD 서라운드',
sky: '몰입형 서라운드',
dolby: '돌비 애트모스',
jymaster: '초고화질 마스터'
},
musicSources: '음원 설정',
musicSourcesDesc: '음악 해석에 사용할 음원 플랫폼 선택',
musicSourcesWarning: '최소 하나의 음원 플랫폼을 선택해야 합니다',
musicUnblockEnable: '음악 해석 활성화',
musicUnblockEnableDesc: '활성화하면 재생할 수 없는 음악을 해석하려고 시도합니다',
configureMusicSources: '음원 구성',
selectedMusicSources: '선택된 음원:',
noMusicSources: '음원이 선택되지 않음',
gdmusicInfo: 'GD 뮤직은 여러 플랫폼 음원을 자동으로 해석하고 최적의 결과를 자동 선택합니다',
autoPlay: '자동 재생',
autoPlayDesc: '앱을 다시 열 때 자동으로 재생을 계속할지 여부',
showStatusBar: '상태바 제어 기능 표시 여부',
showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)'
},
application: {
closeAction: '닫기 동작',
closeActionDesc: '창을 닫을 때의 동작 선택',
closeOptions: {
ask: '매번 묻기',
minimize: '트레이로 최소화',
close: '직접 종료'
},
shortcut: '단축키 설정',
shortcutDesc: '전역 단축키 사용자 정의',
download: '다운로드 관리',
downloadDesc: '다운로드 목록 버튼을 항상 표시할지 여부',
unlimitedDownload: '무제한 다운로드',
unlimitedDownloadDesc: '활성화하면 음악을 무제한으로 다운로드합니다 (다운로드 실패가 발생할 수 있음), 기본 제한 300곡',
downloadPath: '다운로드 디렉토리',
downloadPathDesc: '음악 파일의 다운로드 위치 선택',
remoteControl: '원격 제어',
remoteControlDesc: '원격 제어 기능 설정'
},
network: {
apiPort: '음악 API 포트',
apiPortDesc: '수정 후 앱을 재시작해야 합니다',
proxy: '프록시 설정',
proxyDesc: '음악에 액세스할 수 없을 때 프록시를 활성화할 수 있습니다',
proxyHost: '프록시 주소',
proxyHostPlaceholder: '프록시 주소를 입력하세요',
proxyPort: '프록시 포트',
proxyPortPlaceholder: '프록시 포트를 입력하세요',
realIP: 'realIP 설정',
realIPDesc: '제한으로 인해 이 프로젝트는 해외에서 사용할 때 제한을 받을 수 있으며, realIP 매개변수를 사용하여 국내 IP를 전달하여 해결할 수 있습니다',
messages: {
proxySuccess: '프록시 설정이 저장되었습니다. 앱을 재시작한 후 적용됩니다',
proxyError: '입력이 올바른지 확인하세요',
realIPSuccess: '실제 IP 설정이 저장되었습니다',
realIPError: '유효한 IP 주소를 입력하세요'
}
},
system: {
cache: '캐시 관리',
cacheDesc: '캐시 지우기',
cacheClearTitle: '지울 캐시 유형을 선택하세요:',
cacheTypes: {
history: {
label: '재생 기록',
description: '재생한 곡 기록 지우기'
},
favorite: {
label: '즐겨찾기 기록',
description: '로컬 즐겨찾기 곡 기록 지우기 (클라우드 즐겨찾기에는 영향 없음)'
},
user: {
label: '사용자 데이터',
description: '로그인 정보 및 사용자 관련 데이터 지우기'
},
settings: {
label: '앱 설정',
description: '앱의 모든 사용자 정의 설정 지우기'
},
downloads: {
label: '다운로드 기록',
description: '다운로드 기록 지우기 (다운로드된 파일은 삭제되지 않음)'
},
resources: {
label: '음악 리소스',
description: '로드된 음악 파일, 가사 등 리소스 캐시 지우기'
},
lyrics: {
label: '가사 리소스',
description: '로드된 가사 리소스 캐시 지우기'
}
},
restart: '재시작',
restartDesc: '앱 재시작',
messages: {
clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다'
}
},
about: {
version: '버전',
checkUpdate: '업데이트 확인',
checking: '확인 중...',
latest: '현재 최신 버전입니다',
hasUpdate: '새 버전 발견',
gotoUpdate: '업데이트하러 가기',
gotoGithub: 'Github로 이동',
author: '작성자',
authorDesc: 'algerkong 별점🌟 부탁드려요',
messages: {
checkError: '업데이트 확인 실패, 나중에 다시 시도하세요'
}
},
validation: {
selectProxyProtocol: '프록시 프로토콜을 선택하세요',
proxyHost: '프록시 주소를 입력하세요',
portNumber: '유효한 포트 번호를 입력하세요 (1-65535)'
},
lyricSettings: {
title: '가사 설정',
tabs: {
display: '표시',
interface: '인터페이스',
typography: '텍스트',
mobile: '모바일'
},
pureMode: '순수 모드',
hideCover: '커버 숨기기',
centerDisplay: '중앙 표시',
showTranslation: '번역 표시',
hideLyrics: '가사 숨기기',
hidePlayBar: '재생바 숨기기',
hideMiniPlayBar: '미니 재생바 숨기기',
showMiniPlayBar: '미니 재생바 표시',
backgroundTheme: '배경 테마',
themeOptions: {
default: '기본',
light: '밝음',
dark: '어둠'
},
fontSize: '폰트 크기',
fontSizeMarks: {
small: '작음',
medium: '중간',
large: '큼'
},
letterSpacing: '글자 간격',
letterSpacingMarks: {
compact: '좁음',
default: '기본',
loose: '넓음'
},
lineHeight: '줄 높이',
lineHeightMarks: {
compact: '좁음',
default: '기본',
loose: '넓음'
},
mobileLayout: '모바일 레이아웃',
layoutOptions: {
default: '기본',
ios: 'iOS 스타일',
android: '안드로이드 스타일'
},
mobileCoverStyle: '커버 스타일',
coverOptions: {
record: '레코드',
square: '정사각형',
full: '전체화면'
},
lyricLines: '가사 줄 수',
mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다'
},
themeColor: {
title: '가사 테마 색상',
presetColors: '미리 설정된 색상',
customColor: '사용자 정의 색상',
preview: '미리보기 효과',
previewText: '가사 효과',
colorNames: {
'spotify-green': 'Spotify 그린',
'apple-blue': '애플 블루',
'youtube-red': 'YouTube 레드',
orange: '활력 오렌지',
purple: '신비 퍼플',
pink: '벚꽃 핑크'
},
tooltips: {
openColorPicker: '색상 선택기 열기',
closeColorPicker: '색상 선택기 닫기'
},
placeholder: '#1db954'
},
shortcutSettings: {
title: '단축키 설정',
shortcut: '단축키',
shortcutDesc: '단축키 사용자 정의',
shortcutConflict: '단축키 충돌',
inputPlaceholder: '클릭하여 단축키 입력',
resetShortcuts: '기본값 복원',
disableAll: '모두 비활성화',
enableAll: '모두 활성화',
togglePlay: '재생/일시정지',
prevPlay: '이전 곡',
nextPlay: '다음 곡',
volumeUp: '볼륨 증가',
volumeDown: '볼륨 감소',
toggleFavorite: '즐겨찾기/즐겨찾기 취소',
toggleWindow: '창 표시/숨기기',
scopeGlobal: '전역',
scopeApp: '앱 내',
enabled: '활성화',
disabled: '비활성화',
messages: {
resetSuccess: '기본 단축키로 복원되었습니다. 저장을 잊지 마세요',
conflict: '충돌하는 단축키가 있습니다. 다시 설정하세요',
saveSuccess: '단축키 설정이 저장되었습니다',
saveError: '단축키 저장 실패, 다시 시도하세요',
cancelEdit: '수정이 취소되었습니다',
disableAll: '모든 단축키가 비활성화되었습니다. 저장을 잊지 마세요',
enableAll: '모든 단축키가 활성화되었습니다. 저장을 잊지 마세요'
}
},
remoteControl: {
title: '원격 제어',
enable: '원격 제어 활성화',
port: '서비스 포트',
allowedIps: '허용된 IP 주소',
addIp: 'IP 추가',
emptyListHint: '빈 목록은 모든 IP 액세스를 허용함을 의미합니다',
saveSuccess: '원격 제어 설정이 저장되었습니다',
accessInfo: '원격 제어 액세스 주소:'
},
cookie: {
title: 'Cookie 설정',
description: '넷이즈 클라우드 뮤직의 Cookie를 입력하세요:',
placeholder: '완전한 Cookie를 붙여넣으세요...',
help: {
format: 'Cookie는 일반적으로 "MUSIC_U="로 시작합니다',
source: '브라우저 개발자 도구의 네트워크 요청에서 얻을 수 있습니다',
storage: 'Cookie 설정 후 자동으로 로컬 저장소에 저장됩니다'
},
action: {
save: 'Cookie 저장',
paste: '붙여넣기',
clear: '지우기'
},
validation: {
required: 'Cookie를 입력하세요',
format: 'Cookie 형식이 올바르지 않을 수 있습니다. MUSIC_U가 포함되어 있는지 확인하세요'
},
message: {
saveSuccess: 'Cookie 저장 성공',
saveError: 'Cookie 저장 실패',
pasteSuccess: '붙여넣기 성공',
pasteError: '붙여넣기 실패, 수동으로 복사하세요'
},
info: {
length: '현재 길이: {length} 문자'
}
}
};

View File

@@ -0,0 +1,28 @@
export default {
menu: {
play: '재생',
playNext: '다음에 재생',
download: '곡 다운로드',
addToPlaylist: '플레이리스트에 추가',
favorite: '좋아요',
unfavorite: '좋아요 취소',
removeFromPlaylist: '플레이리스트에서 삭제',
dislike: '싫어요',
undislike: '싫어요 취소'
},
message: {
downloading: '다운로드 중입니다. 잠시 기다려주세요...',
downloadFailed: '다운로드 실패',
downloadQueued: '다운로드 대기열에 추가됨',
addedToNextPlay: '다음 재생에 추가됨',
getUrlFailed: '음악 다운로드 주소 가져오기 실패, 로그인 상태를 확인하세요'
},
dialog: {
dislike: {
title: '알림!',
content: '이 곡을 싫어한다고 확인하시겠습니까? 다시 들어가면 일일 추천에서 제외됩니다.',
positiveText: '싫어요',
negativeText: '취소'
}
}
};

View File

@@ -0,0 +1,48 @@
export default {
profile: {
followers: '팔로워',
following: '팔로잉',
level: '레벨'
},
playlist: {
created: '생성한 플레이리스트',
mine: '내가 만든',
trackCount: '{count}곡',
playCount: '{count}회 재생'
},
ranking: {
title: '음악 청취 순위',
playCount: '{count}회'
},
follow: {
title: '팔로잉 목록',
viewPlaylist: '플레이리스트 보기',
noFollowings: '팔로잉이 없습니다',
loadMore: '더 보기',
noSignature: '이 사람은 게을러서 아무것도 남기지 않았습니다',
userFollowsTitle: '의 팔로잉',
myFollowsTitle: '내 팔로잉'
},
follower: {
title: '팔로워 목록',
noFollowers: '팔로워가 없습니다',
loadMore: '더 보기',
userFollowersTitle: '의 팔로워',
myFollowersTitle: '내 팔로워'
},
detail: {
playlists: '플레이리스트',
records: '음악 청취 순위',
noPlaylists: '플레이리스트가 없습니다',
noRecords: '음악 청취 기록이 없습니다',
artist: '아티스트',
noSignature: '이 사람은 게을러서 아무것도 남기지 않았습니다',
invalidUserId: '사용자 ID가 유효하지 않습니다',
noRecordPermission: '{name}님이 음악 청취 순위를 보지 못하게 했습니다'
},
message: {
loadFailed: '사용자 페이지 로드 실패',
deleteSuccess: '삭제 성공',
deleteFailed: '삭제 실패'
}
};

View File

@@ -27,6 +27,7 @@ export default {
refresh: '刷新',
retry: '重试',
reset: '重置',
back: '返回',
copySuccess: '已复制到剪贴板',
copyFailed: '复制失败',
validation: {

View File

@@ -58,7 +58,7 @@ export default {
wechatQR: '微信收款码',
coffeeDesc: '一杯咖啡,一份支持',
coffeeDescLinkText: '查看更多',
qqGroup: 'QQ频道algermusic',
groupText: '微信公众号AlgerMusic',
messages: {
copySuccess: '已复制到剪贴板'
},
@@ -88,7 +88,11 @@ export default {
restart: '重启',
refresh: '刷新',
currentVersion: '当前版本',
searchPlaceholder: '搜索点什么吧...'
searchPlaceholder: '搜索点什么吧...',
zoom: '页面缩放',
zoom100: '标准缩放100%',
resetZoom: '点击重置缩放',
zoomDefault: '标准缩放'
},
titleBar: {
closeTitle: '请选择关闭方式',
@@ -114,5 +118,73 @@ export default {
addToPlaylist: '添加到播放列表',
addToPlaylistSuccess: '添加到播放列表成功',
songsAlreadyInPlaylist: '歌曲已存在于播放列表中'
}
},
playlist: {
import: {
button: '歌单导入',
title: '歌单导入',
description: '支持通过元数据/文字/链接三种方式导入歌单',
linkTab: '链接导入',
textTab: '文字导入',
localTab: '元数据导入',
linkPlaceholder: '请输入歌单链接,每行一个',
textPlaceholder: '请输入歌曲信息,格式为:歌曲名 歌手名',
localPlaceholder: '请输入JSON格式的歌曲元数据',
linkTips: '支持的链接来源:',
linkTip1: '将歌单分享到微信/微博/QQ后复制链接',
linkTip2: '直接复制歌单/个人主页链接',
linkTip3: '直接复制文章链接',
textTips: '请输入歌曲信息,每行一首歌',
textFormat: '格式:歌曲名 歌手名',
localTips: '请添加歌曲元数据',
localFormat: '格式示例:',
songNamePlaceholder: '歌曲名称',
artistNamePlaceholder: '艺术家名称',
albumNamePlaceholder: '专辑名称',
addSongButton: '添加歌曲',
addLinkButton: '添加链接',
importToStarPlaylist: '导入到我喜欢的音乐',
playlistNamePlaceholder: '请输入歌单名称',
importButton: '开始导入',
emptyLinkWarning: '请输入歌单链接',
emptyTextWarning: '请输入歌曲信息',
emptyLocalWarning: '请输入歌曲元数据',
invalidJsonFormat: 'JSON格式不正确',
importSuccess: '导入任务创建成功',
importFailed: '导入失败',
importStatus: '导入状态',
refresh: '刷新',
taskId: '任务ID',
status: '状态',
successCount: '成功数量',
failReason: '失败原因',
unknownError: '未知错误',
statusPending: '等待处理',
statusProcessing: '处理中',
statusSuccess: '导入成功',
statusFailed: '导入失败',
statusUnknown: '未知状态',
taskList: '任务列表',
taskListTitle: '导入任务列表',
action: '操作',
select: '选择',
fetchTaskListFailed: '获取任务列表失败',
noTasks: '暂无导入任务',
clearTasks: '清除任务',
clearTasksConfirmTitle: '确认清除',
clearTasksConfirmContent: '确定要清除所有导入任务记录吗?此操作不可恢复。',
confirm: '确认',
cancel: '取消',
clearTasksSuccess: '任务列表已清除',
clearTasksFailed: '清除任务列表失败'
}
},
settings: '设置',
user: '用户',
toplist: '排行榜',
history: '收藏历史',
list: '歌单',
mv: 'MV',
home: '首页',
search: '搜索'
};

View File

@@ -3,6 +3,7 @@ export default {
localMusic: '本地音乐',
count: '共 {count} 首歌曲',
clearAll: '清空记录',
settings: '设置',
tabs: {
downloading: '下载中',
downloaded: '已下载'
@@ -44,5 +45,43 @@ export default {
downloadComplete: '{filename} 下载完成',
downloadFailed: '{filename} 下载失败: {error}'
},
loading: '加载中...'
loading: '加载中...',
playStarted: '开始播放: {name}',
playFailed: '播放失败: {name}',
path: {
copied: '路径已复制到剪贴板',
copyFailed: '复制路径失败'
},
settingsPanel: {
title: '下载设置',
path: '下载位置',
pathDesc: '设置音乐文件下载保存的位置',
pathPlaceholder: '请选择下载路径',
noPathSelected: '请先选择下载路径',
select: '选择文件夹',
open: '打开文件夹',
fileFormat: '文件名格式',
fileFormatDesc: '设置下载音乐时的文件命名格式',
customFormat: '自定义格式',
separator: '分隔符',
separators: {
dash: '空格-空格',
underscore: '下划线',
space: '空格'
},
dragToArrange: '拖动排序或使用箭头按钮调整顺序:',
formatVariables: '可用变量',
preview: '预览效果:',
saveSuccess: '下载设置已保存',
presets: {
songArtist: '歌曲名 - 歌手名',
artistSong: '歌手名 - 歌曲名',
songOnly: '仅歌曲名'
},
components: {
songName: '歌曲名',
artistName: '歌手名',
albumName: '专辑名'
}
}
};

View File

@@ -1,29 +0,0 @@
import artist from './artist';
import common from './common';
import comp from './comp';
import donation from './donation';
import download from './download';
import favorite from './favorite';
import history from './history';
import login from './login';
import player from './player';
import search from './search';
import settings from './settings';
import songItem from './songItem';
import user from './user';
export default {
common,
donation,
favorite,
history,
login,
player,
search,
settings,
songItem,
user,
download,
comp,
artist
};

View File

@@ -1,22 +1,62 @@
export default {
title: {
qr: '扫码登录',
phone: '手机号登录'
phone: '手机号登录',
cookie: 'Cookie登录',
uid: 'UID登录'
},
qrTip: '使用网易云APP扫码登录',
phoneTip: '使用网易云账号登录',
tokenTip: '输入有效的网易云音乐Cookie即可登录',
uidTip: '输入用户ID快速登录',
placeholder: {
phone: '手机号',
password: '密码'
password: '密码',
cookie: '请输入网易云音乐Cookietoken',
uid: '请输入用户IDUID'
},
button: {
login: '登录',
switchToQr: '扫码登录',
switchToPhone: '手机号登录'
switchToPhone: '手机号登录',
switchToToken: '使用Cookie登录',
switchToUid: 'UID登录',
backToQr: '返回二维码登录',
cookieLogin: 'Cookie登录',
autoGetCookie: '自动获取Cookie',
refresh: '点击刷新',
refreshing: '刷新中...',
refreshQr: '刷新二维码'
},
message: {
loginSuccess: '登录成功',
loginFailed: '登录失败',
tokenLoginSuccess: 'Cookie登录成功',
uidLoginSuccess: 'UID登录成功',
loadError: '加载登录信息时出错',
qrCheckError: '检查二维码状态时出错'
}
qrCheckError: '检查二维码状态时出错',
tokenRequired: '请输入Cookie',
tokenInvalid: 'Cookie无效请检查后重试',
uidRequired: '请输入用户ID',
uidInvalid: '用户ID无效或用户不存在',
uidLoginFailed: 'UID登录失败请检查用户ID是否正确',
phoneRequired: '请输入手机号',
passwordRequired: '请输入密码',
phoneLoginFailed: '手机号登录失败,请检查手机号和密码是否正确',
autoGetCookieSuccess: '自动获取Cookie成功',
autoGetCookieFailed: '自动获取Cookie失败',
autoGetCookieTip: '将打开网易云音乐登录页面,请完成登录后关闭窗口',
qrCheckFailed: '检查二维码状态失败,请刷新重试',
qrLoading: '正在加载二维码...',
qrExpired: '二维码已过期,请点击刷新',
qrExpiredShort: '二维码已过期',
qrExpiredWarning: '二维码已过期,请点击刷新获取新的二维码',
qrScanned: '已扫码,请在手机上确认登录',
qrScannedShort: '已扫码',
qrScannedInfo: '已扫码,请在手机上确认登录',
qrConfirmed: '登录成功,正在跳转...',
qrGenerating: '正在生成二维码...'
},
qrTitle: '扫码登录网易云音乐',
uidWarning: '注意UID登录仅用于查看用户公开信息无法访问需要登录权限的功能'
};

View File

@@ -11,10 +11,12 @@ export default {
mute: '静音',
unmute: '取消静音',
songNum: '歌曲总数:{num}',
addCorrection: '提前 {num} 秒',
subtractCorrection: '延迟 {num} 秒',
playFailed: '当前歌曲播放失败,播放下一首',
playMode: {
sequence: '顺序播放',
loop: '循环播放',
loop: '单曲循环',
random: '随机播放'
},
fullscreen: {
@@ -36,7 +38,8 @@ export default {
failed: '重新解析失败',
warning: '请选择一个音源',
bilibiliNotSupported: 'B站视频不支持重新解析',
processing: '解析中...'
processing: '解析中...',
clear: '清除自定义音源'
},
playBar: {
expand: '展开歌词',
@@ -59,7 +62,9 @@ export default {
volume: '音量',
favorite: '已收藏{name}',
unFavorite: '已取消收藏{name}',
miniPlayBar: '迷你播放栏'
miniPlayBar: '迷你播放栏',
playbackSpeed: '播放速度',
advancedControls: '更多设置s'
},
eq: {
title: '均衡器',

View File

@@ -16,5 +16,12 @@ export default {
noMore: '没有更多了',
error: {
searchFailed: '搜索失败'
},
search: {
single: '单曲',
album: '专辑',
playlist: '歌单',
mv: 'MV',
bilibili: 'B站'
}
};

View File

@@ -15,8 +15,18 @@ export default {
basic: {
themeMode: '主题模式',
themeModeDesc: '切换日间/夜间主题',
autoTheme: '跟随系统',
manualTheme: '手动切换',
language: '语言设置',
languageDesc: '切换显示语言',
tokenManagement: 'Cookie管理',
tokenManagementDesc: '管理网易云音乐登录Cookie',
tokenStatus: '当前Cookie状态',
tokenSet: '已设置',
tokenNotSet: '未设置',
setToken: '设置Cookie',
modifyToken: '修改Cookie',
clearToken: '清除Cookie',
font: '字体设置',
fontDesc: '选择字体,优先使用排在前面的字体',
fontScope: {
@@ -68,7 +78,7 @@ export default {
autoPlay: '自动播放',
autoPlayDesc: '重新打开应用时是否自动继续播放',
showStatusBar: '是否显示状态栏控制功能',
showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)',
showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)'
},
application: {
closeAction: '关闭行为',
@@ -167,19 +177,34 @@ export default {
portNumber: '请输入有效的端口号(1-65535)'
},
lyricSettings: {
title: '页面设置',
title: '歌词设置',
tabs: {
display: '显示',
interface: '界面',
typography: '文字',
mobile: '移动端'
},
pureMode: '纯净模式',
hideCover: '隐藏封面',
centerDisplay: '居中显示',
showTranslation: '显示翻译',
hideLyrics: '隐藏歌词',
hidePlayBar: '隐藏播放栏',
hideMiniPlayBar: '隐藏迷你播放栏',
showMiniPlayBar: '显示迷你播放栏',
backgroundTheme: '背景主题',
themeOptions: {
default: '默认',
light: '亮色',
dark: '暗色'
},
fontSize: '字体大小',
fontSizeMarks: {
small: '小',
medium: '中',
large: '大'
},
letterSpacing: '字间距',
letterSpacing: '字间距',
letterSpacingMarks: {
compact: '紧凑',
default: '默认',
@@ -191,19 +216,40 @@ export default {
default: '默认',
loose: '宽松'
},
backgroundTheme: '背景主题',
themeOptions: {
mobileLayout: '移动端布局',
layoutOptions: {
default: '默认',
light: '亮色',
dark: '暗色'
ios: 'iOS风格',
android: '安卓风格'
},
hideMiniPlayBar: '隐藏迷你播放栏',
hideLyrics: '隐藏歌词',
tabs: {
interface: '界面',
typography: '文字',
display: '显示'
}
mobileCoverStyle: '封面样式',
coverOptions: {
record: '唱片',
square: '方形',
full: '全屏'
},
lyricLines: '歌词行数',
mobileUnavailable: '此设置仅在移动端可用'
},
themeColor: {
title: '歌词主题色',
presetColors: '预设颜色',
customColor: '自定义颜色',
preview: '预览效果',
previewText: '歌词效果',
colorNames: {
'spotify-green': 'Spotify 绿',
'apple-blue': '苹果蓝',
'youtube-red': 'YouTube 红',
orange: '活力橙',
purple: '神秘紫',
pink: '樱花粉'
},
tooltips: {
openColorPicker: '打开色板',
closeColorPicker: '关闭色板'
},
placeholder: '#1db954'
},
shortcutSettings: {
title: '快捷键设置',
@@ -243,6 +289,34 @@ export default {
addIp: '添加IP',
emptyListHint: '空列表表示允许所有IP访问',
saveSuccess: '远程控制设置已保存',
accessInfo: '远程控制访问地址:',
accessInfo: '远程控制访问地址:'
},
cookie: {
title: 'Cookie设置',
description: '请输入网易云音乐的Cookie',
placeholder: '请粘贴完整的Cookie...',
help: {
format: 'Cookie通常以 "MUSIC_U=" 开头',
source: '可以从浏览器开发者工具的网络请求中获取',
storage: 'Cookie设置后将自动保存到本地存储'
},
action: {
save: '保存Cookie',
paste: '粘贴',
clear: '清空'
},
validation: {
required: '请输入Cookie',
format: 'Cookie格式可能不正确请检查是否包含MUSIC_U'
},
message: {
saveSuccess: 'Cookie保存成功',
saveError: 'Cookie保存失败',
pasteSuccess: '粘贴成功',
pasteError: '粘贴失败,请手动复制'
},
info: {
length: '当前长度:{length} 字符'
}
}
};

View File

@@ -6,7 +6,9 @@ export default {
addToPlaylist: '添加到歌单',
favorite: '喜欢',
unfavorite: '取消喜欢',
removeFromPlaylist: '从歌单中删除'
removeFromPlaylist: '从歌单中删除',
dislike: '不喜欢',
undislike: '取消不喜欢'
},
message: {
downloading: '正在下载中,请稍候...',
@@ -14,5 +16,13 @@ export default {
downloadQueued: '已加入下载队列',
addedToNextPlay: '已添加到下一首播放',
getUrlFailed: '获取音乐下载地址失败,请检查是否登录'
},
dialog: {
dislike: {
title: '提示!',
content: '确认不喜欢这首歌吗?再次进入将从每日推荐中排除。',
positiveText: '不喜欢',
negativeText: '取消'
}
}
};

View File

@@ -6,6 +6,7 @@ export default {
},
playlist: {
created: '创建的歌单',
mine: '我创建的',
trackCount: '{count}首',
playCount: '播放{count}次'
},
@@ -18,12 +19,16 @@ export default {
viewPlaylist: '查看歌单',
noFollowings: '暂无关注',
loadMore: '加载更多',
noSignature: '这个家伙很懒,什么都没留下'
noSignature: '这个家伙很懒,什么都没留下',
userFollowsTitle: '的关注',
myFollowsTitle: '我的关注'
},
follower: {
title: '粉丝列表',
noFollowers: '暂无粉丝',
loadMore: '加载更多'
loadMore: '加载更多',
userFollowersTitle: '的粉丝',
myFollowersTitle: '我的粉丝'
},
detail: {
playlists: '歌单',
@@ -32,7 +37,8 @@ export default {
noRecords: '暂无听歌记录',
artist: '歌手',
noSignature: '这个人很懒,什么都没留下',
invalidUserId: '用户ID无效'
invalidUserId: '用户ID无效',
noRecordPermission: '{name}不让你看听歌排行'
},
message: {
loadFailed: '加载用户页面失败',

View File

@@ -0,0 +1,5 @@
export default {
hotSongs: '熱門歌曲',
albums: '專輯',
description: '藝人介紹'
};

View File

@@ -0,0 +1,56 @@
export default {
play: '播放',
next: '下一首',
previous: '上一首',
volume: '音量',
settings: '設定',
search: '搜尋',
loading: '載入中...',
loadingMore: '載入更多...',
alipay: '支付寶',
wechat: '微信支付',
on: '開啟',
off: '關閉',
show: '顯示',
hide: '隱藏',
confirm: '確認',
cancel: '取消',
configure: '設定',
open: '開啟',
modify: '修改',
success: '操作成功',
error: '操作失敗',
warning: '警告',
info: '提示',
save: '儲存',
delete: '刪除',
refresh: '重新整理',
retry: '重試',
reset: '重設',
back: '返回',
copySuccess: '已複製到剪貼簿',
copyFailed: '複製失敗',
validation: {
required: '此項為必填',
invalidInput: '輸入無效',
selectRequired: '請選擇一個選項',
numberRange: '請輸入 {min} 到 {max} 之間的數字'
},
viewMore: '查看更多',
noMore: '沒有更多了',
selectAll: '全選',
expand: '展開',
collapse: '收合',
songCount: '{count}首',
language: '語言',
tray: {
show: '顯示',
quit: '退出',
playPause: '播放/暫停',
prev: '上一首',
next: '下一首',
pause: '暫停',
play: '播放',
favorite: '收藏'
}
};

View File

@@ -0,0 +1,190 @@
export default {
installApp: {
description: '安裝應用程式,獲得更好的體驗',
noPrompt: '不再提示',
install: '立即安裝',
cancel: '暫不安裝',
download: '下載',
downloadFailed: '下載失敗',
downloadComplete: '下載完成',
downloadProblem: '下載遇到問題?去',
downloadProblemLinkText: '下載最新版本'
},
playlistDrawer: {
title: '新增至播放清單',
createPlaylist: '建立新播放清單',
cancelCreate: '取消建立',
create: '建立',
playlistName: '播放清單名稱',
privatePlaylist: '私人播放清單',
publicPlaylist: '公開播放清單',
createSuccess: '播放清單建立成功',
createFailed: '播放清單建立失敗',
addSuccess: '歌曲新增成功',
addFailed: '歌曲新增失敗',
private: '私人',
public: '公開',
count: '首歌曲',
loginFirst: '請先登入',
getPlaylistFailed: '取得播放清單失敗',
inputPlaylistName: '請輸入播放清單名稱'
},
update: {
title: '發現新版本',
currentVersion: '目前版本',
cancel: '暫不更新',
prepareDownload: '準備下載...',
downloading: '下載中...',
nowUpdate: '立即更新',
downloadFailed: '下載失敗,請重試或手動下載',
startFailed: '啟動下載失敗,請重試或手動下載',
noDownloadUrl: '未找到適合目前系統的安裝包,請手動下載',
installConfirmTitle: '安裝更新',
installConfirmContent: '是否關閉應用程式並安裝更新?',
manualInstallTip: '如果關閉應用程式後沒有正常彈出安裝程式,請至下載資料夾尋找檔案並手動開啟。',
yesInstall: '立即安裝',
noThanks: '稍後安裝',
fileLocation: '檔案位置',
copy: '複製路徑',
copySuccess: '路徑已複製到剪貼簿',
copyFailed: '複製失敗',
backgroundDownload: '背景下載'
},
coffee: {
title: '請我喝杯咖啡',
alipay: '支付寶',
wechat: '微信支付',
alipayQR: '支付寶收款碼',
wechatQR: '微信收款碼',
coffeeDesc: '一杯咖啡,一份支持',
coffeeDescLinkText: '查看更多',
groupText: '微信公众号AlgerMusic',
messages: {
copySuccess: '已複製到剪貼簿'
},
donateList: '請我喝杯咖啡'
},
playlistType: {
title: '播放清單分類',
showAll: '顯示全部',
hide: '隱藏部分'
},
recommendAlbum: {
title: '最新專輯'
},
recommendSinger: {
title: '每日推薦',
songlist: '每日推薦清單'
},
recommendSonglist: {
title: '本週最熱音樂'
},
searchBar: {
login: '登入',
toLogin: '去登入',
logout: '登出',
set: '設定',
theme: '主題',
restart: '重新啟動',
refresh: '重新整理',
currentVersion: '目前版本',
searchPlaceholder: '搜尋點什麼吧...',
zoom: '頁面縮放',
zoom100: '標準縮放100%',
resetZoom: '點擊重設縮放',
zoomDefault: '標準縮放'
},
titleBar: {
closeTitle: '請選擇關閉方式',
minimizeToTray: '最小化到系統匣',
exitApp: '退出應用程式',
rememberChoice: '記住我的選擇',
closeApp: '關閉應用程式'
},
userPlayList: {
title: '{name}的常聽'
},
musicList: {
searchSongs: '搜尋歌曲',
noSearchResults: '沒有找到相關歌曲',
switchToNormal: '切換到預設版面',
switchToCompact: '切換到緊湊版面',
playAll: '播放全部',
collect: '收藏',
collectSuccess: '收藏成功',
cancelCollectSuccess: '取消收藏成功',
operationFailed: '操作失敗',
cancelCollect: '取消收藏',
addToPlaylist: '新增至播放清單',
addToPlaylistSuccess: '新增至播放清單成功',
songsAlreadyInPlaylist: '歌曲已存在於播放清單中'
},
playlist: {
import: {
button: '播放清單匯入',
title: '播放清單匯入',
description: '支援透過元資料/文字/連結三種方式匯入播放清單',
linkTab: '連結匯入',
textTab: '文字匯入',
localTab: '元資料匯入',
linkPlaceholder: '請輸入播放清單連結,每行一個',
textPlaceholder: '請輸入歌曲資訊,格式為:歌曲名 歌手名',
localPlaceholder: '請輸入JSON格式的歌曲元資料',
linkTips: '支援的連結來源:',
linkTip1: '將播放清單分享到微信/微博/QQ後複製連結',
linkTip2: '直接複製播放清單/個人主頁連結',
linkTip3: '直接複製文章連結',
textTips: '請輸入歌曲資訊,每行一首歌',
textFormat: '格式:歌曲名 歌手名',
localTips: '請新增歌曲元資料',
localFormat: '格式範例:',
songNamePlaceholder: '歌曲名稱',
artistNamePlaceholder: '藝人名稱',
albumNamePlaceholder: '專輯名稱',
addSongButton: '新增歌曲',
addLinkButton: '新增連結',
importToStarPlaylist: '匯入到我喜歡的音樂',
playlistNamePlaceholder: '請輸入播放清單名稱',
importButton: '開始匯入',
emptyLinkWarning: '請輸入播放清單連結',
emptyTextWarning: '請輸入歌曲資訊',
emptyLocalWarning: '請輸入歌曲元資料',
invalidJsonFormat: 'JSON格式不正確',
importSuccess: '匯入任務建立成功',
importFailed: '匯入失敗',
importStatus: '匯入狀態',
refresh: '重新整理',
taskId: '任務ID',
status: '狀態',
successCount: '成功數量',
failReason: '失敗原因',
unknownError: '未知錯誤',
statusPending: '等待處理',
statusProcessing: '處理中',
statusSuccess: '匯入成功',
statusFailed: '匯入失敗',
statusUnknown: '未知狀態',
taskList: '任務清單',
taskListTitle: '匯入任務清單',
action: '操作',
select: '選擇',
fetchTaskListFailed: '取得任務清單失敗',
noTasks: '暫無匯入任務',
clearTasks: '清除任務',
clearTasksConfirmTitle: '確認清除',
clearTasksConfirmContent: '確定要清除所有匯入任務記錄嗎?此操作不可恢復。',
confirm: '確認',
cancel: '取消',
clearTasksSuccess: '任務清單已清除',
clearTasksFailed: '清除任務清單失敗'
}
},
settings: '設定',
user: '使用者',
toplist: '排行榜',
history: '收藏歷史',
list: '播放清單',
mv: 'MV',
home: '首頁',
search: '搜尋'
};

View File

@@ -0,0 +1,8 @@
export default {
description: '您的捐贈將用於支持開發和維護工作,包括但不限於伺服器維護、域名續費等。',
message: '留言時可留下您的電子郵件或 github 名稱。',
refresh: '重新整理列表',
toDonateList: '請我喝杯咖啡',
noMessage: '暫無留言',
title: '捐贈列表'
};

View File

@@ -0,0 +1,87 @@
export default {
title: '下載管理',
localMusic: '本機音樂',
count: '共 {count} 首歌曲',
clearAll: '清空記錄',
settings: '設定',
tabs: {
downloading: '下載中',
downloaded: '已下載'
},
empty: {
noTasks: '暫無下載任務',
noDownloaded: '暫無已下載歌曲'
},
progress: {
total: '總進度: {progress}%'
},
status: {
downloading: '下載中',
completed: '已完成',
failed: '失敗',
unknown: '未知'
},
artist: {
unknown: '未知歌手'
},
delete: {
title: '刪除確認',
message: '確定要刪除歌曲 "{filename}" 嗎?此操作不可恢復。',
confirm: '確定刪除',
cancel: '取消',
success: '刪除成功',
failed: '刪除失敗',
fileNotFound: '檔案不存在或已被移動,已從記錄中移除',
recordRemoved: '檔案刪除失敗,但已從記錄中移除'
},
clear: {
title: '清空下載記錄',
message: '確定要清空所有下載記錄嗎?此操作不會刪除已下載的音樂檔案,但將清空所有記錄。',
confirm: '確定清空',
cancel: '取消',
success: '下載記錄已清空'
},
message: {
downloadComplete: '{filename} 下載完成',
downloadFailed: '{filename} 下載失敗: {error}'
},
loading: '載入中...',
playStarted: '開始播放: {name}',
playFailed: '播放失敗: {name}',
path: {
copied: '路徑已複製到剪貼簿',
copyFailed: '複製路徑失敗'
},
settingsPanel: {
title: '下載設定',
path: '下載位置',
pathDesc: '設定音樂檔案下載儲存的位置',
pathPlaceholder: '請選擇下載路徑',
noPathSelected: '請先選擇下載路徑',
select: '選擇資料夾',
open: '開啟資料夾',
fileFormat: '檔名格式',
fileFormatDesc: '設定下載音樂時的檔案命名格式',
customFormat: '自訂格式',
separator: '分隔符號',
separators: {
dash: '空格-空格',
underscore: '底線',
space: '空格'
},
dragToArrange: '拖曳排序或使用箭頭按鈕調整順序:',
formatVariables: '可用變數',
preview: '預覽效果:',
saveSuccess: '下載設定已儲存',
presets: {
songArtist: '歌曲名 - 歌手名',
artistSong: '歌手名 - 歌曲名',
songOnly: '僅歌曲名'
},
components: {
songName: '歌曲名',
artistName: '歌手名',
albumName: '專輯名'
}
}
};

View File

@@ -0,0 +1,13 @@
export default {
title: '我的收藏',
count: '共 {count} 首',
batchDownload: '批次下載',
download: '下載 ({count})',
emptyTip: '還沒有收藏歌曲',
downloadSuccess: '下載完成',
downloadFailed: '下載失敗',
downloading: '正在下載中,請稍候...',
selectSongsFirst: '請先選擇要下載的歌曲',
descending: '降',
ascending: '升'
};

View File

@@ -0,0 +1,5 @@
export default {
title: '播放歷史',
playCount: '{count}',
getHistoryFailed: '取得歷史記錄失敗'
};

View File

@@ -0,0 +1,48 @@
export default {
title: {
qr: '掃碼登入',
phone: '手機號登入',
cookie: 'Cookie登入',
uid: 'UID登入'
},
qrTip: '使用網易雲APP掃碼登入',
phoneTip: '使用網易雲帳號登入',
tokenTip: '輸入有效的網易雲音樂Cookie即可登入',
uidTip: '輸入使用者ID快速登入',
placeholder: {
phone: '手機號',
password: '密碼',
cookie: '請輸入網易雲音樂Cookietoken',
uid: '請輸入使用者IDUID'
},
button: {
login: '登入',
switchToQr: '掃碼登入',
switchToPhone: '手機號登入',
switchToToken: '使用Cookie登入',
switchToUid: 'UID登入',
backToQr: '返回二維碼登入',
cookieLogin: 'Cookie登入',
autoGetCookie: '自動取得Cookie',
refresh: '點擊刷新',
refreshing: '刷新中...',
refreshQr: '刷新二維碼'
},
message: {
loginSuccess: '登入成功',
tokenLoginSuccess: 'Cookie登入成功',
uidLoginSuccess: 'UID登入成功',
loadError: '載入登入資訊時出錯',
qrCheckError: '檢查二維碼狀態時出錯',
tokenRequired: '請輸入Cookie',
tokenInvalid: 'Cookie無效請檢查後重試',
uidRequired: '請輸入使用者ID',
uidInvalid: '使用者ID無效或使用者不存在',
uidLoginFailed: 'UID登入失敗請檢查使用者ID是否正確',
autoGetCookieSuccess: '自動取得Cookie成功',
autoGetCookieFailed: '自動取得Cookie失敗',
autoGetCookieTip: '將開啟網易雲音樂登入頁面,請完成登入後關閉視窗'
},
qrTitle: '掃碼登入網易雲音樂',
uidWarning: '注意UID登入僅用於查看使用者公開資訊無法訪問需要登入權限的功能'
};

View File

@@ -0,0 +1,123 @@
export default {
nowPlaying: '正在播放',
playlist: '播放清單',
lyrics: '歌詞',
previous: '上一個',
play: '播放',
pause: '暫停',
next: '下一個',
volumeUp: '音量增加',
volumeDown: '音量減少',
mute: '靜音',
unmute: '取消靜音',
songNum: '歌曲總數:{num}',
addCorrection: '提前 {num} 秒',
subtractCorrection: '延遲 {num} 秒',
playFailed: '目前歌曲播放失敗,播放下一首',
playMode: {
sequence: '順序播放',
loop: '單曲循環',
random: '隨機播放'
},
fullscreen: {
enter: '全螢幕',
exit: '退出全螢幕'
},
close: '關閉',
modeHint: {
single: '單曲循環',
list: '自動播放下一個'
},
lrc: {
noLrc: '暫無歌詞, 請欣賞'
},
reparse: {
title: '選擇解析音源',
desc: '點擊音源直接進行解析,下次播放此歌曲時將使用所選音源',
success: '重新解析成功',
failed: '重新解析失敗',
warning: '請選擇一個音源',
bilibiliNotSupported: 'B站影片不支援重新解析',
processing: '解析中...',
clear: '清除自訂音源'
},
playBar: {
expand: '展開歌詞',
collapse: '收合歌詞',
like: '喜歡',
lyric: '歌詞',
noSongPlaying: '沒有正在播放的歌曲',
eq: '等化器',
playList: '播放清單',
reparse: '重新解析',
playMode: {
sequence: '順序播放',
loop: '循環播放',
random: '隨機播放'
},
play: '開始播放',
pause: '暫停播放',
prev: '上一首',
next: '下一首',
volume: '音量',
favorite: '已收藏{name}',
unFavorite: '已取消收藏{name}',
miniPlayBar: '迷你播放列',
playbackSpeed: '播放速度',
advancedControls: '更多設定s'
},
eq: {
title: '等化器',
reset: '重設',
on: '開啟',
off: '關閉',
bass: '低音',
midrange: '中音',
treble: '高音',
presets: {
flat: '平坦',
pop: '流行',
rock: '搖滾',
classical: '古典',
jazz: '爵士',
electronic: '電子',
hiphop: '嘻哈',
rb: 'R&B',
metal: '金屬',
vocal: '人聲',
dance: '舞曲',
acoustic: '原聲',
custom: '自訂'
}
},
// 定時關閉功能相關
sleepTimer: {
title: '定時關閉',
cancel: '取消定時',
timeMode: '按時間關閉',
songsMode: '按歌曲數關閉',
playlistEnd: '播放完清單後關閉',
afterPlaylist: '播放完清單後關閉',
activeUntilEnd: '播放至清單結束',
minutes: '分鐘',
hours: '小時',
songs: '首歌',
set: '設定',
timerSetSuccess: '已設定{minutes}分鐘後關閉',
songsSetSuccess: '已設定播放{songs}首歌後關閉',
playlistEndSetSuccess: '已設定播放完清單後關閉',
timerCancelled: '已取消定時關閉',
timerEnded: '定時關閉已觸發',
playbackStopped: '音樂播放已停止',
minutesRemaining: '剩餘{minutes}分鐘',
songsRemaining: '剩餘{count}首歌'
},
playList: {
clearAll: '清空播放清單',
alreadyEmpty: '播放清單已經為空',
cleared: '已清空播放清單',
empty: '播放清單為空',
clearConfirmTitle: '清空播放清單',
clearConfirmContent: '這將清空所有播放清單中的歌曲並停止目前播放。是否繼續?'
}
};

View File

@@ -0,0 +1,27 @@
export default {
title: {
hotSearch: '熱搜列表',
searchList: '搜尋列表',
searchHistory: '搜尋歷史'
},
button: {
clear: '清空',
back: '返回',
playAll: '播放列表'
},
loading: {
more: '載入中...',
failed: '搜尋失敗'
},
noMore: '沒有更多了',
error: {
searchFailed: '搜尋失敗'
},
search: {
single: '單曲',
album: '專輯',
playlist: '歌單',
mv: 'MV',
bilibili: 'B站'
}
};

View File

@@ -0,0 +1,262 @@
export default {
theme: '主題',
language: '語言',
regard: '關於',
logout: '登出',
sections: {
basic: '基礎設定',
playback: '播放設定',
application: '應用程式設定',
network: '網路設定',
system: '系統管理',
donation: '捐贈支持',
regard: '關於'
},
basic: {
themeMode: '主題模式',
themeModeDesc: '切換日間/夜間主題',
autoTheme: '跟隨系統',
manualTheme: '手動切換',
language: '語言設定',
languageDesc: '切換顯示語言',
tokenManagement: 'Cookie管理',
tokenManagementDesc: '管理網易雲音樂登入Cookie',
tokenStatus: '目前Cookie狀態',
tokenSet: '已設定',
tokenNotSet: '未設定',
setCookie: '設定Cookie',
modifyToken: '修改Cookie',
clearToken: '清除Cookie',
font: '字體設定',
fontDesc: '選擇字體,優先使用排在前面的字體',
fontScope: {
global: '全域',
lyric: '僅歌詞'
},
animation: '動畫速度',
animationDesc: '是否開起動畫',
animationSpeed: {
slow: '極慢',
normal: '正常',
fast: '極快'
},
fontPreview: {
title: '字體預覽',
chinese: '中文',
english: 'English',
japanese: '日本語',
korean: '한국어',
chineseText: '靜夜思 床前明月光 疑是地上霜',
englishText: 'The quick brown fox jumps over the lazy dog',
japaneseText: 'あいうえお かきくけこ さしすせそ',
koreanText: '가나다라마 바사아자차 카타파하'
}
},
playback: {
quality: '音質設定',
qualityDesc: '選擇音樂播放音質網易云VIP',
qualityOptions: {
standard: '標準',
higher: '較高',
exhigh: '極高',
lossless: '無損',
hires: 'Hi-Res',
jyeffect: '高清環繞聲',
sky: '沉浸環繞聲',
dolby: '杜比全景聲',
jymaster: '超清母帶'
},
musicSources: '音源設定',
musicSourcesDesc: '選擇音樂解析使用的音源平台',
musicSourcesWarning: '至少需要選擇一個音源平台',
musicUnblockEnable: '啟用音樂解析',
musicUnblockEnableDesc: '開啟後將嘗試解析無法播放的音樂',
configureMusicSources: '設定音源',
selectedMusicSources: '已選音源:',
noMusicSources: '未選擇音源',
gdmusicInfo: 'GD音樂台可自動解析多個平台音源自動選擇最佳結果',
autoPlay: '自動播放',
autoPlayDesc: '重新開啟應用程式時是否自動繼續播放',
showStatusBar: '是否顯示狀態列控制功能',
showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)'
},
application: {
closeAction: '關閉行為',
closeActionDesc: '選擇關閉視窗時的行為',
closeOptions: {
ask: '每次詢問',
minimize: '最小化到系統匣',
close: '直接退出'
},
shortcut: '快捷鍵設定',
shortcutDesc: '自訂全域快捷鍵',
download: '下載管理',
downloadDesc: '是否始終顯示下載清單按鈕',
unlimitedDownload: '無限制下載',
unlimitedDownloadDesc: '開啟後將無限制下載音樂(可能出現下載失敗的情況), 預設限制 300 首',
downloadPath: '下載目錄',
downloadPathDesc: '選擇音樂檔案的下載位置',
remoteControl: '遠端控制',
remoteControlDesc: '設定遠端控制功能'
},
network: {
apiPort: '音樂API連接埠',
apiPortDesc: '修改後需要重啟應用程式',
proxy: '代理設定',
proxyDesc: '無法存取音樂時可以開啟代理',
proxyHost: '代理位址',
proxyHostPlaceholder: '請輸入代理位址',
proxyPort: '代理連接埠',
proxyPortPlaceholder: '請輸入代理連接埠',
realIP: 'realIP設定',
realIPDesc: '由於限制,此項目在國外使用會受到限制可使用realIP參數,傳進國內IP解決',
messages: {
proxySuccess: '代理設定已儲存,重啟應用程式後生效',
proxyError: '請檢查輸入是否正確',
realIPSuccess: '真實IP設定已儲存',
realIPError: '請輸入有效的IP位址'
}
},
system: {
cache: '快取管理',
cacheDesc: '清除快取',
cacheClearTitle: '請選擇要清除的快取類型:',
cacheTypes: {
history: {
label: '播放歷史',
description: '清除播放過的歌曲記錄'
},
favorite: {
label: '收藏記錄',
description: '清除本機收藏的歌曲記錄(不會影響雲端收藏)'
},
user: {
label: '使用者資料',
description: '清除登入資訊和使用者相關資料'
},
settings: {
label: '應用程式設定',
description: '清除應用程式的所有自訂設定'
},
downloads: {
label: '下載記錄',
description: '清除下載歷史記錄(不會刪除已下載的檔案)'
},
resources: {
label: '音樂資源',
description: '清除已載入的音樂檔案、歌詞等資源快取'
},
lyrics: {
label: '歌詞資源',
description: '清除已載入的歌詞資源快取'
}
},
restart: '重新啟動',
restartDesc: '重新啟動應用程式',
messages: {
clearSuccess: '清除成功,部分設定在重啟後生效'
}
},
about: {
version: '版本',
checkUpdate: '檢查更新',
checking: '檢查中...',
latest: '目前已是最新版本',
hasUpdate: '發現新版本',
gotoUpdate: '前往更新',
gotoGithub: '前往 Github',
author: '作者',
authorDesc: 'algerkong 點個star🌟呗',
messages: {
checkError: '檢查更新失敗,請稍後重試'
}
},
validation: {
selectProxyProtocol: '請選擇代理協議',
proxyHost: '請輸入代理位址',
portNumber: '請輸入有效的連接埠號(1-65535)'
},
lyricSettings: {
title: '歌詞設定',
tabs: {
display: '顯示',
interface: '介面',
typography: '文字',
mobile: '行動端'
},
pureMode: '純淨模式',
hideCover: '隱藏封面',
centerDisplay: '置中顯示',
showTranslation: '顯示翻譯',
hideLyrics: '隱藏歌詞',
hidePlayBar: '隱藏播放列',
hideMiniPlayBar: '隱藏迷你播放列',
showMiniPlayBar: '顯示迷你播放列',
backgroundTheme: '背景主題',
themeOptions: {
default: '預設',
light: '亮色',
dark: '暗色'
},
fontSize: '字體大小',
fontSizeMarks: {
small: '小',
medium: '中',
large: '大'
},
letterSpacing: '字間距',
letterSpacingMarks: {
compact: '緊湊',
default: '預設',
loose: '寬鬆'
}
},
themeColor: {
title: '歌詞主題色',
presetColors: '預設顏色',
customColor: '自訂顏色',
preview: '預覽效果',
previewText: '歌詞效果',
colorNames: {
'spotify-green': 'Spotify 綠',
'apple-blue': '蘋果藍',
'youtube-red': 'YouTube 紅',
orange: '活力橙',
purple: '神秘紫',
pink: '櫻花粉'
},
tooltips: {
openColorPicker: '開啟色板',
closeColorPicker: '關閉色板'
},
placeholder: '#1db954'
},
cookie: {
title: 'Cookie設定',
description: '請輸入網易雲音樂的Cookie',
placeholder: '請貼上完整的Cookie...',
help: {
format: 'Cookie通常以 "MUSIC_U=" 開頭',
source: '可以從瀏覽器開發者工具的網路請求中取得',
storage: 'Cookie設定後將自動儲存到本機儲存'
},
action: {
save: '儲存Cookie',
paste: '貼上',
clear: '清空'
},
validation: {
required: '請輸入Cookie',
format: 'Cookie格式可能不正確請檢查是否包含MUSIC_U'
},
message: {
saveSuccess: 'Cookie儲存成功',
saveError: 'Cookie儲存失敗',
pasteSuccess: '貼上成功',
pasteError: '貼上失敗,請手動複製'
},
info: {
length: '目前長度:{length} 字元'
}
}
};

View File

@@ -0,0 +1,28 @@
export default {
menu: {
play: '播放',
playNext: '下一首播放',
download: '下載歌曲',
addToPlaylist: '新增至播放清單',
favorite: '喜歡',
unfavorite: '取消喜歡',
removeFromPlaylist: '從播放清單中刪除',
dislike: '不喜歡',
undislike: '取消不喜歡'
},
message: {
downloading: '正在下載中,請稍候...',
downloadFailed: '下載失敗',
downloadQueued: '已加入下載佇列',
addedToNextPlay: '已新增至下一首播放',
getUrlFailed: '取得音樂下載位址失敗,請檢查是否登入'
},
dialog: {
dislike: {
title: '提示!',
content: '確認不喜歡這首歌嗎?再次進入將從每日推薦中排除。',
positiveText: '不喜歡',
negativeText: '取消'
}
}
};

View File

@@ -0,0 +1,48 @@
export default {
profile: {
followers: '粉絲',
following: '關注',
level: '等級'
},
playlist: {
created: '建立的歌單',
mine: '我建立的',
trackCount: '{count}首',
playCount: '播放{count}次'
},
ranking: {
title: '聽歌排行',
playCount: '{count}次'
},
follow: {
title: '關注列表',
viewPlaylist: '查看歌單',
noFollowings: '暫無關注',
loadMore: '載入更多',
noSignature: '這個傢伙很懶,什麼都沒留下',
userFollowsTitle: '的關注',
myFollowsTitle: '我的關注'
},
follower: {
title: '粉絲列表',
noFollowers: '暫無粉絲',
loadMore: '載入更多',
userFollowersTitle: '的粉絲',
myFollowersTitle: '我的粉絲'
},
detail: {
playlists: '歌單',
records: '聽歌排行',
noPlaylists: '暫無歌單',
noRecords: '暫無聽歌記錄',
artist: '歌手',
noSignature: '這個人很懶,什麼都沒留下',
invalidUserId: '使用者ID無效',
noRecordPermission: '{name}不讓你聽歌排行'
},
message: {
loadFailed: '載入使用者頁面失敗',
deleteSuccess: '刪除成功',
deleteFailed: '刪除失敗'
}
};

25
src/i18n/languages.ts Normal file
View File

@@ -0,0 +1,25 @@
// 语言配置文件 - 集中管理语言相关的配置
// 语言显示名称映射
export const LANGUAGE_DISPLAY_NAMES: Record<string, string> = {
'zh-CN': '简体中文',
'zh-Hant': '繁體中文',
'en-US': 'English',
'ja-JP': '日本語',
'ko-KR': '한국어'
};
// 默认语言
export const DEFAULT_LANGUAGE = 'zh-CN';
// 回退语言
export const FALLBACK_LANGUAGE = 'en-US';
// 语言排序优先级用于在UI中的显示顺序
export const LANGUAGE_PRIORITY: Record<string, number> = {
'zh-CN': 1,
'zh-Hant': 2,
'en-US': 3,
'ja-JP': 4,
'ko-KR': 5
};

View File

@@ -1,17 +1,15 @@
import enUS from './lang/en-US';
import zhCN from './lang/zh-CN';
import { DEFAULT_LANGUAGE } from './languages';
import { buildLanguageMessages } from './utils';
const messages = {
'zh-CN': zhCN,
'en-US': enUS
} as const;
// 使用工具函数构建语言消息对象
const messages = buildLanguageMessages();
type Language = keyof typeof messages;
// 为主进程提供一个简单的 i18n 实现
const mainI18n = {
global: {
currentLocale: 'zh-CN' as Language,
currentLocale: DEFAULT_LANGUAGE as Language,
get locale() {
return this.currentLocale;
},

View File

@@ -1,17 +1,15 @@
import { createI18n } from 'vue-i18n';
import enUS from './lang/en-US';
import zhCN from './lang/zh-CN';
import { DEFAULT_LANGUAGE, FALLBACK_LANGUAGE } from './languages';
import { buildLanguageMessages } from './utils';
const messages = {
'zh-CN': zhCN,
'en-US': enUS
};
// 使用工具函数构建语言消息对象
const messages = buildLanguageMessages();
const i18n = createI18n({
legacy: false,
locale: 'zh-CN',
fallbackLocale: 'en-US',
locale: DEFAULT_LANGUAGE,
fallbackLocale: FALLBACK_LANGUAGE,
messages,
globalInjection: true,
silentTranslationWarn: true,

60
src/i18n/utils.ts Normal file
View File

@@ -0,0 +1,60 @@
// 自动导入所有语言的所有翻译文件
const allLangModules = import.meta.glob('./lang/**/*.ts', { eager: true });
// 构建语言消息对象
export const buildLanguageMessages = () => {
const messages: Record<string, Record<string, any>> = {};
Object.entries(allLangModules).forEach(([path, module]) => {
// 解析路径,例如 './lang/zh-CN/common.ts' -> { lang: 'zh-CN', module: 'common' }
const match = path.match(/\.\/lang\/([^/]+)\/([^/]+)\.ts$/);
if (match) {
const [, langCode, moduleName] = match;
// 跳过 index 文件
if (moduleName !== 'index') {
if (!messages[langCode]) {
messages[langCode] = {};
}
messages[langCode][moduleName] = (module as any).default;
}
}
});
return messages;
};
// 获取所有支持的语言
export const getSupportedLanguages = (): string[] => {
const messages = buildLanguageMessages();
return Object.keys(messages);
};
export const isLanguageSupported = (lang: string): boolean => {
return getSupportedLanguages().includes(lang);
};
import { LANGUAGE_DISPLAY_NAMES, LANGUAGE_PRIORITY } from './languages';
// 获取语言显示名称的映射
export const getLanguageDisplayNames = (): Record<string, string> => {
return LANGUAGE_DISPLAY_NAMES;
};
// 生成语言选项数组,用于下拉选择等组件
export const getLanguageOptions = () => {
const supportedLanguages = getSupportedLanguages();
const displayNames = getLanguageDisplayNames();
// 按优先级排序
const sortedLanguages = supportedLanguages.sort((a, b) => {
const priorityA = LANGUAGE_PRIORITY[a] || 999;
const priorityB = LANGUAGE_PRIORITY[b] || 999;
return priorityA - priorityB;
});
return sortedLanguages.map((lang) => ({
label: displayNames[lang] || lang,
value: lang
}));
};

View File

@@ -8,20 +8,19 @@ import { loadLyricWindow } from './lyric';
import { initializeConfig } from './modules/config';
import { initializeFileManager } from './modules/fileManager';
import { initializeFonts } from './modules/fonts';
import { initializeLoginWindow } from './modules/loginWindow';
import { initializeRemoteControl } from './modules/remoteControl';
import { initializeShortcuts, registerShortcuts } from './modules/shortcuts';
import { initializeStats, setupStatsHandlers } from './modules/statsService';
import { initializeTray, updateCurrentSong, updatePlayState, updateTrayMenu } from './modules/tray';
import { setupUpdateHandlers } from './modules/update';
import { createMainWindow, initializeWindowManager } from './modules/window';
import { createMainWindow, initializeWindowManager, setAppQuitting } from './modules/window';
import { initWindowSizeManager } from './modules/window-size';
import { startMusicApi } from './server';
// 导入所有图标
const iconPath = join(__dirname, '../../resources');
const icon = nativeImage.createFromPath(
process.platform === 'darwin'
? join(iconPath, 'icon.icns')
: join(iconPath, 'icon.png')
process.platform === 'darwin' ? join(iconPath, 'icon.icns') : join(iconPath, 'icon.png')
);
let mainWindow: Electron.BrowserWindow;
@@ -43,6 +42,8 @@ function initialize() {
initializeWindowManager();
// 初始化字体管理
initializeFonts();
// 初始化登录窗口
initializeLoginWindow();
// 创建主窗口
mainWindow = createMainWindow(icon);
@@ -50,12 +51,6 @@ function initialize() {
// 初始化托盘
initializeTray(iconPath, mainWindow);
// 初始化统计服务
initializeStats();
// 设置统计相关的IPC处理程序
setupStatsHandlers(ipcMain);
// 启动音乐API
startMusicApi();
@@ -99,6 +94,9 @@ if (!isSingleInstance) {
optimizer.watchWindowShortcuts(window);
});
// 初始化窗口大小管理器
initWindowSizeManager();
// 初始化应用
initialize();
@@ -140,6 +138,12 @@ if (!isSingleInstance) {
}
});
// 应用即将退出时的处理
app.on('before-quit', () => {
// 设置退出标志
setAppQuitting(true);
});
// 重启应用
ipcMain.on('restart', () => {
app.relaunch();

View File

@@ -122,7 +122,7 @@ const createWin = () => {
}
});
lyricWindow.on('blur', () => lyricWindow && lyricWindow.setMaximizable(false))
lyricWindow.on('blur', () => lyricWindow && lyricWindow.setMaximizable(false));
return lyricWindow;
};

View File

@@ -1,10 +1,13 @@
import axios from 'axios';
import { app, dialog, ipcMain, Notification, protocol, shell } from 'electron';
import Store from 'electron-store';
import { fileTypeFromFile } from 'file-type';
import * as fs from 'fs';
import * as http from 'http';
import * as https from 'https';
import * as mm from 'music-metadata';
import * as NodeID3 from 'node-id3';
import * as os from 'os';
import * as path from 'path';
import { getStore } from './config';
@@ -29,6 +32,9 @@ const audioCacheStore = new Store({
}
});
// 保存已发送通知的文件,避免重复通知
const sentNotifications = new Map();
/**
* 初始化文件管理相关的IPC监听
*/
@@ -36,8 +42,17 @@ export function initializeFileManager() {
// 注册本地文件协议
protocol.registerFileProtocol('local', (request, callback) => {
try {
const decodedUrl = decodeURIComponent(request.url);
const filePath = decodedUrl.replace('local://', '');
const url = request.url;
// local://C:/Users/xxx.mp3
let filePath = decodeURIComponent(url.replace('local:///', ''));
// 兼容 local:///C:/Users/xxx.mp3 这种情况
if (/^\/[a-zA-Z]:\//.test(filePath)) {
filePath = filePath.slice(1);
}
// 还原为系统路径格式
filePath = path.normalize(filePath);
// 检查文件是否存在
if (!fs.existsSync(filePath)) {
@@ -53,6 +68,31 @@ export function initializeFileManager() {
}
});
// 检查文件是否存在
ipcMain.handle('check-file-exists', (_, filePath) => {
try {
return fs.existsSync(filePath);
} catch (error) {
console.error('Error checking if file exists:', error);
return false;
}
});
// 获取支持的音频格式列表
ipcMain.handle('get-supported-audio-formats', () => {
return {
formats: [
{ ext: 'mp3', name: 'MP3' },
{ ext: 'm4a', name: 'M4A/AAC' },
{ ext: 'flac', name: 'FLAC' },
{ ext: 'wav', name: 'WAV' },
{ ext: 'ogg', name: 'OGG Vorbis' },
{ ext: 'aac', name: 'AAC' }
],
default: 'mp3'
};
});
// 通用的选择目录处理
ipcMain.handle('select-directory', async () => {
const result = await dialog.showOpenDialog({
@@ -84,6 +124,23 @@ export function initializeFileManager() {
}
});
// 获取默认下载路径
ipcMain.handle('get-downloads-path', () => {
return app.getPath('downloads');
});
// 获取存储的配置值
ipcMain.handle('get-store-value', (_, key) => {
const store = new Store();
return store.get(key);
});
// 设置存储的配置值
ipcMain.on('set-store-value', (_, key, value) => {
const store = new Store();
store.set(key, value);
});
// 下载音乐处理
ipcMain.on('download-music', handleDownloadRequest);
@@ -132,7 +189,8 @@ export function initializeFileManager() {
const validEntriesPromises = await Promise.all(
entriesArray.map(async ([path, info]) => {
try {
const exists = await fs.promises.access(path)
const exists = await fs.promises
.access(path)
.then(() => true)
.catch(() => false);
return exists ? info : null;
@@ -145,7 +203,7 @@ export function initializeFileManager() {
// 过滤有效的歌曲并排序
const validSongs = validEntriesPromises
.filter(song => song !== null)
.filter((song) => song !== null)
.sort((a, b) => (b.downloadTime || 0) - (a.downloadTime || 0));
// 更新存储,移除不存在的文件记录
@@ -311,6 +369,7 @@ async function downloadMusic(
) {
let finalFilePath = '';
let writer: fs.WriteStream | null = null;
let tempFilePath = '';
try {
// 使用配置Store来获取设置
@@ -319,28 +378,43 @@ async function downloadMusic(
(configStore.get('set.downloadPath') as string) || app.getPath('downloads');
const apiPort = configStore.get('set.musicApiPort') || 30488;
// 清理文件名中的非法字符
const sanitizedFilename = sanitizeFilename(filename);
// 获取文件名格式设置
const nameFormat =
(configStore.get('set.downloadNameFormat') as string) || '{songName} - {artistName}';
// 从URL中获取文件扩展名如果没有则使用传入的type或默认mp3
const urlExt = type ? `.${type}` : '.mp3';
const filePath = path.join(downloadPath, `${sanitizedFilename}${urlExt}`);
// 根据格式创建文件名
let formattedFilename = filename;
if (songInfo) {
// 准备替换变量
const artistName = songInfo.ar?.map((a: any) => a.name).join('/') || '未知艺术家';
const songName = songInfo.name || filename;
const albumName = songInfo.al?.name || '未知专辑';
// 检查文件是否已存在,如果存在则添加序号
finalFilePath = filePath;
let counter = 1;
while (fs.existsSync(finalFilePath)) {
const ext = path.extname(filePath);
const nameWithoutExt = filePath.slice(0, -ext.length);
finalFilePath = `${nameWithoutExt} (${counter})${ext}`;
counter++;
// 应用自定义格式
formattedFilename = nameFormat
.replace(/\{songName\}/g, songName)
.replace(/\{artistName\}/g, artistName)
.replace(/\{albumName\}/g, albumName);
}
// 清理文件名中的非法字符
const sanitizedFilename = sanitizeFilename(formattedFilename);
// 创建临时文件路径 (在系统临时目录中创建)
const tempDir = path.join(os.tmpdir(), 'AlgerMusicPlayerTemp');
// 确保临时目录存在
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
tempFilePath = path.join(tempDir, `${Date.now()}_${sanitizedFilename}.tmp`);
// 先获取文件大小
const headResponse = await axios.head(url);
const totalSize = parseInt(headResponse.headers['content-length'] || '0', 10);
// 开始下载
// 开始下载到临时文件
const response = await axios({
url,
method: 'GET',
@@ -350,7 +424,7 @@ async function downloadMusic(
httpsAgent: new https.Agent({ keepAlive: true })
});
writer = fs.createWriteStream(finalFilePath);
writer = fs.createWriteStream(tempFilePath);
let downloadedSize = 0;
// 使用 data 事件来跟踪下载进度
@@ -362,7 +436,7 @@ async function downloadMusic(
progress,
loaded: downloadedSize,
total: totalSize,
path: finalFilePath,
path: tempFilePath,
status: progress === 100 ? 'completed' : 'downloading',
songInfo: songInfo || {
name: filename,
@@ -380,11 +454,79 @@ async function downloadMusic(
});
// 验证文件是否完整下载
const stats = fs.statSync(finalFilePath);
const stats = fs.statSync(tempFilePath);
if (stats.size !== totalSize) {
throw new Error('文件下载不完整');
}
// 检测文件类型
let fileExtension = '';
try {
// 首先尝试使用file-type库检测
const fileType = await fileTypeFromFile(tempFilePath);
if (fileType && fileType.ext) {
fileExtension = `.${fileType.ext}`;
console.log(`文件类型检测结果: ${fileType.mime}, 扩展名: ${fileExtension}`);
} else {
// 如果file-type无法识别尝试使用music-metadata
const metadata = await mm.parseFile(tempFilePath);
if (metadata && metadata.format) {
// 根据format.container或codec判断扩展名
const formatInfo = metadata.format;
const container = formatInfo.container || '';
const codec = formatInfo.codec || '';
// 音频格式映射表
const formatMap = {
mp3: ['MPEG', 'MP3', 'mp3'],
aac: ['AAC'],
flac: ['FLAC'],
ogg: ['Ogg', 'Vorbis'],
wav: ['WAV', 'PCM'],
m4a: ['M4A', 'MP4']
};
// 查找匹配的格式
const format = Object.entries(formatMap).find(([_, keywords]) =>
keywords.some((keyword) => container.includes(keyword) || codec.includes(keyword))
);
// 设置文件扩展名如果没找到则默认为mp3
fileExtension = format ? `.${format[0]}` : '.mp3';
console.log(
`music-metadata检测结果: 容器:${container}, 编码:${codec}, 扩展名: ${fileExtension}`
);
} else {
// 两种方法都失败使用传入的type或默认mp3
fileExtension = type ? `.${type}` : '.mp3';
console.log(`无法检测文件类型,使用默认扩展名: ${fileExtension}`);
}
}
} catch (err) {
console.error('检测文件类型失败:', err);
// 检测失败使用传入的type或默认mp3
fileExtension = type ? `.${type}` : '.mp3';
}
// 使用检测到的文件扩展名创建最终文件路径
const filePath = path.join(downloadPath, `${sanitizedFilename}${fileExtension}`);
// 检查文件是否已存在,如果存在则添加序号
finalFilePath = filePath;
let counter = 1;
while (fs.existsSync(finalFilePath)) {
const ext = path.extname(filePath);
const nameWithoutExt = filePath.slice(0, -ext.length);
finalFilePath = `${nameWithoutExt} (${counter})${ext}`;
counter++;
}
// 将临时文件移动到最终位置
fs.copyFileSync(tempFilePath, finalFilePath);
fs.unlinkSync(tempFilePath); // 删除临时文件
// 下载歌词
let lyricData = null;
let lyricsContent = '';
@@ -413,8 +555,7 @@ async function downloadMusic(
}
}
// 不再单独写入歌词文件只保存在ID3标签中
console.log('歌词已准备好将写入ID3标签');
console.log('歌词已准备好,将写入元数据');
}
}
} catch (lyricError) {
@@ -437,9 +578,7 @@ async function downloadMusic(
// 获取封面图片的buffer
coverImageBuffer = Buffer.from(coverResponse.data);
// 不再单独保存封面文件只保存在ID3标签中
console.log('封面已准备好将写入ID3标签');
console.log('封面已准备好,将写入元数据');
}
}
} catch (coverError) {
@@ -447,54 +586,58 @@ async function downloadMusic(
// 继续处理,不影响音乐下载
}
// 在写入ID3标签前先移除可能存在的旧标签
try {
NodeID3.removeTags(finalFilePath);
} catch (err) {
console.error('Error removing existing ID3 tags:', err);
}
// 强化ID3标签的写入格式
const fileFormat = fileExtension.toLowerCase();
const artistNames =
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('/ ') || '未知艺术家';
const tags = {
title: filename,
artist: artistNames,
TPE1: artistNames,
TPE2: artistNames,
album: songInfo?.al?.name || songInfo?.song?.album?.name || songInfo?.name || filename,
APIC: {
// 专辑封面
imageBuffer: coverImageBuffer,
type: {
id: 3,
name: 'front cover'
},
description: 'Album cover',
mime: 'image/jpeg'
},
USLT: {
// 歌词
language: 'chi',
description: 'Lyrics',
text: lyricsContent || ''
},
trackNumber: songInfo?.no || undefined,
year: songInfo?.publishTime
? new Date(songInfo.publishTime).getFullYear().toString()
: undefined
};
try {
const success = NodeID3.write(tags, finalFilePath);
if (!success) {
console.error('Failed to write ID3 tags');
} else {
console.log('ID3 tags written successfully');
// 根据文件类型处理元数据
if (['.mp3'].includes(fileFormat)) {
// 对MP3文件使用NodeID3处理ID3标签
try {
// 在写入ID3标签前先移除可能存在的旧标签
NodeID3.removeTags(finalFilePath);
const tags = {
title: filename,
artist: artistNames,
TPE1: artistNames,
TPE2: artistNames,
album: songInfo?.al?.name || songInfo?.song?.album?.name || songInfo?.name || filename,
APIC: {
// 专辑封面
imageBuffer: coverImageBuffer,
type: {
id: 3,
name: 'front cover'
},
description: 'Album cover',
mime: 'image/jpeg'
},
USLT: {
// 歌词
language: 'chi',
description: 'Lyrics',
text: lyricsContent || ''
},
trackNumber: songInfo?.no || undefined,
year: songInfo?.publishTime
? new Date(songInfo.publishTime).getFullYear().toString()
: undefined
};
const success = NodeID3.write(tags, finalFilePath);
if (!success) {
console.error('Failed to write ID3 tags');
} else {
console.log('ID3 tags written successfully');
}
} catch (err) {
console.error('Error writing ID3 tags:', err);
}
} catch (err) {
console.error('Error writing ID3 tags:', err);
} else {
// 对于非MP3文件使用music-metadata来写入元数据可能需要专门的库
// 或者根据不同文件类型使用专用工具,暂时只记录但不处理
console.log(`文件类型 ${fileFormat} 不支持使用NodeID3写入标签跳过元数据写入`);
}
// 保存下载信息
@@ -519,7 +662,7 @@ async function downloadMusic(
size: totalSize,
path: finalFilePath,
downloadTime: Date.now(),
type: type || 'mp3',
type: fileExtension.substring(1), // 去掉前面的点号,只保留扩展名
lyric: lyricData
};
@@ -532,27 +675,38 @@ async function downloadMusic(
history.unshift(newSongInfo);
downloadStore.set('history', history);
// 发送桌面通知
try {
const artistNames =
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('/') ||
'未知艺术家';
const notification = new Notification({
title: '下载完成',
body: `${songInfo?.name || filename} - ${artistNames}`,
silent: false
});
// 避免重复发送通知
const notificationId = `download-${finalFilePath}`;
if (!sentNotifications.has(notificationId)) {
sentNotifications.set(notificationId, true);
notification.on('click', () => {
shell.showItemInFolder(finalFilePath);
});
// 发送桌面通知
try {
const artistNames =
(songInfo?.ar || songInfo?.song?.artists)?.map((a: any) => a.name).join('/') ||
'未知艺术家';
const notification = new Notification({
title: '下载完成',
body: `${songInfo?.name || filename} - ${artistNames}`,
silent: false
});
notification.show();
} catch (notifyError) {
console.error('发送通知失败:', notifyError);
notification.on('click', () => {
shell.showItemInFolder(finalFilePath);
});
notification.show();
// 60秒后清理通知记录释放内存
setTimeout(() => {
sentNotifications.delete(notificationId);
}, 60000);
} catch (notifyError) {
console.error('发送通知失败:', notifyError);
}
}
// 发送下载完成事件
// 发送下载完成事件,确保只发送一次
event.reply('music-download-complete', {
success: true,
path: finalFilePath,
@@ -571,6 +725,17 @@ async function downloadMusic(
if (writer) {
writer.end();
}
// 清理临时文件
if (tempFilePath && fs.existsSync(tempFilePath)) {
try {
fs.unlinkSync(tempFilePath);
} catch (e) {
console.error('Failed to delete temporary file:', e);
}
}
// 清理未完成的最终文件
if (finalFilePath && fs.existsSync(finalFilePath)) {
try {
fs.unlinkSync(finalFilePath);

Some files were not shown because too many files have changed in this diff Show More