Compare commits

..

1 Commits
main ... v3.9.1

Author SHA1 Message Date
alger
7b18d9eba3 🐞 fix: 修复登录状态问题 修复播放退出登录的问题 2025-01-23 11:42:03 +08:00
391 changed files with 10492 additions and 63714 deletions

View File

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

4
.eslintignore Normal file
View File

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

137
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,137 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'eslint-config-airbnb-base',
'@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-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 name: 反馈 Bug
description: 通过 github 模板进行 Bug 反馈。 description: 通过 github 模板进行 Bug 反馈。
title: '描述问题的标题' title: "描述问题的标题"
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

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

View File

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

View File

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

View File

@@ -6,26 +6,13 @@ on:
- 'v*' - 'v*'
jobs: jobs:
build: release:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false
matrix: matrix:
include: os: [macos-latest, windows-latest, ubuntu-latest]
- id: mac-x64
os: macos-latest
build_command: npm run build:mac:x64
- id: mac-arm64
os: macos-latest
build_command: npm run build:mac:arm64
- id: windows
os: windows-latest
build_command: npm run build:win
- id: linux
os: ubuntu-latest
build_command: npm run build:linux
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -33,82 +20,68 @@ jobs:
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 18
- name: Install dependencies - name: Install Dependencies
run: npm install run: npm install
- name: Install Linux build dependencies # MacOS Build
- name: Build MacOS
if: matrix.os == 'macos-latest'
run: |
export ELECTRON_BUILDER_EXTRA_ARGS="--universal"
npm run build:mac
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_IDENTITY_AUTO_DISCOVERY: false
DEBUG: electron-builder
# Windows Build
- name: Build Windows
if: matrix.os == 'windows-latest'
run: npm run build:win
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Linux Build
- name: Build Linux
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
npm run build:linux
- name: Build artifacts
run: ${{ matrix.build_command }}
env: env:
CSC_IDENTITY_AUTO_DISCOVERY: false GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare mac update metadata
if: startsWith(matrix.id, 'mac-')
run: rm -f dist/latest-mac.yml
- name: Upload release bundle
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.id }}
if-no-files-found: error
path: |
dist/*.dmg
dist/*.zip
dist/*.exe
dist/*.deb
dist/*.rpm
dist/*.AppImage
dist/latest*.yml
dist/*.blockmap
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
# Get version from tag
- name: Get version from tag - name: Get version from tag
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
shell: bash
# Read release notes
- name: Read release notes - name: Read release notes
id: release_notes
run: | run: |
NOTES=$(awk "/## \[v${{ env.VERSION }}\]/{p=1;print;next} /## \[v/{p=0}p" CHANGELOG.md) NOTES=$(awk "/## \[v${{ env.VERSION }}\]/{p=1;print;next} /## \[v/{p=0}p" CHANGELOG.md)
echo "NOTES<<EOF" >> $GITHUB_ENV echo "NOTES<<EOF" >> $GITHUB_ENV
echo "$NOTES" >> $GITHUB_ENV echo "$NOTES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV
shell: bash
- name: Download build artifacts # Upload artifacts
uses: actions/download-artifact@v4 - name: Upload artifacts
with: uses: softprops/action-gh-release@v1
path: release-artifacts
- name: Prepare release files
run: |
mkdir -p release-upload
find release-artifacts -type f \
! -name 'latest-mac-x64.yml' \
! -name 'latest-mac-arm64.yml' \
-exec cp {} release-upload/ \;
node scripts/merge_latest_mac_yml.mjs \
release-artifacts/mac-x64/latest-mac-x64.yml \
release-artifacts/mac-arm64/latest-mac-arm64.yml \
release-upload/latest-mac.yml
- name: Publish GitHub Release
uses: softprops/action-gh-release@v2
with: with:
files: |
dist/*.dmg
dist/*.exe
dist/*.deb
dist/*.AppImage
dist/latest*.yml
dist/*.blockmap
body: ${{ env.NOTES }} body: ${{ env.NOTES }}
draft: false draft: false
prerelease: false prerelease: false
files: release-upload/*
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,51 +0,0 @@
name: Deploy Web
on:
push:
branches:
- main # 或者您的主分支名称
workflow_dispatch: # 允许手动触发
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: 创建环境变量文件
run: |
echo "VITE_API=${{ secrets.VITE_API }}" > .env.production.local
echo "VITE_API_MUSIC=${{ secrets.VITE_API_MUSIC }}" >> .env.production.local
# 添加其他需要的环境变量
cat .env.production.local # 查看创建的文件内容,调试用
- name: Install Dependencies
run: npm install
- name: Build
run: npm run build
- name: Deploy to Server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
source: 'out/renderer/*'
target: ${{ secrets.DEPLOY_PATH }}
strip_components: 2
- name: Execute Remote Commands
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd ${{ secrets.DEPLOY_PATH }}
echo "部署完成于 $(date)"

28
.gitignore vendored
View File

@@ -16,35 +16,9 @@ dist.zip
.vscode .vscode
bun.lockb bun.lockb
bun.lock
.env.*.local .env.*.local
out out
.cursorrules .cursorrules
.github/deploy_keys
resources/android/**/*
android/app/release
.cursor
.windsurf
.agent
.agents
.claude
.kiro
CLAUDE.md
AGENTS.md
.sisyphus
.worktrees
.auto-imports.d.ts
.components.d.ts
src/renderer/auto-imports.d.ts
src/renderer/components.d.ts

View File

@@ -1,10 +0,0 @@
echo "对已暂存文件运行 lint-staged..."
npx lint-staged
echo "运行类型检查..."
npm run typecheck
echo "运行国际化检查..."
npm run lint:i18n
echo "所有检查通过,准备提交..."

View File

@@ -1,7 +0,0 @@
echo "对已暂存文件运行 lint-staged..."
npx lint-staged
echo "运行类型检查..."
npm run typecheck
echo "所有检查通过,准备提交..."

5
.prettierrc.yaml Normal file
View File

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

View File

@@ -1,122 +1,12 @@
# 更新日志 # 更新日志
## v5.1.0 ## v3.9.1
### ✨ 新功能 ### 🐞 修复
- 修复登录状态问题 修复播放退出登录的问题
- 新增本地音乐扫描播放功能
- 新增播客页面与组件
- 新增专辑页面
- 桌面歌词新增 单行/双行/滚动 三种显示模式,支持翻译开关和双行分组淡出动画
- 重构自动更新系统,使用 electron-updater 替代手动下载
- 设置页新增音频设备配置
- 快捷键整体重构优化
- 重构 SearchBar集成标题滚动显示功能
- 优化音源解析策略与播放逻辑
- 优化移动端适配与 UI 布局
### 🐛 Bug 修复 ## 咖啡☕️
| 微信 | | 支付宝 |
- 修复自动播放循环与暂停失效问题 | :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: |
- 修复桌面歌词窗口首次打开无歌词问题 | <img src="https://www.ghproxy.cn/https://raw.githubusercontent.com/algerkong/AlgerMusicPlayer/dev_electron/src/renderer/assets/wechat.png" alt="WeChat QRcode" width=200>| | <img src="https://www.ghproxy.cn/https://raw.githubusercontent.com/algerkong/AlgerMusicPlayer/dev_electron/src/renderer/assets/alipay.png" alt="Wechat QRcode" width=200> |
- 修复播放并发控制死代码、shallowRef 响应式丢失、歌词 IPC 高频调用
- 修复 AppMenu 错误主题色
- 修复播放列表抽屉关闭动画使用 setTimeout 不可靠问题
- 修复搜索结果滚动加载触发距离过大
- 修复本地音乐元数据解析并发限流与封面体积限制
- 修复本地音乐扫描增量判断逻辑
- 修复 preload 层 ipc.on 解绑监听器失效
- 修复歌词缓存 IPC 通道未接入初始化
- 修复歌词组件卸载时 groupFadeTimer 未清理导致内存泄漏
- 补全 MV/排行榜/歌单/搜索/专辑页面缺失的国际化
- 修复 NeteaseCloudMusicApi anonymous_token 文件不存在导致启动崩溃
- 修复移动端全屏歌词前奏阶段第一句歌词不可见
- 修复移动端音乐列表页按钮尺寸过大
- 登录页扫码登录改为默认首选
- 设置桌面端最小窗口尺寸为 900×640 防止内容截断
- 移除首页顶部多余 padding
- HomeHero 快捷导航仅移动端显示
### 🔒 安全
- 本地音乐 API 仅监听回环地址,防止外部访问
- LX Music 脚本执行隔离到 Worker 沙箱
### 🎨 优化
- 全面重构 UI播放器、播放条、通用组件、列表项、布局、标题栏、搜索页等
- 重构首页 UI
- 设置页拆分为 7 个独立 Tab 组件,优化捐赠列表性能
- 重构音乐和歌词缓存逻辑,支持可配置缓存目录
- 统一进度追踪机制,移除重复的 rAF 更新循环
- 优化播放列表持久化,精简序列化字段并添加防抖写入
- 优化骨架屏加载效果,修复用户页左侧黑色背景
- 统一 SongItem 圆角与 hover 背景色
- 重构历史记录模块
- 调整主题主色
- 扩展数据层与播放能力
- 增加 i18n 检查脚本与提交钩子
- 重构 i18n 键值检查并增加引用告警模式
## v5.0.0
### ✨ 新功能
- LX Music 音源脚本导入
- 逐字歌词,支持全屏歌词和桌面歌词同步显示
- 心动模式播放
- 移动设备整体页面风格和效果优化
- 移动端添加平板模式设置
- 歌词页面样式控制优化 支持背景、宽度、字体粗细等个性化设置
- 历史日推查看
- 播放记录热力图
- 历史记录支持本地和云端记录
- 用户页面收藏专辑展示
- 添加 GPU 硬件加速设置
- 菜单展开状态保存 - 感谢 [harenchi](https://github.com/souvenp) 的贡献
- 搜索建议 - 感谢 [harenchi](https://github.com/souvenp) 的贡献
- 歌词繁体中文翻译模块,集成 OpenCC 引擎 - 感谢 [Leko](https://github.com/lekoOwO) 的贡献
- 自定义 API源 支持 [自定义源文档](https://github.com/algerkong/AlgerMusicPlayer/blob/main/docs/custom-api-readme.md) - 感谢 [harenchi](https://github.com/souvenp) 的贡献
### 🐛 Bug 修复
- 修复随机播放顺序异常
- 修复音源解析错误处理
- 修复 Mini 播放栏主题颜色问题
- 修复桌面歌词透明模式标题栏显示
- 修复逐字歌词字间距
- 修复远程控制设置无法保存
- 修复下载无损格式返回 HiRes 音质 - 感谢 [harenchi](https://github.com/souvenp) 的贡献
- 兼容 pnpm 包管理器 - 感谢 [Leko](https://github.com/lekoOwO) 的贡献
### 🎨 优化
- 音源解析缓存
- 完善多语言国际化
- 优化播放检测和错误处理
- FLAC 元数据和封面图片处理 - 感谢 [harenchi](https://github.com/souvenp) 的贡献
- 日推不感兴趣调用官方接口 - 感谢 [harenchi](https://github.com/souvenp) 的贡献
- 代码提交流程优化,添加 lint-staged
## 赞赏支持☕️
[赞赏列表](https://donate.alger.fun/donate)
<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>

423
CLAUDE.md
View File

@@ -1,423 +0,0 @@
# CLAUDE.md
本文件为 Claude Code (claude.ai/code) 提供项目指南。
## 项目概述
Alger Music Player 是基于 **Electron + Vue 3 + TypeScript** 构建的第三方网易云音乐播放器支持桌面端Windows/macOS/Linux、Web 和移动端,具备本地 API 服务、桌面歌词、无损音乐下载、音源解锁、EQ 均衡器等功能。
## 技术栈
- **桌面端**: Electron 40 + electron-vite 5
- **前端框架**: Vue 3.5 (Composition API + `<script setup>`)
- **状态管理**: Pinia 3 + pinia-plugin-persistedstate
- **UI 框架**: naive-ui自动导入
- **样式**: Tailwind CSS 3仅在模板中使用 class禁止在 `<style>` 中使用 `@apply`
- **图标**: remixicon
- **音频**: 原生 HTMLAudioElement + Web Audio APIEQ 均衡器)
- **工具库**: VueUse, lodash
- **国际化**: vue-i18n5 种语言zh-CN、en-US、ja-JP、ko-KR、zh-Hant
- **音乐 API**: netease-cloud-music-api-alger + @unblockneteasemusic/server
- **自动更新**: electron-updaterGitHub Releases
- **构建**: Vite 6, electron-builder
## 开发命令
```bash
# 安装依赖(推荐 Node 18+
npm install
# 桌面端开发(推荐)
npm run dev
# Web 端开发(需自建 netease-cloud-music-api 服务)
npm run dev:web
# 类型检查
npm run typecheck # 全部检查
npm run typecheck:node # 主进程
npm run typecheck:web # 渲染进程
# 代码规范
npm run lint # ESLint + i18n 检查
npm run format # Prettier 格式化
# 构建
npm run build # 构建渲染进程和主进程
npm run build:win # Windows 安装包
npm run build:mac # macOS DMG
npm run build:linux # AppImage, deb, rpm
npm run build:unpack # 仅构建不打包
```
## 项目架构
### 目录结构
```
src/
├── main/ # Electron 主进程
│ ├── index.ts # 入口,窗口生命周期
│ ├── modules/ # 功能模块15 个文件)
│ │ ├── window.ts # 窗口管理(主窗口、迷你模式、歌词窗口)
│ │ ├── tray.ts # 系统托盘
│ │ ├── shortcuts.ts # 全局快捷键
│ │ ├── fileManager.ts # 下载管理
│ │ ├── remoteControl.ts # 远程控制 HTTP API
│ │ └── update.ts # 自动更新electron-updater
│ ├── lyric.ts # 歌词窗口
│ ├── server.ts # 本地 API 服务
│ └── unblockMusic.ts # 音源解锁服务
├── preload/index.ts # IPC 桥接(暴露 window.api
├── shared/ # 主进程/渲染进程共享代码
│ └── appUpdate.ts # 更新状态类型定义
├── i18n/ # 国际化
│ ├── lang/ # 语言文件5 语言 × 15 分类 = 75 个文件)
│ ├── main.ts # 主进程 i18n
│ ├── renderer.ts # 渲染进程 i18n
│ └── utils.ts # i18n 工具
└── renderer/ # Vue 应用
├── store/modules/ # Pinia 状态15 个模块)
│ ├── playerCore.ts # 🔑 播放核心状态(纯状态:播放/暂停、音量、倍速)
│ ├── playlist.ts # 🔑 播放列表管理(上/下一首、播放模式)
│ ├── settings.ts # 应用设置
│ ├── user.ts # 用户认证与同步
│ ├── lyric.ts # 歌词状态
│ ├── music.ts # 音乐元数据
│ └── favorite.ts # 收藏管理
├── services/ # 服务层
│ ├── audioService.ts # 🔑 原生 HTMLAudioElement + Web Audio APIEQ、MediaSession
│ ├── playbackController.ts # 🔑 播放控制流playTrack 入口、generation 取消、初始化恢复)
│ ├── playbackRequestManager.ts # 请求 ID 追踪(供 usePlayerHooks 内部取消检查)
│ ├── preloadService.ts # 下一首 URL 预验证
│ ├── SongSourceConfigManager.ts # 单曲音源配置
│ └── translation-engines/ # 翻译引擎策略
├── hooks/ # 组合式函数9 个文件)
│ ├── MusicHook.ts # 🔑 音乐主逻辑(歌词、进度、快捷键)
│ ├── usePlayerHooks.ts # 播放器 hooks
│ ├── useDownload.ts # 下载功能
│ └── IndexDBHook.ts # IndexedDB 封装
├── api/ # API 层16 个文件)
│ ├── musicParser.ts # 🔑 多音源 URL 解析(策略模式)
│ ├── music.ts # 网易云音乐 API
│ ├── bilibili.ts # B站音源
│ ├── gdmusic.ts # GD Music 平台
│ ├── lxMusicStrategy.ts # LX Music 音源策略
│ ├── donation.ts # 捐赠 API
│ └── parseFromCustomApi.ts # 自定义 API 解析
├── components/ # 组件59+ 个文件)
│ ├── common/ # 通用组件24 个)
│ ├── player/ # 播放器组件10 个)
│ ├── settings/ # 设置弹窗组件7 个)
│ └── ...
├── views/ # 页面53 个文件)
│ ├── set/ # 设置页(已拆分为 Tab 组件)
│ │ ├── index.vue # 设置页壳组件(导航 + provide/inject
│ │ ├── keys.ts # InjectionKey 定义
│ │ ├── SBtn.vue # 自定义按钮组件
│ │ ├── SInput.vue # 自定义输入组件
│ │ ├── SSelect.vue # 自定义选择器组件
│ │ ├── SettingItem.vue
│ │ ├── SettingSection.vue
│ │ └── tabs/ # 7 个 Tab 组件
│ │ ├── BasicTab.vue
│ │ ├── PlaybackTab.vue
│ │ ├── ApplicationTab.vue
│ │ ├── NetworkTab.vue
│ │ ├── SystemTab.vue
│ │ ├── AboutTab.vue
│ │ └── DonationTab.vue
│ └── ...
├── router/ # Vue Router3 个文件)
├── types/ # TypeScript 类型20 个文件)
├── utils/ # 工具函数17 个文件)
├── directive/ # 自定义指令
├── const/ # 常量定义
└── assets/ # 静态资源
```
### 核心模块职责
| 模块 | 文件 | 职责 |
|------|------|------|
| 播放控制 | `services/playbackController.ts` | 🔑 播放入口playTrack、generation 取消、初始化恢复、URL 过期处理 |
| 音频服务 | `services/audioService.ts` | 原生 HTMLAudioElement + Web Audio API、EQ 滤波、MediaSession |
| 播放状态 | `store/playerCore.ts` | 纯状态:播放/暂停、音量、倍速、当前歌曲、音频设备 |
| 播放列表 | `store/playlist.ts` | 列表管理、播放模式、上/下一首 |
| 音源解析 | `api/musicParser.ts` | 多音源 URL 解析与缓存 |
| 音乐钩子 | `hooks/MusicHook.ts` | 歌词解析、进度跟踪、键盘快捷键 |
### 播放系统架构
```
用户操作 / 自动播放
playbackController.playTrack(song) ← 唯一入口generation++ 取消旧操作
├─ 加载歌词 + 背景色
├─ 获取播放 URLgetSongDetail
└─ audioService.play(url, track)
├─ audio.src = url ← 单一 HTMLAudioElement换歌改 src
├─ Web Audio API EQ 链 ← createMediaElementSource 只调一次
└─ 原生 DOM 事件 → emit
MusicHook 监听(进度、歌词同步、播放状态)
```
**关键设计**
- **Generation-based 取消**:每次 `playTrack()` 递增 generationawait 后检查是否过期,过期则静默退出
- **单一 HTMLAudioElement**:启动时创建,永不销毁。换歌改 `audio.src`EQ 链不重建
- **Seek**:直接 `audio.currentTime = time`,无 Howler.js 的 pause→play 问题
### 音源解析策略
`musicParser.ts` 使用 **策略模式** 从多个来源解析音乐 URL
**优先级顺序**(可通过 `SongSourceConfigManager` 按曲配置):
1. `custom` - 自定义 API
2. `bilibili` - B站音频
3. `gdmusic` - GD Music 平台
4. `lxmusic` - LX Music HTTP 源
5. `unblock` - UnblockNeteaseMusic 服务
**缓存策略**
- 成功的 URL 在 IndexedDB 缓存 30 分钟(`music_url_cache`
- 失败的尝试在内存中缓存 1 分钟(应用重启自动清除)
- 音源配置变更时缓存失效
### 设置页架构
设置页(`views/set/`)采用 **provide/inject** 模式拆分为 7 个 Tab 组件:
- `index.vue` 作为壳组件:管理 Tab 导航、`setData` 双向绑定与防抖保存
- `keys.ts` 定义类型化的 InjectionKey`SETTINGS_DATA_KEY``SETTINGS_MESSAGE_KEY``SETTINGS_DIALOG_KEY`
- 自定义 UI 组件(`SBtn``SInput``SSelect`)替代部分 naive-ui 组件
- 字体选择器保留 naive-ui `n-select`(需要 filterable + multiple + render-label
## 代码规范
### 命名
- **目录**: kebab-case`components/music-player`
- **组件**: PascalCase`MusicPlayer.vue`
- **组合式函数**: camelCase + `use` 前缀(`usePlayer.ts`
- **Store**: camelCase`playerCore.ts`
- **常量**: UPPER_SNAKE_CASE`MAX_RETRY_COUNT`
### TypeScript
- **优先使用 `type` 而非 `interface`**
- **禁止使用 `enum`,使用 `const` 对象 + `as const`**
- 所有导出函数必须有类型标注
```typescript
// ✅ 正确
type SongResult = { id: number; name: string };
const PlayMode = { ORDER: 'order', LOOP: 'loop' } as const;
// ❌ 避免
interface ISongResult { ... }
enum PlayMode { ... }
```
### Vue 组件结构
```vue
<script setup lang="ts">
// 1. 导入(按类型分组)
import { ref, computed, onMounted } from 'vue';
import { usePlayerStore } from '@/store';
import type { SongResult } from '@/types/music';
// 2. Props & Emits
const props = defineProps<{ id: number }>();
const emit = defineEmits<{ play: [id: number] }>();
// 3. Store
const playerStore = usePlayerStore();
// 4. 响应式状态使用描述性命名isLoading, hasError
const isLoading = ref(false);
// 5. 计算属性
const displayName = computed(() => /* ... */);
// 6. 方法(动词开头命名)
const handlePlay = () => { /* ... */ };
// 7. 生命周期钩子
onMounted(() => { /* ... */ });
</script>
<template>
<!-- naive-ui 组件 + Tailwind CSS -->
</template>
```
### 样式规范
- **禁止在 `<style>` 中使用 `@apply`**,所有 Tailwind 类直接写在模板中
- 如发现代码中有 `@apply` 用法,应优化为内联 Tailwind class
- `<style scoped>` 仅用于无法用 Tailwind 实现的 CSS如 keyframes 动画、`:deep()` 穿透)
### 导入约定
- **naive-ui 组件**:自动导入,无需手动 import
- **Vue 组合式 API**`useDialog``useMessage``useNotification``useLoadingBar` 自动导入
- **路径别名**`@``src/renderer``@i18n``src/i18n`
## 关键实现模式
### 状态持久化
Store 使用 `pinia-plugin-persistedstate` 自动持久化:
```typescript
export const useXxxStore = defineStore('xxx', () => {
// store 逻辑
}, {
persist: {
key: 'xxx-store',
storage: localStorage,
pick: ['fieldsToPersist'] // 仅持久化指定字段
}
});
```
### IPC 通信
```typescript
// 主进程 (src/main/modules/*)
ipcMain.handle('channel-name', async (_, args) => {
return result;
});
// Preload (src/preload/index.ts)
const api = {
methodName: (args) => ipcRenderer.invoke('channel-name', args)
};
contextBridge.exposeInMainWorld('api', api);
// 渲染进程 (src/renderer/*)
const result = await window.api.methodName(args);
```
### IndexedDB 使用
使用 `IndexDBHook` 组合式函数:
```typescript
const db = await useIndexedDB('dbName', [
{ name: 'storeName', keyPath: 'id' }
], version);
const { saveData, getData, deleteData } = db;
await saveData('storeName', { id: 1, data: 'value' });
const data = await getData('storeName', 1);
```
### 新增页面
1. 创建 `src/renderer/views/xxx/index.vue`
2.`src/renderer/router/other.ts` 中添加路由
3.`src/i18n/lang/*/` 下所有 5 种语言中添加 i18n 键值
### 新增 Store
```typescript
// src/renderer/store/modules/xxx.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useXxxStore = defineStore('xxx', () => {
const state = ref(initialValue);
const action = () => { /* ... */ };
return { state, action };
});
// 在 src/renderer/store/index.ts 中导出
export * from './modules/xxx';
```
### 新增音源策略
编辑 `src/renderer/api/musicParser.ts`
```typescript
class NewStrategy implements MusicSourceStrategy {
name = 'new';
priority = 5;
canHandle(sources: string[]) { return sources.includes('new'); }
async parse(id: number, data: any): Promise<ParsedMusicResult> {
// 实现解析逻辑
}
}
// 在 ParserManager 构造函数中注册
this.strategies.push(new NewStrategy());
```
## 平台相关说明
### Web 端开发
运行 `npm run dev:web` 需要:
1. 自建 `netease-cloud-music-api` 服务
2. 在项目根目录创建 `.env.development.local`
```env
VITE_API=https://your-api-server.com
VITE_API_MUSIC=https://your-unblock-server.com
```
### Electron 功能
- **窗口管理**: `src/main/modules/window.ts`(主窗口、迷你模式、歌词窗口)
- **系统托盘**: `src/main/modules/tray.ts`
- **全局快捷键**: `src/main/modules/shortcuts.ts`
- **自动更新**: `src/main/modules/update.ts`electron-updater + GitHub Releases
- **远程控制**: `src/main/modules/remoteControl.ts`HTTP API 远程播放控制)
- **磁盘缓存**: 音乐和歌词文件缓存支持可配置目录、容量上限、LRU/FIFO 清理策略
## API 请求注意事项
- **axios 响应结构**`request.get('/xxx')` 返回 axios response实际数据在 `res.data` 中。若 API 本身也有 `data` 字段(如 `/personal_fm` 返回 `{data: [...], code: 200}`),则需要 `res.data.data` 才能拿到真正的数组,**不要** 直接用 `res.data` 当结果。
- **避免并发请求风暴**:首页不要一次性并发请求大量接口(如 15 个歌单详情),会导致本地 API 服务与 `music.163.com` 的 TLS 连接被 reset502。应使用懒加载hover 时加载)或严格限制并发数。
- **timestamp 参数**:对 `/personal_fm` 等需要实时数据的接口,传 `timestamp: Date.now()` 避免服务端缓存和 stale 连接。`request.ts` 拦截器已自动添加 timestampAPI 层无需重复添加。
### 本地 API 服务调试
- **地址**`http://127.0.0.1:{port}`,默认端口 `30488`,可在设置中修改
- **API 文档**:基于 [NeteaseCloudMusicApi](https://www.npmjs.com/package/NeteaseCloudMusicApi)v4.29),接口文档参见 node_modules/NeteaseCloudMusicApi/public/docs/home.md
- **调试方式**:可直接用 `curl` 测试接口,例如:
```bash
# 测试私人FM需登录 cookie
curl "http://127.0.0.1:30488/personal_fm?timestamp=$(date +%s000)"
# 测试歌单详情
curl "http://127.0.0.1:30488/playlist/detail?id=12449928929"
# 测试FM不喜欢
curl -X POST "http://127.0.0.1:30488/fm_trash?id=歌曲ID&timestamp=$(date +%s000)"
```
- **502 排查**:通常是并发请求过多导致 TLS 连接 reset用 curl 单独调用可验证接口本身是否正常
- **Cookie 传递**:渲染进程通过 `request.ts` 拦截器自动附加 `localStorage` 中的 token
## 重要注意事项
- **主分支**: `dev_electron`PR 目标分支,非 `main`
- **自动导入**: naive-ui 组件、Vue 组合式 API`ref`、`computed` 等)均已自动导入
- **代码风格**: 使用 ESLint + Prettier通过 husky + lint-staged 在 commit 时自动执行
- **国际化**: 所有面向用户的文字必须翻译为 5 种语言
- **提交规范**: commit message 中禁止包含 `Co-Authored-By` 信息
- **IndexedDB 存储**:
- `music`: 歌曲元数据缓存
- `music_lyric`: 歌词缓存
- `api_cache`: 通用 API 响应缓存
- `music_url_cache`: 音乐 URL 缓存30 分钟 TTL

198
DEV.md
View File

@@ -1,198 +0,0 @@
# 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 下

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Alger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,101 +1,66 @@
<h2 align="center">🎵 Alger Music Player</h2> # Alger Music Player
<div align="center">
<div align="center">
<a href="https://github.com/algerkong/AlgerMusicPlayer/stargazers">
<img src="https://img.shields.io/github/stars/algerkong/AlgerMusicPlayer?style=for-the-badge&logo=github&label=Stars&logoColor=white&color=22c55e" alt="GitHub stars">
</a>
<a href="https://github.com/algerkong/AlgerMusicPlayer/releases">
<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频道-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、专辑等完整音乐服务 - 支持歌单、MV、专辑等完整音乐服务
- 音乐资源解析(基于 @unblockneteasemusic/server - 灰色音乐资源解析(基于 @unblockneteasemusic/server
- EQ均衡器 - 高品质音乐试听需网易云VIP
- 定时播放 远程控制播放 倍速播放 - 音乐文件下载(支持右键下载和批量下载)
- 高品质音乐
- 音乐文件下载
- 搜索 MV 音乐 专辑 歌单 bilibili
- 音乐单独选择音源解析
- 🚀 技术特性 - 🚀 技术特性
- 本地化服务无需依赖在线API (基于 netease-cloud-music-api) - 本地化服务无需依赖在线API (基于 netease-cloud-music-api)
- 全平台适配Desktop & Web & Mobile Web & Android<测试> & ios<后续> - 自动更新检测
- 全平台适配Desktop & Web & Mobile Web
## 项目简介 ## 项目简介
一个基于 electron typescript vue3 的桌面音乐播放器 适配 web端 桌面端 web移动端
一个第三方音乐播放器、本地服务、桌面歌词、音乐下载、最高音质
## 预览地址 ## 预览地址
[http://mc.alger.fun/](http://mc.alger.fun/)
[http://music.alger.fun/](http://music.alger.fun/) QQ群:789288579
## 软件截图 ## 软件截图
![首页白](./docs/image.png) ![首页白](./docs/image.png)
![首页黑](./docs/image3.png) ![首页黑](./docs/image3.png)
![歌词](./docs/image6.png) ![歌词](./docs/image1.png)
![桌面歌词](./docs/image2.png) ![桌面歌词](./docs/image2.png)
![设置页面](./docs/image4.png) ![设置页面](./docs/image4.png)
![音乐远程控制](./docs/image5.png)
## 项目启动 ## 技术栈
```bash ### 主要框架
npm install - Vue 3 - 渐进式 JavaScript 框架
npm run dev - TypeScript - JavaScript 的超集,添加了类型系统
``` - Electron - 跨平台桌面应用开发框架
- Vite - 下一代前端构建工具
- Naive UI - 基于 Vue 3 的组件库
## 开发文档
点击这里[开发文档](./DEV.md) ## 咖啡☕️
## 赞赏☕️ [<img src="https://api.gitsponsors.com/api/badge/img?id=710867462" height="90">](https://api.gitsponsors.com/api/badge/link?p=GTUHmTNQ9W5XzPhaLd8cPBm26uhtP/QOon9hexaWh9gnfaDT3ivj1ID0uKScVHL61jTFrK1fRWyigScIYvcLh/no+3zgtdW3TK0+vN0TVs84Mt3RibhEqAgBHSd8KhNLxaMd4vMIY37P5gOA2/QYcw==)
[赞赏列表](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> | | <img src="https://github.com/algerkong/algerkong/blob/main/wechat.jpg?raw=true" alt="WeChat QRcode" width=200> | <img src="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" alt="Wechat QRcode" width=200> |
## 项目统计
## Stargazers over time
[![Stargazers over time](https://starchart.cc/algerkong/AlgerMusicPlayer.svg?variant=adaptive)](https://starchart.cc/algerkong/AlgerMusicPlayer) [![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')
## 欢迎提Issues ## 欢迎提Issues
## 声明 ## 免责声明
本软件仅用于学习交流,禁止用于商业用途,否则后果自负。 本软件仅用于学习交流,禁止用于商业用途,否则后果自负。
希望大家还是要多多支持官方正版,此软件仅用作开发教学。

101
android/.gitignore vendored
View File

@@ -1,101 +0,0 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml

90
auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,90 @@
/* 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');
}

View File

@@ -16,7 +16,5 @@
<true/> <true/>
<key>com.apple.security.files.downloads.read-write</key> <key>com.apple.security.files.downloads.read-write</key>
<true/> <true/>
<key>com.apple.security.device.microphone</key>
<true/>
</dict> </dict>
</plist> </plist>

33
components.d.ts vendored Normal file
View File

@@ -0,0 +1,33 @@
/* 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']
}
}

View File

@@ -1,62 +0,0 @@
## 🎵 自定义音源API配置
现在支持通过导入一个简单的 JSON 配置文件来对接第三方的音乐解析 API。这将提供极大的灵活性可以接入任意第三方音源。
### 如何使用
1. 前往 **设置 -> 播放设置 -> 音源设置**
2.**自定义 API 设置** 区域,点击 **“导入 JSON 配置”** 按钮。
3. 选择你已经编写好的 `xxx.json` 配置文件。
4. 导入成功后,程序将优先使用你的自定义 API 进行解析。
### JSON 配置文件格式说明
导入的配置文件必须是一个合法的 JSON 文件,并包含以下字段:
| 字段名 | 类型 | 是否必须 | 描述 |
| ----------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | 是 | API 名称,将显示在应用的 UI 界面上。 |
| `apiUrl` | `string` | 是 | API 的基础请求地址。 |
| `method` | `string` | 否 | HTTP 请求方法。可以是 `"GET"``"POST"`。**如果省略,默认为 "GET"**。 |
| `params` | `object` | 是 | 请求时需要发送的参数。对于 `GET` 请求,它们会作为查询字符串;对于 `POST` 请求,它们会作为请求体。 |
| `qualityMapping` | `object` | 否 | **音质映射表**。用于将应用内部的音质值(如 `"lossless"`)翻译成你的 API 需要的特定值。如果省略,则直接使用应用内部值。 |
| `responseUrlPath` | `string` | 是 | **URL提取路径**。用于从 API 返回的 JSON 响应中找到最终可播放的音乐链接。支持点 `.` 和方括号 `[]` 语法来访问嵌套对象和数组。 |
#### 占位符
`params` 对象的值中,你可以使用以下占位符,程序在请求时会自动替换它们:
- `{songId}`: 将被替换为当前歌曲的 ID。
- `{quality}`: 将被替换为当前用户设置的音质字符串 (例如, `"higher"`, `"lossless"`)。
#### 音质值列表
应用内部使用的音质值如下,你可以在 `qualityMapping` 中使用它们作为**键**
`standard`, `higher`, `exhigh`, `lossless`, `hires`, `jyeffect`, `sky`, `dolby`, `jymaster`
### 示例
假设有一个 API 如下:
`https://api.example.com/music?song_id=12345&bitrate=320000`
它返回的 JSON 是:
`{ "code": 200, "data": { "play_url": "http://..." } }`
那么对应的 JSON 配置文件应该是:
```json
{
"name": "Example API",
"apiUrl": "https://api.example.com/music",
"method": "GET",
"params": {
"song_id": "{songId}",
"bitrate": "{quality}"
},
"qualityMapping": {
"higher": "128000",
"exhigh": "320000",
"lossless": "999000"
},
"responseUrlPath": "data.play_url"
}
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 KiB

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 KiB

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

45
electron-builder.yml Normal file
View File

@@ -0,0 +1,45 @@
appId: com.electron.app
productName: electron-lan-file
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
asarUnpack:
- resources/**
win:
executableName: electron-lan-file
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: generic
url: https://example.com/auto-updates
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/

View File

@@ -1,27 +1,29 @@
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'electron-vite'; import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
import { resolve } from 'path'; import { resolve } from 'path';
import AutoImport from 'unplugin-auto-import/vite'; import AutoImport from 'unplugin-auto-import/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'; import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite';
import viteCompression from 'vite-plugin-compression'; import viteCompression from 'vite-plugin-compression';
import VueDevTools from 'vite-plugin-vue-devtools';
export default defineConfig({ export default defineConfig({
main: {}, main: {
preload: {}, plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: { renderer: {
resolve: { resolve: {
alias: { alias: {
'@': resolve('src/renderer'), '@': resolve('src/renderer'),
'@renderer': resolve('src/renderer'), '@renderer': resolve('src/renderer')
'@i18n': resolve('src/i18n')
} }
}, },
plugins: [ plugins: [
vue(), vue(),
viteCompression(), viteCompression(),
VueDevTools(), // VueDevTools(),
AutoImport({ AutoImport({
imports: [ imports: [
'vue', 'vue',
@@ -34,10 +36,25 @@ export default defineConfig({
resolvers: [NaiveUiResolver()] resolvers: [NaiveUiResolver()]
}) })
], ],
publicDir: resolve('resources'),
server: { server: {
host: '0.0.0.0', proxy: {
port: 2389 // 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}`), '')
}
}
} }
} }
}); });

View File

@@ -1,237 +0,0 @@
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,120 +1,88 @@
{ {
"name": "AlgerMusicPlayer", "name": "AlgerMusicPlayer",
"version": "5.1.0", "version": "3.9.1",
"description": "Alger Music Player", "description": "Alger Music Player",
"author": "Alger <algerkc@qq.com>", "author": "Alger <algerkc@qq.com>",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"homepage": "https://github.com/algerkong/AlgerMusicPlayer", "homepage": "https://github.com/algerkong/AlgerMusicPlayer",
"scripts": { "scripts": {
"prepare": "husky", "format": "prettier --write .",
"format": "prettier --write ./src", "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
"lint": "eslint ./src --fix && npm run lint:i18n",
"lint:i18n": "bun scripts/check_i18n.ts",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false", "typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web", "typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview", "start": "electron-vite preview",
"dev": "electron-vite dev", "dev": "electron-vite dev",
"dev:web": "vite dev", "build": "npm run typecheck && electron-vite build",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir", "build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win --publish never", "build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac --x64 --publish never && cp dist/latest-mac.yml dist/latest-mac-x64.yml && electron-builder --mac --arm64 --publish never && cp dist/latest-mac.yml dist/latest-mac-arm64.yml && node scripts/merge_latest_mac_yml.mjs dist/latest-mac-x64.yml dist/latest-mac-arm64.yml dist/latest-mac.yml", "build:mac": "npm run build && electron-builder --mac",
"build:mac:x64": "npm run build && electron-builder --mac --x64 --publish never && cp dist/latest-mac.yml dist/latest-mac-x64.yml", "build:linux": "npm run build && electron-builder --linux"
"build:mac:arm64": "npm run build && electron-builder --mac --arm64 --publish never && cp dist/latest-mac.yml dist/latest-mac-arm64.yml",
"build:linux": "npm run build && electron-builder --linux --publish never"
},
"lint-staged": {
"*.{ts,tsx,vue,js}": [
"eslint --fix"
],
"*.{ts,tsx,vue,js,css,scss,md,json}": [
"prettier --write"
]
}, },
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^3.0.0",
"@unblockneteasemusic/server": "^0.27.10", "@unblockneteasemusic/server": "^0.27.8-patch.1",
"cors": "^2.8.5", "electron-store": "^8.1.0",
"crypto-js": "^4.2.0", "electron-updater": "^6.1.7",
"electron-store": "^8.2.0", "font-list": "^1.5.1",
"electron-updater": "^6.6.2", "netease-cloud-music-api-alger": "^4.25.0"
"electron-window-state": "^5.0.3",
"express": "^4.22.1",
"file-type": "^21.1.1",
"flac-tagger": "^1.0.7",
"font-list": "^1.6.0",
"form-data": "^4.0.5",
"husky": "^9.1.7",
"jsencrypt": "^3.5.4",
"music-metadata": "^11.10.3",
"netease-cloud-music-api-alger": "^4.30.0",
"node-fetch": "^2.7.0",
"node-id3": "^0.2.9",
"node-machine-id": "^1.1.12",
"pinia-plugin-persistedstate": "^4.7.1",
"vue-i18n": "^11.2.2"
}, },
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^2.1.0", "@electron-toolkit/eslint-config": "^1.0.2",
"@electron-toolkit/eslint-config-ts": "^3.1.0", "@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1", "@electron-toolkit/tsconfig": "^1.0.1",
"@eslint/js": "^9.39.2", "@rushstack/eslint-patch": "^1.10.3",
"@rushstack/eslint-patch": "^1.15.0", "@tailwindcss/postcss7-compat": "^2.2.4",
"@types/howler": "^2.2.12", "@types/howler": "^2.2.12",
"@types/node": "^20.19.26", "@types/node": "^20.14.8",
"@types/node-fetch": "^2.6.13",
"@types/tinycolor2": "^1.4.6", "@types/tinycolor2": "^1.4.6",
"@typescript-eslint/eslint-plugin": "^8.49.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^8.49.0", "@typescript-eslint/parser": "^7.0.0",
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^5.0.5",
"@vue/compiler-sfc": "^3.5.25", "@vue/compiler-sfc": "^3.5.0",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^14.6.0", "@vue/eslint-config-typescript": "^13.0.0",
"@vue/runtime-core": "^3.5.25", "@vue/runtime-core": "^3.5.0",
"@vueuse/core": "^11.3.0", "@vueuse/core": "^11.0.3",
"@vueuse/electron": "^13.9.0", "@vueuse/electron": "^11.0.3",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"autoprefixer": "^10.4.22", "autoprefixer": "^10.4.20",
"axios": "^1.13.2", "axios": "^1.7.7",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"electron": "^40.1.0", "electron": "^34.0.0",
"electron-builder": "^26.0.12", "electron-builder": "^25.1.8",
"electron-vite": "^5.0.0", "electron-vite": "^2.3.0",
"eslint": "^9.39.2", "eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.32.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^10.6.2", "eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-vue-scoped-css": "^2.12.0", "eslint-plugin-vue": "^9.26.0",
"globals": "^16.5.0", "eslint-plugin-vue-scoped-css": "^2.7.2",
"howler": "^2.2.4", "howler": "^2.2.4",
"lint-staged": "^15.5.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"marked": "^15.0.12", "marked": "^15.0.4",
"naive-ui": "^2.43.2", "naive-ui": "^2.41.0",
"pinia": "^3.0.4", "postcss": "^8.4.49",
"pinyin-match": "^1.2.10", "prettier": "^3.3.2",
"postcss": "^8.5.6", "remixicon": "^4.2.0",
"prettier": "^3.7.4", "sass": "^1.83.4",
"remixicon": "^4.7.0", "tailwindcss": "^3.4.17",
"sass": "^1.96.0",
"tailwindcss": "^3.4.19",
"tinycolor2": "^1.6.0", "tinycolor2": "^1.6.0",
"tunajs": "^1.0.15", "typescript": "^5.5.2",
"typescript": "^5.9.3", "unplugin-auto-import": "^0.18.2",
"unplugin-auto-import": "^19.3.0", "unplugin-vue-components": "^0.27.4",
"unplugin-vue-components": "^28.8.0", "vfonts": "^0.1.0",
"vite": "^6.4.1", "vite": "^5.3.1",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-vue-devtools": "7.7.2", "vite-plugin-vue-devtools": "7.4.0",
"vue": "^3.5.25", "vue": "^3.5.13",
"vue-eslint-parser": "^10.2.0", "vue-router": "^4.5.0",
"vue-router": "^4.6.4", "vue-tsc": "^2.0.22",
"vue-tsc": "^2.2.12" "vuex": "^4.1.0"
}, },
"build": { "build": {
"appId": "com.alger.music", "appId": "com.alger.music",
@@ -126,20 +94,15 @@
"repo": "AlgerMusicPlayer" "repo": "AlgerMusicPlayer"
} }
], ],
"extraResources": [
{
"from": "resources/html",
"to": "html",
"filter": [
"**/*"
]
}
],
"mac": { "mac": {
"icon": "resources/icon.icns", "icon": "resources/icon.icns",
"target": [ "target": [
"dmg", {
"zip" "target": "dmg",
"arch": [
"universal"
]
}
], ],
"artifactName": "${productName}-${version}-mac-${arch}.${ext}", "artifactName": "${productName}-${version}-mac-${arch}.${ext}",
"darkModeSupport": true, "darkModeSupport": true,
@@ -147,12 +110,6 @@
"gatekeeperAssess": false, "gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist", "entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist", "entitlementsInherit": "build/entitlements.mac.plist",
"extendInfo": {
"NSMicrophoneUsageDescription": "AlgerMusicPlayer needs access to the microphone for audio visualization.",
"NSCameraUsageDescription": "Application requests access to the device's camera.",
"NSDocumentsFolderUsageDescription": "Application requests access to the user's Documents folder.",
"NSDownloadsFolderUsageDescription": "Application requests access to the user's Downloads folder."
},
"notarize": false, "notarize": false,
"identity": null, "identity": null,
"type": "distribution", "type": "distribution",
@@ -161,14 +118,13 @@
] ]
}, },
"win": { "win": {
"icon": "resources/icon.ico", "icon": "resources/favicon.ico",
"target": [ "target": [
{ {
"target": "nsis", "target": "nsis",
"arch": [ "arch": [
"x64", "x64",
"ia32", "ia32"
"arm64"
] ]
} }
], ],
@@ -181,22 +137,13 @@
{ {
"target": "AppImage", "target": "AppImage",
"arch": [ "arch": [
"x64", "x64"
"arm64"
] ]
}, },
{ {
"target": "deb", "target": "deb",
"arch": [ "arch": [
"x64", "x64"
"arm64"
]
},
{
"target": "rpm",
"arch": [
"x64",
"arm64"
] ]
} }
], ],
@@ -207,20 +154,12 @@
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"installerIcon": "resources/icon.ico", "installerIcon": "resources/favicon.ico",
"uninstallerIcon": "resources/icon.ico", "uninstallerIcon": "resources/favicon.ico",
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "AlgerMusicPlayer", "shortcutName": "AlgerMusicPlayer",
"include": "build/installer.nsh", "include": "build/installer.nsh"
"deleteAppDataOnUninstall": true,
"uninstallDisplayName": "AlgerMusicPlayer"
} }
},
"pnpm": {
"onlyBuiltDependencies": [
"electron",
"esbuild"
]
} }
} }

View File

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

View File

@@ -1,505 +0,0 @@
<!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) {
: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 {
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;
}
.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="status-bar">
<span id="status" class="status-message">准备就绪</span>
</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');
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";
}
// 更新播放状态
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>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

View File

@@ -1,251 +0,0 @@
import fs from 'fs';
import path from 'path';
import { pathToFileURL } from 'url';
type TranslationObject = Record<string, unknown>;
type KeyValueMap = Map<string, string>;
type KeyReference = {
file: string;
line: number;
key: string;
};
const SOURCE_LANG = 'zh-CN';
const TARGET_LANGS = ['en-US', 'ja-JP', 'ko-KR', 'zh-Hant'] as const;
const CHECK_EXTENSIONS = new Set(['.ts', '.vue']);
function isPlainObject(value: unknown): value is TranslationObject {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function flattenTranslations(
input: TranslationObject,
prefix = '',
output: KeyValueMap = new Map()
): KeyValueMap {
Object.entries(input).forEach(([key, value]) => {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (isPlainObject(value)) {
flattenTranslations(value, fullKey, output);
return;
}
output.set(fullKey, String(value ?? ''));
});
return output;
}
async function loadTranslationFile(filePath: string): Promise<TranslationObject | null> {
if (!fs.existsSync(filePath)) {
return null;
}
const moduleUrl = pathToFileURL(filePath).href;
const loaded = await import(moduleUrl);
const payload = loaded.default;
if (!isPlainObject(payload)) {
throw new Error(`翻译文件默认导出必须是对象: ${filePath}`);
}
return payload;
}
function walkFiles(dirPath: string): string[] {
const results: string[] = [];
if (!fs.existsSync(dirPath)) {
return results;
}
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
results.push(...walkFiles(fullPath));
continue;
}
if (entry.isFile() && CHECK_EXTENSIONS.has(path.extname(entry.name))) {
results.push(fullPath);
}
}
return results;
}
function getLineNumber(content: string, index: number): number {
let line = 1;
for (let i = 0; i < index; i += 1) {
if (content[i] === '\n') {
line += 1;
}
}
return line;
}
function collectReferencesFromContent(content: string, file: string): KeyReference[] {
const references: KeyReference[] = [];
const patterns = [
/\bt\(\s*['"`]([^'"`$]+)['"`]\s*[,)]/g,
/\bi18n\.global\.t\(\s*['"`]([^'"`$]+)['"`]\s*[,)]/g,
/\$t\(\s*['"`]([^'"`$]+)['"`]\s*[,)]/g
];
for (const pattern of patterns) {
let match: RegExpExecArray | null = pattern.exec(content);
while (match) {
references.push({
file,
line: getLineNumber(content, match.index),
key: match[1]
});
match = pattern.exec(content);
}
}
return references;
}
function collectTranslationReferences(projectRoot: string): KeyReference[] {
const scanDirs = ['src/renderer', 'src/main', 'src/preload'];
const references: KeyReference[] = [];
for (const scanDir of scanDirs) {
const absoluteDir = path.join(projectRoot, scanDir);
const files = walkFiles(absoluteDir);
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
references.push(...collectReferencesFromContent(content, path.relative(projectRoot, file)));
}
}
return references;
}
async function main() {
const projectRoot = process.cwd();
const langDir = path.join(projectRoot, 'src/i18n/lang');
const sourceDir = path.join(langDir, SOURCE_LANG);
const fileNames = fs
.readdirSync(sourceDir)
.filter((file) => file.endsWith('.ts'))
.sort();
const missingByLang: Record<string, Record<string, string[]>> = {};
const extraByLang: Record<string, Record<string, string[]>> = {};
const sourceKeys = new Set<string>();
const sourceValues = new Map<string, string>();
let hasBlockingIssue = false;
const strictMode = process.env.I18N_STRICT === '1';
for (const fileName of fileNames) {
const moduleName = fileName.replace(/\.ts$/, '');
const sourcePath = path.join(sourceDir, fileName);
const sourceObject = await loadTranslationFile(sourcePath);
if (!sourceObject) {
continue;
}
const sourceMap = flattenTranslations(sourceObject, moduleName);
const sourceMapKeys = new Set(sourceMap.keys());
sourceMap.forEach((value, key) => {
sourceKeys.add(key);
sourceValues.set(key, value);
});
for (const lang of TARGET_LANGS) {
if (!missingByLang[lang]) {
missingByLang[lang] = {};
}
if (!extraByLang[lang]) {
extraByLang[lang] = {};
}
const targetPath = path.join(langDir, lang, fileName);
const targetObject = await loadTranslationFile(targetPath);
const targetMap = targetObject
? flattenTranslations(targetObject, moduleName)
: new Map<string, string>();
const targetMapKeys = new Set(targetMap.keys());
const missing = Array.from(sourceMapKeys).filter((key) => !targetMapKeys.has(key));
const extra = Array.from(targetMapKeys).filter((key) => !sourceMapKeys.has(key));
if (missing.length > 0) {
missingByLang[lang][fileName] = missing;
hasBlockingIssue = true;
}
if (extra.length > 0) {
extraByLang[lang][fileName] = extra;
}
}
}
const allReferences = collectTranslationReferences(projectRoot);
const invalidReferences = allReferences.filter((item) => !sourceKeys.has(item.key));
const hasWarningIssue =
invalidReferences.length > 0 ||
Object.values(extraByLang).some((item) => Object.keys(item).length > 0);
const shouldFail = hasBlockingIssue || (strictMode && hasWarningIssue);
if (hasBlockingIssue || hasWarningIssue) {
console.error('发现国际化问题:');
for (const lang of TARGET_LANGS) {
const missingFiles = missingByLang[lang];
const extraFiles = extraByLang[lang];
const hasLangIssue =
Object.keys(missingFiles).length > 0 || Object.keys(extraFiles).length > 0;
if (!hasLangIssue) {
continue;
}
console.error(`\n语言: ${lang}`);
for (const fileName of Object.keys(missingFiles)) {
console.error(` 文件: ${fileName}`);
for (const key of missingFiles[fileName]) {
const sourceValue = sourceValues.get(key) ?? '';
console.error(` - 缺失键 [${key}]${sourceValue}`);
}
}
for (const fileName of Object.keys(extraFiles)) {
console.error(` 文件: ${fileName}`);
for (const key of extraFiles[fileName]) {
console.error(` - 多余键 [${key}]`);
}
}
}
if (invalidReferences.length > 0) {
console.error('\n代码中引用了不存在的 i18n key:');
for (const item of invalidReferences) {
console.error(` - ${item.file}:${item.line} -> ${item.key}`);
}
}
if (strictMode && hasWarningIssue && !hasBlockingIssue) {
console.error('\n当前为严格模式告警将导致失败I18N_STRICT=1。');
}
}
if (shouldFail) {
process.exit(1);
}
if (!hasBlockingIssue && !hasWarningIssue) {
console.log('所有国际化键值检查通过!');
return;
}
console.log('国际化检查通过(含告警,建议尽快修复)');
}
main().catch((error) => {
console.error('国际化检查执行失败:', error);
process.exit(1);
});

View File

@@ -1,130 +0,0 @@
import fs from 'fs';
import path from 'path';
async function main() {
const rootDir = process.cwd();
const langDir = path.join(rootDir, 'src/i18n/lang/zh-CN');
const definedKeys = new Set<string>();
const langFiles = fs.readdirSync(langDir).filter((f) => f.endsWith('.ts'));
function getKeys(obj: any, prefix = '') {
for (const key in obj) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
getKeys(obj[key], fullKey);
} else {
definedKeys.add(fullKey);
}
}
}
for (const file of langFiles) {
const content = fs.readFileSync(path.join(langDir, file), 'utf-8');
const match = content.match(/export\s+default\s+([\s\S]+);/);
if (match) {
try {
const obj = eval(`(${match[1]})`);
getKeys(obj, file.replace('.ts', ''));
} catch (error) {
console.warn('Failed to parse i18n file:', file, error);
}
}
}
// @ts-ignore
const glob = new Bun.Glob('src/renderer/**/*.{vue,ts,js}');
// @ts-ignore
const files = Array.from(
glob.scanSync({
cwd: rootDir,
onlyFiles: true
})
);
const report = {
hardcodedChinese: [] as any[],
missingKeys: [] as any[]
};
const chineseMatchRegex = /[\u4e00-\u9fa5]+/g;
const i18nRegex = /\bt\(['"]([^'"]+)['"]\)/g;
for (const relativeFile of files) {
const rel = relativeFile as string;
if (
rel.includes('node_modules') ||
rel.includes('android/') ||
rel.includes('resources/') ||
rel.includes('scripts/') ||
rel.endsWith('.d.ts')
)
continue;
const file = path.join(rootDir, rel);
let content = fs.readFileSync(file, 'utf-8');
content = content.replace(/\/\*[\s\S]*?\*\//g, (match) => {
const lines = match.split('\n').length - 1;
return '\n'.repeat(lines);
});
content = content.replace(/<!--[\s\S]*?-->/g, (match) => {
const lines = match.split('\n').length - 1;
return '\n'.repeat(lines);
});
const lines = content.split('\n');
let isInConsole = false;
lines.forEach((line, index) => {
const lineNumber = index + 1;
const cleanLine = line.split('//')[0];
if (cleanLine.includes('console.')) {
isInConsole = true;
}
if (!isInConsole && !cleanLine.includes('import')) {
const chineseMatches = cleanLine.match(chineseMatchRegex);
if (chineseMatches) {
chineseMatches.forEach((text) => {
report.hardcodedChinese.push({
file: rel,
line: lineNumber,
text: text.trim(),
context: line.trim()
});
});
}
}
if (isInConsole && cleanLine.includes(');')) {
isInConsole = false;
}
let i18nMatch;
while ((i18nMatch = i18nRegex.exec(cleanLine)) !== null) {
const key = i18nMatch[1];
if (!definedKeys.has(key)) {
report.missingKeys.push({
file: rel,
line: lineNumber,
key: key,
context: line.trim()
});
}
}
});
}
const outputPath = path.join(rootDir, 'i18n_report.json');
fs.writeFileSync(outputPath, JSON.stringify(report, null, 2));
console.log(`\n报告生成成功`);
console.log(`- 硬编码中文: ${report.hardcodedChinese.length}`);
console.log(`- 缺失的 Key: ${report.missingKeys.length}`);
console.log(`- 报告路径: ${outputPath}\n`);
}
main();

View File

@@ -1,119 +0,0 @@
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path';
function readScalar(line, prefix) {
return line.slice(prefix.length).trim().replace(/^'/, '').replace(/'$/, '');
}
function parseLatestMacYml(filePath) {
const content = readFileSync(filePath, 'utf8');
const lines = content.split(/\r?\n/);
const result = {
version: '',
files: [],
path: '',
sha512: '',
releaseDate: ''
};
let currentFile = null;
for (const line of lines) {
if (!line.trim()) {
continue;
}
if (line.startsWith('version: ')) {
result.version = readScalar(line, 'version: ');
continue;
}
if (line.startsWith('path: ')) {
result.path = readScalar(line, 'path: ');
continue;
}
if (line.startsWith('sha512: ')) {
result.sha512 = readScalar(line, 'sha512: ');
continue;
}
if (line.startsWith('releaseDate: ')) {
result.releaseDate = readScalar(line, 'releaseDate: ');
continue;
}
if (line.startsWith(' - url: ')) {
currentFile = {
url: readScalar(line, ' - url: ')
};
result.files.push(currentFile);
continue;
}
if (line.startsWith(' sha512: ') && currentFile) {
currentFile.sha512 = readScalar(line, ' sha512: ');
continue;
}
if (line.startsWith(' size: ') && currentFile) {
currentFile.size = Number.parseInt(readScalar(line, ' size: '), 10);
}
}
return result;
}
function uniqueFiles(files) {
const fileMap = new Map();
for (const file of files) {
fileMap.set(file.url, file);
}
return Array.from(fileMap.values());
}
function stringifyLatestMacYml(data) {
const lines = [`version: ${data.version}`, 'files:'];
for (const file of data.files) {
lines.push(` - url: ${file.url}`);
lines.push(` sha512: ${file.sha512}`);
lines.push(` size: ${file.size}`);
}
lines.push(`path: ${data.path}`);
lines.push(`sha512: ${data.sha512}`);
lines.push(`releaseDate: '${data.releaseDate}'`);
return `${lines.join('\n')}\n`;
}
const [x64Path, arm64Path, outputPath] = process.argv.slice(2);
if (!x64Path || !arm64Path || !outputPath) {
console.error(
'Usage: node scripts/merge_latest_mac_yml.mjs <latest-mac-x64.yml> <latest-mac-arm64.yml> <output.yml>'
);
process.exit(1);
}
const x64Data = parseLatestMacYml(x64Path);
const arm64Data = parseLatestMacYml(arm64Path);
if (x64Data.version !== arm64Data.version) {
console.error(
`Version mismatch between mac update files: ${x64Data.version} !== ${arm64Data.version}`
);
process.exit(1);
}
const mergedData = {
...x64Data,
files: uniqueFiles([...x64Data.files, ...arm64Data.files]),
releaseDate: arm64Data.releaseDate || x64Data.releaseDate
};
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, stringifyLatestMacYml(mergedData), 'utf8');

View File

@@ -1,5 +0,0 @@
export default {
hotSongs: 'Hot Songs',
albums: 'Albums',
description: 'Artist Introduction'
};

View File

@@ -1,62 +0,0 @@
export default {
play: 'Play',
next: 'Next',
previous: 'Previous',
volume: 'Volume',
settings: 'Settings',
search: 'Search',
loading: 'Loading...',
loadingMore: 'Loading more...',
alipay: 'Alipay',
wechat: 'WeChat Pay',
on: 'On',
off: 'Off',
show: 'Show',
hide: 'Hide',
confirm: 'Confirm',
cancel: 'Cancel',
clear: 'Clear',
configure: 'Configure',
open: 'Open',
modify: 'Modify',
success: 'Operation Successful',
error: 'Operation Failed',
warning: 'Warning',
info: 'Info',
save: 'Save',
delete: 'Delete',
refresh: 'Refresh',
retry: 'Retry',
reset: 'Reset',
loadFailed: 'Load Failed',
noData: 'No data',
back: 'Back',
copySuccess: 'Copied to clipboard',
copyFailed: 'Copy failed',
validation: {
required: 'This field is required',
invalidInput: 'Invalid input',
selectRequired: 'Please select an option',
numberRange: 'Please enter a number between {min} and {max}'
},
viewMore: 'View More',
noMore: 'No more',
selectAll: 'Select All',
playAll: 'Play All',
expand: 'Expand',
collapse: 'Collapse',
songCount: '{count} songs',
language: 'Language',
today: 'Today',
yesterday: 'Yesterday',
tray: {
show: 'Show',
quit: 'Quit',
playPause: 'Play/Pause',
prev: 'Previous',
next: 'Next',
pause: 'Pause',
play: 'Play',
favorite: 'Favorite'
}
};

View File

@@ -1,337 +0,0 @@
export default {
more: 'More',
homeListItem: {
loading: 'Loading...'
},
installApp: {
description: 'Install the application for a better experience',
noPrompt: 'Do not prompt again',
install: 'Install now',
cancel: 'Cancel',
download: 'Download',
downloadFailed: 'Download failed',
downloadComplete: 'Download complete',
downloadProblem: 'Download problem? Go to',
downloadProblemLinkText: 'Download the latest version'
},
playlistDrawer: {
title: 'Add to playlist',
createPlaylist: 'Create new playlist',
cancelCreate: 'Cancel create',
create: 'Create',
playlistName: 'Playlist name',
privatePlaylist: 'Private playlist',
publicPlaylist: 'Public playlist',
createSuccess: 'Playlist created successfully',
createFailed: 'Playlist creation failed',
addSuccess: 'Song added successfully',
addFailed: 'Song addition failed',
private: 'Private',
public: 'Public',
count: 'songs',
loginFirst: 'Please login first',
getPlaylistFailed: 'Get playlist failed',
inputPlaylistName: 'Please enter the playlist name'
},
update: {
title: 'New version found',
currentVersion: 'Current version',
cancel: 'Do not update',
checking: 'Checking for updates...',
prepareDownload: 'Preparing to download...',
downloading: 'Downloading...',
readyToInstall: 'The update package is ready to install',
nowUpdate: 'Update now',
downloadFailed: 'Download failed, please try again or download manually',
startFailed: 'Start download failed, please try again or download manually',
autoUpdateFailed: 'Automatic update failed',
openOfficialSite: 'Open official download page',
manualFallbackHint:
'If automatic update fails, you can download the latest version from the official release page.',
noDownloadUrl:
'No suitable installation package found for the current system, please download manually',
installConfirmTitle: 'Install Update',
installConfirmContent: 'Do you want to close the application and install the update?',
manualInstallTip:
'If the installer does not open automatically after closing the application, please find the file in your download folder and open it manually.',
yesInstall: 'Install Now',
noThanks: 'Later',
fileLocation: 'File Location',
copy: 'Copy Path',
copySuccess: 'Path copied to clipboard',
copyFailed: 'Copy failed',
backgroundDownload: 'Background Download'
},
disclaimer: {
title: 'Terms of Use',
warning:
'This application is a development test version. Functions are not yet perfect, and there may be many problems and bugs. It is for learning and exchange only.',
item1:
'This application is for personal learning, research and technical exchange only. Please do not use it for any commercial purposes.',
item2:
'Please delete it within 24 hours after downloading. If you need to use it for a long time, please support the genuine music service.',
item3:
'By using this application, you understand and assume the relevant risks. The developer is not responsible for any loss.',
agree: 'I have read and agree',
disagree: 'Disagree and Exit'
},
donate: {
title: 'Support Developer',
subtitle: 'Your support is my motivation',
tip: 'Donation is completely voluntary. All functions can be used normally without donation. Thank you for your understanding and support!',
wechat: 'WeChat',
alipay: 'Alipay',
wechatQR: 'WeChat QR Code',
alipayQR: 'Alipay QR Code',
scanTip: 'Please use your phone to scan the QR code above to donate',
enterApp: 'Enter App',
noForce: 'No forced donation, click to enter'
},
coffee: {
title: 'Buy me a coffee',
alipay: 'Alipay',
wechat: 'Wechat',
alipayQR: 'Alipay QR code',
wechatQR: 'Wechat QR code',
coffeeDesc: 'A cup of coffee, a support',
coffeeDescLinkText: 'View more',
groupText: 'Wechat Public Account: AlgerMusic',
messages: {
copySuccess: 'Copied to clipboard'
},
donateList: 'Buy me a coffee'
},
playlistType: {
title: 'Playlist Category',
showAll: 'Show all',
hide: 'Hide some'
},
recommendAlbum: {
title: 'Latest Album'
},
recommendSinger: {
title: 'Daily Recommendation',
songlist: 'Daily Recommendation List'
},
recommendSonglist: {
title: 'Weekly Hot Music',
empty: 'No playlists available'
},
dailyRecommend: {
title: 'Daily Recommendation',
badge: 'Recommended',
empty: 'No recommended songs',
intelligenceHint: 'Turn on Intelligence Mode to discover more music you love'
},
recommendMV: {
title: 'Recommended MVs'
},
newAlbum: {
title: 'Albums',
empty: 'No new albums'
},
recommendNewMusic: {
title: 'New Songs'
},
privateContent: {
title: 'Exclusive Content'
},
djProgram: {
title: 'Recommended Radio'
},
homeHero: {
dailyRecommend: 'Daily Recommend',
songs: 'Songs',
playNow: 'Play Now',
intelligenceMode: 'Intelligence Mode',
intelligenceModeOn: 'On Air',
intelligenceModeDesc: 'Start smart recommendation',
intelligenceModeActiveDesc: 'Smart recommendations based on your taste',
startIntelligence: 'Start',
stopIntelligence: 'Stop',
playing: 'Playing',
toplistDesc: 'Trending now',
mvDesc: 'Music videos',
playlistDesc: 'Curated playlists',
personalFm: 'Personal FM',
discoverMusic: 'Discover Music',
personalFmDesc: 'Based on your taste',
recentPlays: 'Recent Plays',
viewAll: 'View All',
followedArtists: 'Followed Artists',
newSongs: ' new songs',
fromFollowedArtists: 'From artists you follow',
recommendNewMusic: 'New Music',
newSongExpress: 'New Releases',
discoverNewReleases: 'Discover the latest releases',
hotPlaylists: 'Hot Playlists',
hotArtists: 'Hot Artists',
hotArtistsTitle: 'Popular Artists',
hotArtistsDesc: 'Most popular artists right now',
fmTrash: 'Dislike',
fmNext: 'Next',
quickNav: {
myFavorite: 'My Favorites',
playHistory: 'History',
myProfile: 'My Profile',
toplist: 'Top Charts'
}
},
searchBar: {
login: 'Login',
toLogin: 'To Login',
logout: 'Logout',
set: 'Settings',
theme: 'Theme',
restart: 'Restart',
refresh: 'Refresh',
currentVersion: 'Current Version',
searchPlaceholder: 'Search for something...',
zoom: 'Zoom',
zoom100: 'Zoom 100%',
resetZoom: 'Reset Zoom',
zoomDefault: 'Default Zoom',
tabPlaylist: 'Playlist',
tabMv: 'MV',
tabCharts: 'Charts',
cancelSearch: 'Cancel',
intelligenceMode: 'Intelligence Mode',
exitIntelligence: 'Exit Intelligence Mode'
},
titleBar: {
closeTitle: 'Choose how to close',
minimizeToTray: 'Minimize to Tray',
exitApp: 'Exit App',
rememberChoice: 'Remember my choice',
closeApp: 'Close App'
},
userPlayList: {
title: "{name}'s Playlist"
},
musicList: {
searchSongs: 'Search Songs',
noSearchResults: 'No search results',
switchToNormal: 'Switch to normal layout',
switchToCompact: 'Switch to compact layout',
playAll: 'Play All',
collect: 'Collect',
collectSuccess: 'Collect Success',
cancelCollectSuccess: 'Cancel Collect Success',
cancelCollect: 'Cancel Collect',
addToPlaylist: 'Add to Playlist',
addToPlaylistSuccess: 'Add to Playlist Success',
operationFailed: 'Operation Failed',
songsAlreadyInPlaylist: 'Songs already in playlist',
locateCurrent: 'Locate current song',
historyRecommend: 'Daily History',
fetchDatesFailed: 'Failed to fetch dates',
fetchSongsFailed: 'Failed to fetch songs',
noSongs: 'No songs'
},
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',
options: 'Options',
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',
album: 'Album',
localMusic: 'Local Music',
pages: {
toplist: {
desc: 'The most authoritative music charts, discover the hottest music'
},
mv: {
desc: 'Explore amazing video content',
loadingMore: 'Loading more...',
noMore: '— All content loaded —',
area: {
all: 'All',
mainland: 'Mainland',
hktw: 'HK/TW',
western: 'Western',
japan: 'Japan',
korea: 'Korea'
}
},
list: {
desc: 'Discover more great playlists',
dailyRecommend: 'Daily Picks'
},
search: {
desc: 'Explore the hottest search trends'
},
album: {
area: {
all: 'All',
chinese: 'Chinese',
western: 'Western',
korea: 'Korea',
japan: 'Japan'
}
}
}
};

View File

@@ -1,9 +0,0 @@
export default {
description:
'Your donation will be used to support development and maintenance work, including but not limited to server maintenance, domain name renewal, etc.',
message: 'You can leave your email or github name when leaving a message.',
refresh: 'Refresh List',
toDonateList: 'Buy me a coffee',
title: 'Donation List',
noMessage: 'No Message'
};

View File

@@ -1,114 +0,0 @@
export default {
title: 'Download Manager',
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',
noDownloadedHint: 'Download your favorite songs to listen offline'
},
progress: {
total: 'Total Progress: {progress}%'
},
status: {
downloading: 'Downloading',
completed: 'Completed',
failed: 'Failed',
unknown: 'Unknown',
queued: 'Queued',
paused: 'Paused',
cancelled: 'Cancelled'
},
action: {
pause: 'Pause',
resume: 'Resume',
cancel: 'Cancel',
cancelAll: 'Cancel All',
retrying: 'Re-resolving URL...'
},
batch: {
complete: 'Download complete: {success}/{total} songs succeeded',
allComplete: 'All downloads complete'
},
artist: {
unknown: 'Unknown Artist'
},
delete: {
title: 'Delete Confirmation',
message: 'Are you sure you want to delete "{filename}"? This action cannot be undone.',
confirm: 'Delete',
cancel: 'Cancel',
success: 'Successfully deleted',
failed: 'Failed to delete',
fileNotFound: 'File not found or moved, removed from records',
recordRemoved: 'Failed to delete file, but removed from records'
},
clear: {
title: 'Clear Download Records',
message:
'Are you sure you want to clear all download records? This will not delete the actual music files, but will clear all records.',
confirm: 'Clear',
cancel: 'Cancel',
success: 'Download records cleared',
failed: 'Failed to clear download records'
},
message: {
downloadComplete: '{filename} download completed',
downloadFailed: '{filename} download failed: {error}'
},
loading: 'Loading...',
playStarted: 'Play started: {name}',
playFailed: 'Play failed: {name}',
path: {
copy: 'Copy 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',
saveLyric: 'Save Lyrics File',
saveLyricDesc: 'Save a separate .lrc lyrics file alongside the downloaded song',
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:',
concurrency: 'Max Concurrent',
concurrencyDesc: 'Maximum number of simultaneous downloads (1-5)',
saveSuccess: 'Download settings saved',
presets: {
songArtist: 'Song - Artist',
artistSong: 'Artist - Song',
songOnly: 'Song only'
},
components: {
songName: 'Song name',
artistName: 'Artist name',
albumName: 'Album name'
}
},
error: {
incomplete: 'File download incomplete',
urlExpired: 'URL expired, re-resolving',
resumeFailed: 'Resume failed'
}
};

View File

@@ -1,13 +0,0 @@
export default {
title: 'Favorites',
count: 'Total {count}',
batchDownload: 'Batch Download',
download: 'Download ({count})',
emptyTip: 'No favorite songs yet',
downloadSuccess: 'Download completed',
downloadFailed: 'Download failed',
downloading: 'Downloading, please wait...',
selectSongsFirst: 'Please select songs to download first',
descending: 'Descending',
ascending: 'Ascending'
};

View File

@@ -1,49 +0,0 @@
export default {
title: 'Play History',
heatmapTitle: 'Heatmap',
playCount: '{count}',
getHistoryFailed: 'Failed to get play history',
categoryTabs: {
songs: 'Songs',
playlists: 'Playlists',
albums: 'Albums',
podcasts: 'Podcasts'
},
podcastTabs: {
episodes: 'Episodes',
radios: 'Radios'
},
tabs: {
all: 'All Records',
local: 'Local Records',
cloud: 'Cloud Records'
},
getCloudRecordFailed: 'Failed to get cloud records',
needLogin: 'Please login with cookie to view cloud records',
merging: 'Merging records...',
noDescription: 'No description',
noData: 'No records',
heatmap: {
title: 'Play Heatmap',
loading: 'Loading data...',
unit: 'plays',
footerText: 'Hover to view details',
playCount: 'Played {count} times',
topSongs: 'Top songs of the day',
times: 'times',
totalPlays: 'Total Plays',
activeDays: 'Active Days',
noData: 'No play records',
colorTheme: 'Color Theme',
colors: {
green: 'Green',
blue: 'Blue',
orange: 'Orange',
purple: 'Purple',
red: 'Red'
},
mostPlayedSong: 'Most Played Song',
mostActiveDay: 'Most Active Day',
latestNightSong: 'Latest Night Song'
}
};

View File

@@ -1,13 +0,0 @@
export default {
title: 'Local Music',
scanFolder: 'Scan Folder',
removeFolder: 'Remove Folder',
scanning: 'Scanning...',
scanComplete: 'Scan Complete',
playAll: 'Play All',
search: 'Search local music',
emptyState: 'No local music found. Please select a folder to scan.',
fileNotFound: 'File not found or has been moved',
rescan: 'Rescan',
songCount: '{count} songs'
};

View File

@@ -1,64 +0,0 @@
export default {
title: {
qr: 'QR Code 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',
cookie: 'Please enter NetEase Cloud Music Cookie (token)',
uid: 'Please enter User ID (UID)'
},
button: {
login: 'Login',
switchToQr: 'QR Code 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',
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

@@ -1,148 +0,0 @@
export default {
nowPlaying: 'Now Playing',
playlist: 'Playlist',
lyrics: 'Lyrics',
previous: 'Previous',
play: 'Play',
pause: 'Pause',
next: 'Next',
volumeUp: 'Volume Up',
volumeDown: 'Volume Down',
mute: 'Mute',
unmute: 'Unmute',
songNum: 'Song Number: {num}',
addCorrection: 'Add {num} seconds',
subtractCorrection: 'Subtract {num} seconds',
playFailed: 'Play Failed, Play Next Song',
parseFailedPlayNext: 'Song parsing failed, playing next',
consecutiveFailsError:
'Playback error, possibly due to network issues or invalid source. Please switch playlist or try again later',
playListEnded: 'Reached the end of the playlist',
autoResumed: 'Playback resumed automatically',
resumeFailed: 'Failed to resume playback, please try manually',
playMode: {
sequence: 'Sequence',
loop: 'Loop',
random: 'Random'
},
fullscreen: {
enter: 'Enter Fullscreen',
exit: 'Exit Fullscreen'
},
close: 'Close',
modeHint: {
single: 'Single',
list: 'Next'
},
lrc: {
noLrc: 'No lyrics, please enjoy',
noAutoScroll: 'This lyrics does not support auto-scroll'
},
reparse: {
title: 'Select Music Source',
desc: 'Click a source to directly reparse the current song. This source will be used next time this song plays.',
success: 'Reparse successful',
failed: 'Reparse failed',
warning: 'Please select a music source',
bilibiliNotSupported: 'Bilibili videos do not support reparsing',
processing: 'Processing...',
clear: 'Clear Custom Source',
customApiFailed: 'Custom API parsing failed, trying built-in sources...',
customApiError: 'Custom API request error, trying built-in sources...'
},
playBar: {
expand: 'Expand Lyrics',
collapse: 'Collapse Lyrics',
like: 'Like',
lyric: 'Lyric',
noSongPlaying: 'No song playing',
eq: 'Equalizer',
playList: 'Play List',
reparse: 'Reparse',
miniPlayBar: 'Mini Play Bar',
playMode: {
sequence: 'Sequence',
loop: 'Loop',
random: 'Random'
},
play: 'Play',
pause: 'Pause',
prev: 'Previous',
next: 'Next',
volume: 'Volume',
favorite: 'Favorite {name}',
unFavorite: 'Unfavorite {name}',
playbackSpeed: 'Playback Speed',
advancedControls: 'Advanced Controls',
intelligenceMode: {
title: 'Intelligence Mode',
needCookieLogin: 'Please login with Cookie method to use Intelligence Mode',
noFavoritePlaylist: 'Favorite playlist not found',
noLikedSongs: 'You have no liked songs yet',
loading: 'Loading Intelligence Mode',
success: 'Loaded {count} songs',
failed: 'Failed to get Intelligence Mode list',
error: 'Intelligence Mode error'
}
},
eq: {
title: 'Equalizer',
reset: 'Reset',
on: 'On',
off: 'Off',
bass: 'Bass',
midrange: 'Midrange',
treble: 'Treble',
presets: {
flat: 'Flat',
pop: 'Pop',
rock: 'Rock',
classical: 'Classical',
jazz: 'Jazz',
electronic: 'Electronic',
hiphop: 'Hip-Hop',
rb: 'R&B',
metal: 'Metal',
vocal: 'Vocal',
dance: 'Dance',
acoustic: 'Acoustic',
custom: 'Custom'
}
},
// Playback settings
settings: {
title: 'Playback Settings',
playbackSpeed: 'Playback Speed'
},
// Sleep timer related
sleepTimer: {
title: 'Sleep Timer',
cancel: 'Cancel Timer',
timeMode: 'By Time',
songsMode: 'By Songs',
playlistEnd: 'After Playlist',
afterPlaylist: 'After Playlist Ends',
activeUntilEnd: 'Active until end of playlist',
minutes: 'min',
hours: 'hr',
songs: 'songs',
set: 'Set',
timerSetSuccess: 'Timer set for {minutes} minutes',
songsSetSuccess: 'Timer set for {songs} songs',
playlistEndSetSuccess: 'Timer set to end after playlist',
timerCancelled: 'Sleep timer cancelled',
timerEnded: 'Sleep timer ended',
playbackStopped: 'Music playback stopped',
minutesRemaining: '{minutes} min remaining',
songsRemaining: '{count} songs remaining'
},
playList: {
clearAll: 'Clear Playlist',
alreadyEmpty: 'Playlist is already empty',
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?'
}
};

View File

@@ -1,40 +0,0 @@
export default {
podcast: 'Podcast',
mySubscriptions: 'My Subscriptions',
discover: 'Discover',
categories: 'Categories',
todayPerfered: "Today's Picks",
recommended: 'Recommended',
hotRanking: 'Hot',
newRanking: 'New',
subscribeCount: 'Subscribers',
programCount: 'Episodes',
subscribe: 'Subscribe',
subscribed: 'Subscribed',
unsubscribe: 'Unsubscribe',
unsubscribed: 'Unsubscribed',
subscribeSuccess: 'Subscribed successfully',
unsubscribeFailed: 'Failed to unsubscribe',
subscribeFailed: 'Failed to subscribe',
radioDetail: 'Radio Detail',
programList: 'Episodes',
playProgram: 'Play',
recentPlayed: 'Recently Played',
listeners: 'Listeners',
noSubscriptions: 'No subscriptions',
goDiscover: 'Discover Podcasts',
searchPodcast: 'Search Podcast',
category: 'Category',
all: 'All',
dj: 'DJ',
episodes: 'Eps',
playAll: 'Play All',
popularCategories: 'Popular Categories',
allCategories: 'All Categories',
categoryRadios: 'Category Radios',
exploreCategoryRadios: 'Explore more amazing radios',
hotRadios: 'Hot Radios',
noCategoryRadios: 'No radios in this category',
searchPlaceholder: 'Search podcasts, episodes...',
searchResults: 'Search Results'
};

View File

@@ -1,32 +0,0 @@
export default {
title: {
hotSearch: 'Hot Search',
searchList: 'Search Results',
searchHistory: 'Search History'
},
button: {
clear: 'Clear',
back: 'Back',
playAll: 'Play All'
},
loading: {
more: 'Loading...',
failed: 'Search failed',
searching: 'Searching...'
},
noMore: 'No more results',
error: {
searchFailed: 'Search failed'
},
search: {
single: 'Single',
album: 'Album',
playlist: 'Playlist',
mv: 'MV',
djradio: 'Podcast',
bilibili: 'Bilibili'
},
history: 'Search History',
hot: 'Hot Searches',
suggestions: 'Search Suggestions'
};

View File

@@ -1,521 +0,0 @@
export default {
theme: 'Theme',
language: 'Language',
regard: 'About',
logout: 'Logout',
sections: {
basic: 'Basic Settings',
playback: 'Playback Settings',
application: 'Application Settings',
network: 'Network Settings',
system: 'System Management',
donation: 'Donation',
about: 'About'
},
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: {
global: 'Global',
lyric: 'Lyrics Only'
},
animation: 'Animation Speed',
animationDesc: 'Enable/disable animations',
animationSpeed: {
slow: 'Very Slow',
normal: 'Normal',
fast: 'Very Fast'
},
fontPreview: {
title: 'Font Preview',
chinese: 'Chinese',
english: 'English',
japanese: 'Japanese',
korean: 'Korean',
chineseText: '静夜思 床前明月光 疑是地上霜',
englishText: 'The quick brown fox jumps over the lazy dog',
japaneseText: 'あいうえお かきくけこ さしすせそ',
koreanText: '가나다라마 바사아자차 카타파하'
},
gpuAcceleration: 'GPU Acceleration',
gpuAccelerationDesc:
'Enable or disable hardware acceleration, can improve rendering performance but may increase GPU load',
gpuAccelerationRestart:
'Changing GPU acceleration settings requires application restart to take effect',
gpuAccelerationChangeSuccess:
'GPU acceleration settings updated, restart application to take effect',
gpuAccelerationChangeError: 'Failed to update GPU acceleration settings',
tabletMode: 'Tablet Mode',
tabletModeDesc: 'Enabling tablet mode allows using PC-style interface on mobile devices'
},
playback: {
quality: 'Audio Quality',
qualityDesc: 'Select music playback quality (VIP)',
qualityOptions: {
standard: 'Standard',
higher: 'Higher',
exhigh: 'Extreme',
lossless: 'Lossless',
hires: 'Hi-Res',
jyeffect: 'HD Surround',
sky: 'Immersive',
dolby: 'Dolby Atmos',
jymaster: 'Master'
},
musicSources: 'Music Sources',
musicSourcesDesc: 'Select music sources for song resolution',
musicSourcesWarning: 'At least one music source must be selected',
musicUnblockEnable: 'Enable Music Unblocking',
musicUnblockEnableDesc: 'When enabled, attempts to resolve unplayable songs',
configureMusicSources: 'Configure Sources',
selectedMusicSources: 'Selected sources:',
noMusicSources: 'No sources selected',
gdmusicInfo:
'GD Music Station intelligently resolves music from multiple platforms automatically',
autoPlay: 'Auto Play',
autoPlayDesc: 'Auto resume playback when reopening the app',
audioDevice: 'Audio Output Device',
audioDeviceDesc: 'Select audio output device such as speakers, headphones or Bluetooth devices',
testAudio: 'Test',
selectAudioDevice: 'Select output device',
showStatusBar: 'Show Status Bar',
showStatusBarContent:
'You can display the music control function in your mac status bar (effective after a restart)',
fallbackParser: 'Fallback Parser (GD Music)',
fallbackParserDesc:
'When "GD Music" is checked and regular sources fail, this service will be used.',
parserGD: 'GD Music (Built-in)',
parserCustom: 'Custom API',
// Source labels
sourceLabels: {
migu: 'Migu',
kugou: 'Kugou',
kuwo: 'Kuwo',
pyncmd: 'NetEase (Built-in)',
qq: 'QQ Music',
joox: 'JOOX',
bilibili: 'Bilibili',
gdmusic: 'GD Music',
lxMusic: 'LX Music',
custom: 'Custom API'
},
customApi: {
sectionTitle: 'Custom API Settings',
importConfig: 'Import JSON Config',
currentSource: 'Current Source',
notImported: 'No custom source imported yet.',
importSuccess: 'Successfully imported source: {name}',
importFailed: 'Import failed: {message}',
enableHint: 'Import a JSON config file to enable',
status: {
imported: 'Custom Source Imported',
notImported: 'Not Imported'
}
},
lxMusic: {
tabs: {
sources: 'Source Selection',
lxMusic: 'LX Music',
customApi: 'Custom API'
},
scripts: {
title: 'Imported Scripts',
importLocal: 'Import Local',
importOnline: 'Import Online',
urlPlaceholder: 'Enter LX Music Script URL',
importBtn: 'Import',
empty: 'No imported LX Music scripts',
notConfigured: 'Not configured (Configure in LX Music Tab)',
importHint: 'Import compatible custom API plugins to extend sources',
noScriptWarning: 'Please import LX Music script first',
noSelectionWarning: 'Please select an LX Music source first',
notFound: 'Source not found',
switched: 'Switched to source: {name}',
deleted: 'Deleted source: {name}',
enterUrl: 'Please enter script URL',
invalidUrl: 'Invalid URL format',
invalidScript: 'Invalid LX Music script, globalThis.lx code not found',
nameRequired: 'Name cannot be empty',
renameSuccess: 'Rename successful'
}
}
},
application: {
closeAction: 'Close Action',
closeActionDesc: 'Choose action when closing window',
closeOptions: {
ask: 'Ask Every Time',
minimize: 'Minimize to Tray',
close: 'Exit Directly'
},
shortcut: 'Shortcut Settings',
shortcutDesc: 'Customize global shortcuts',
download: 'Download Management',
downloadDesc: 'Always show download list button',
unlimitedDownload: 'Unlimited Download',
unlimitedDownloadDesc: 'Enable unlimited download mode for music , default limit 300 songs',
downloadPath: 'Download Directory',
downloadPathDesc: 'Choose download location for music files',
remoteControl: 'Remote Control',
remoteControlDesc: 'Set remote control function'
},
network: {
apiPort: 'Music API Port',
apiPortDesc: 'Restart required after modification',
proxy: 'Proxy Settings',
proxyDesc: 'Enable proxy when unable to access music',
proxyHost: 'Proxy Host',
proxyHostPlaceholder: 'Enter proxy host',
proxyPort: 'Proxy Port',
proxyPortPlaceholder: 'Enter proxy port',
realIP: 'RealIP Settings',
realIPDesc: 'Use realIP parameter with mainland China IP to resolve access restrictions abroad',
messages: {
proxySuccess: 'Proxy settings saved, restart required to take effect',
proxyError: 'Please check your input',
realIPSuccess: 'RealIP settings saved',
realIPError: 'Please enter a valid IP address'
}
},
system: {
cache: 'Cache Management',
cacheDesc: 'Clear cache',
diskCache: 'Disk Cache',
diskCacheDesc: 'Cache played music and lyrics on local disk to speed up repeated playback',
cacheDirectory: 'Cache Directory',
cacheDirectoryDesc: 'Custom directory for music and lyric cache files',
selectDirectory: 'Select Directory',
openDirectory: 'Open Directory',
cacheMaxSize: 'Cache Size Limit',
cacheMaxSizeDesc: 'Older cache items are cleaned automatically when limit is reached',
cleanupPolicy: 'Cleanup Policy',
cleanupPolicyDesc: 'Auto cleanup rule when cache reaches the size limit',
cleanupPolicyOptions: {
lru: 'Least Recently Used',
fifo: 'First In, First Out'
},
cacheStatus: 'Cache Status',
cacheStatusDesc: 'Used {used} / Limit {limit}',
cacheStatusDetail: 'Music {musicCount}, Lyrics {lyricCount}',
manageDiskCache: 'Manual Disk Cache Cleanup',
manageDiskCacheDesc: 'Clean cache by category',
clearMusicCache: 'Clear Music Cache',
clearLyricCache: 'Clear Lyric Cache',
clearAllCache: 'Clear All Cache',
switchDirectoryMigrateTitle: 'Existing Cache Detected',
switchDirectoryMigrateContent: 'Do you want to migrate old cache files to the new directory?',
switchDirectoryMigrateConfirm: 'Migrate',
switchDirectoryDestroyTitle: 'Destroy Old Cache',
switchDirectoryDestroyContent:
'If you do not migrate, do you want to destroy old cache files in the previous directory?',
switchDirectoryDestroyConfirm: 'Destroy',
switchDirectoryKeepOld: 'Keep Old Cache',
cacheClearTitle: 'Select cache types to clear:',
cacheTypes: {
history: {
label: 'Play History',
description: 'Clear played song records'
},
favorite: {
label: 'Favorites',
description: 'Clear local favorite songs (cloud favorites not affected)'
},
user: {
label: 'User Data',
description: 'Clear login info and user-related data'
},
settings: {
label: 'App Settings',
description: 'Clear all custom app settings'
},
downloads: {
label: 'Download History',
description: 'Clear download history (downloaded files not affected)'
},
resources: {
label: 'Music Resources',
description: 'Clear cached music files, lyrics and other resources'
},
lyrics: {
label: 'Lyrics Resources',
description: 'Clear cached lyrics resources'
}
},
restart: 'Restart',
restartDesc: 'Restart application',
messages: {
clearSuccess: 'Cache cleared successfully, some settings will take effect after restart',
diskCacheClearSuccess: 'Disk cache cleaned',
diskCacheClearFailed: 'Failed to clean disk cache',
diskCacheStatsLoadFailed: 'Failed to load cache status',
switchDirectorySuccess: 'Cache directory switched, old cache is kept',
switchDirectoryFailed: 'Failed to switch cache directory',
switchDirectoryMigrated: 'Cache directory switched, migrated {count} cache files',
switchDirectoryDestroyed: 'Cache directory switched, destroyed {count} old cache files'
}
},
about: {
version: 'Version',
checkUpdate: 'Check for Updates',
checking: 'Checking...',
latest: 'Already latest version',
hasUpdate: 'New version available',
gotoUpdate: 'Go to Update',
manualUpdate: 'Manual Update',
gotoGithub: 'Go to Github',
author: 'Author',
authorDesc: 'algerkong Give a star🌟',
messages: {
checkError: 'Failed to check for updates, please try again later'
}
},
validation: {
selectProxyProtocol: 'Please select proxy protocol',
proxyHost: 'Please enter proxy host',
portNumber: 'Please enter a valid port number (1-65535)'
},
lyricSettings: {
title: 'Lyric Settings',
tabs: {
display: 'Display',
interface: 'Interface',
typography: 'Typography',
background: 'Background',
mobile: 'Mobile'
},
pureMode: 'Pure Mode',
hideCover: 'Hide Cover',
centerDisplay: 'Center Display',
showTranslation: 'Show Translation',
hideLyrics: 'Hide Lyrics',
hidePlayBar: 'Hide Play Bar',
hideMiniPlayBar: 'Hide Mini Play Bar',
showMiniPlayBar: 'Show Mini Play Bar',
backgroundTheme: 'Background Theme',
themeOptions: {
default: 'Default',
light: 'Light',
dark: 'Dark'
},
fontSize: 'Font Size',
fontSizeMarks: {
small: 'Small',
medium: 'Medium',
large: 'Large'
},
fontWeight: 'Font Weight',
fontWeightMarks: {
thin: 'Thin',
normal: 'Normal',
bold: 'Bold'
},
letterSpacing: 'Letter Spacing',
letterSpacingMarks: {
compact: 'Compact',
default: 'Default',
loose: 'Loose'
},
lineHeight: 'Line Height',
lineHeightMarks: {
compact: 'Compact',
default: 'Default',
loose: 'Loose'
},
contentWidth: 'Content Width',
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',
// Background settings
background: {
useCustomBackground: 'Use Custom Background',
backgroundMode: 'Background Mode',
modeOptions: {
solid: 'Solid',
gradient: 'Gradient',
image: 'Image',
css: 'CSS'
},
solidColor: 'Select Color',
presetColors: 'Preset Colors',
customColor: 'Custom Color',
gradientEditor: 'Gradient Editor',
gradientColors: 'Gradient Colors',
gradientDirection: 'Gradient Direction',
directionOptions: {
toBottom: 'Top to Bottom',
toRight: 'Left to Right',
toBottomRight: 'Top Left to Bottom Right',
angle45: '45 Degrees',
toTop: 'Bottom to Top',
toLeft: 'Right to Left'
},
addColor: 'Add Color',
removeColor: 'Remove Color',
imageUpload: 'Upload Image',
imagePreview: 'Image Preview',
clearImage: 'Clear Image',
imageBlur: 'Blur',
imageBrightness: 'Brightness',
customCss: 'Custom CSS Style',
customCssPlaceholder: 'Enter CSS style, e.g.: background: linear-gradient(...)',
customCssHelp: 'Supports any CSS background property',
reset: 'Reset to Default',
fileSizeLimit: 'Image size limit: 20MB',
invalidImageFormat: 'Invalid image format',
imageTooLarge: 'Image too large, please select an image smaller than 20MB'
}
},
translationEngine: 'Lyric Translation Engine',
translationEngineOptions: {
none: 'Off',
opencc: 'OpenCC Traditionalize'
},
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',
shortcut: 'Shortcut',
shortcutDesc: 'Customize global shortcuts',
summaryReady: 'Shortcut configuration is ready to save',
summaryRecording: 'Recording a new shortcut combination',
summaryBlocked: 'Fix conflicts or invalid entries before saving',
platformHintMac: 'On macOS, CommandOrControl is displayed as Cmd',
platformHintWindows: 'On Windows, CommandOrControl is displayed as Ctrl',
platformHintLinux: 'On Linux, CommandOrControl is displayed as Ctrl',
platformHintGeneric: 'CommandOrControl is adapted per operating system',
enabledCount: 'Enabled',
recordingTip: 'Click a shortcut field, press combination. Esc cancels, Delete disables',
shortcutConflict: 'Shortcut Conflict',
inputPlaceholder: 'Click to input shortcut',
clickToRecord: 'Click then press a shortcut',
recording: 'Recording...',
resetShortcuts: 'Reset',
restoreSingle: 'Restore',
disableAll: 'Disable All',
enableAll: 'Enable All',
groups: {
playback: 'Playback',
sound: 'Volume & Favorite',
window: 'Window'
},
togglePlay: 'Play/Pause',
togglePlayDesc: 'Toggle current playback state',
prevPlay: 'Previous',
prevPlayDesc: 'Play the previous track',
nextPlay: 'Next',
nextPlayDesc: 'Play the next track',
volumeUp: 'Volume Up',
volumeUpDesc: 'Increase player volume',
volumeDown: 'Volume Down',
volumeDownDesc: 'Decrease player volume',
toggleFavorite: 'Favorite/Unfavorite',
toggleFavoriteDesc: 'Favorite or unfavorite current track',
toggleWindow: 'Show/Hide Window',
toggleWindowDesc: 'Quickly show or hide the main window',
scopeGlobal: 'Global',
scopeApp: 'App Only',
enabled: 'Enabled',
disabled: 'Disabled',
issueInvalid: 'Invalid combo',
issueReserved: 'System reserved',
registrationWarningTitle: 'These shortcuts could not be registered',
registrationOccupied: 'Occupied by system or another app',
registrationInvalid: 'Invalid shortcut format',
messages: {
resetSuccess: 'Shortcuts reset successfully, please save',
conflict: 'Shortcut conflict, please reset',
saveSuccess: 'Shortcuts saved successfully',
saveError: 'Failed to save shortcuts',
saveValidationError: 'Shortcut validation failed, please review and try again',
partialRegistered: 'Saved, but some global shortcuts were not registered',
cancelEdit: 'Edit cancelled',
clearToDisable: 'Shortcut disabled',
invalidShortcut: 'Invalid shortcut, please use a valid combination',
disableAll: 'All shortcuts disabled, please save to apply',
enableAll: 'All shortcuts enabled, please save to apply'
}
},
remoteControl: {
title: 'Remote Control',
enable: 'Enable Remote Control',
port: 'Port',
allowedIps: 'Allowed IPs',
addIp: 'Add IP',
emptyListHint: 'Empty list means allow all IPs',
saveSuccess: 'Remote control settings saved',
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

@@ -1,32 +0,0 @@
export default {
menu: {
play: 'Play',
playNext: 'Play Next',
download: 'Download',
downloadLyric: 'Download Lyrics',
addToPlaylist: 'Add to Playlist',
favorite: 'Like',
unfavorite: 'Unlike',
removeFromPlaylist: 'Remove from Playlist',
dislike: 'Dislike',
undislike: 'Undislike'
},
message: {
downloading: 'Downloading, please wait...',
downloadFailed: 'Download failed',
downloadQueued: 'Added to download queue',
addedToNextPlay: 'Added to play next',
getUrlFailed: 'Failed to get music download URL, please check if logged in',
noLyric: 'No lyrics available for this song',
lyricDownloaded: 'Lyrics downloaded successfully',
lyricDownloadFailed: 'Failed to download lyrics'
},
dialog: {
dislike: {
title: 'Dislike',
content: 'Are you sure you want to dislike this song?',
positiveText: 'Dislike',
negativeText: 'Cancel'
}
}
};

View File

@@ -1,53 +0,0 @@
export default {
profile: {
followers: 'Followers',
following: 'Following',
level: 'Level'
},
playlist: {
created: 'Created Playlists',
mine: 'Mine',
trackCount: '{count} tracks',
playCount: 'Played {count} times'
},
tabs: {
created: 'Created',
favorite: 'Favorite',
album: 'Album'
},
ranking: {
title: 'Listening History',
playCount: '{count} times'
},
follow: {
title: 'Follow List',
viewPlaylist: 'View Playlist',
noFollowings: 'No Followings',
loadMore: 'Load More',
noSignature: 'This guy is lazy, nothing left',
userFollowsTitle: "'s Followings",
myFollowsTitle: 'My Followings'
},
follower: {
title: 'Follower List',
noFollowers: 'No Followers',
loadMore: 'Load More',
userFollowersTitle: "'s Followers",
myFollowersTitle: 'My Followers'
},
detail: {
playlists: 'Playlists',
records: 'Listening History',
noPlaylists: 'No Playlists',
noRecords: 'No Listening History',
artist: 'Artist',
noSignature: 'This guy is lazy, nothing left',
invalidUserId: 'Invalid User ID',
noRecordPermission: "{name} doesn't let you see your listening history"
},
message: {
loadFailed: 'Failed to load user page',
deleteSuccess: 'Successfully deleted',
deleteFailed: 'Failed to delete'
}
};

View File

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

View File

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

View File

@@ -1,337 +0,0 @@
export default {
more: 'もっと見る',
homeListItem: {
loading: '読み込み中...'
},
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: '後で更新',
checking: '更新を確認中...',
prepareDownload: 'ダウンロード準備中...',
downloading: 'ダウンロード中...',
readyToInstall: '更新パッケージのダウンロードが完了しました。今すぐインストールできます',
nowUpdate: '今すぐ更新',
downloadFailed: 'ダウンロードに失敗しました。再試行するか手動でダウンロードしてください',
startFailed: 'ダウンロードの開始に失敗しました。再試行するか手動でダウンロードしてください',
autoUpdateFailed: '自動更新に失敗しました',
openOfficialSite: '公式サイトから更新',
manualFallbackHint:
'自動更新に失敗した場合は、公式リリースページから最新版をダウンロードできます。',
noDownloadUrl:
'現在のシステムに適したインストールパッケージが見つかりません。手動でダウンロードしてください',
installConfirmTitle: '更新をインストール',
installConfirmContent: 'アプリを閉じて更新をインストールしますか?',
manualInstallTip:
'アプリを閉じた後にインストーラーが正常に起動しない場合は、ダウンロードフォルダでファイルを見つけて手動で開いてください。',
yesInstall: '今すぐインストール',
noThanks: '後でインストール',
fileLocation: 'ファイルの場所',
copy: 'パスをコピー',
copySuccess: 'パスをクリップボードにコピーしました',
copyFailed: 'コピーに失敗しました',
backgroundDownload: 'バックグラウンドダウンロード'
},
disclaimer: {
title: '使用上の注意',
warning:
'このアプリは開発テスト版であり、機能が不完全で、多くの問題やバグが存在する可能性があります。学習と交流のみを目的としています。',
item1:
'このアプリは個人の学習、研究、技術交流のみを目的としています。商業目的で使用しないでください。',
item2:
'ダウンロード後24時間以内に削除してください。長期使用を希望される場合は、正規の音楽サービスをサポートしてください。',
item3:
'このアプリを使用することで、関連するリスクを理解し、負担するものとします。開発者は一切の損失に対して責任を負いません。',
agree: '以上の内容を読み、同意します',
disagree: '同意せずに終了'
},
donate: {
title: '開発者を支援',
subtitle: '皆様のサポートが私の原動力です',
tip: '寄付は完全に任意です。寄付しなくてもすべての機能を通常通り使用できます。ご理解とご支援に感謝します!',
wechat: 'WeChat',
alipay: 'Alipay',
wechatQR: 'WeChat 受取コード',
alipayQR: 'Alipay 受取コード',
scanTip: 'スマートフォンのアプリで上記のQRコードをスキャンして寄付してください',
enterApp: 'アプリに入る',
noForce: '寄付は強制ではありません。クリックして入れます'
},
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: '今週の人気音楽',
empty: 'おすすめのプレイリストがありません'
},
dailyRecommend: {
title: '毎日のおすすめ',
badge: 'おすすめ',
empty: 'おすすめの曲がありません',
intelligenceHint: 'インテリジェンスモードをオンにして、もっと好きな音楽を見つけましょう'
},
recommendMV: {
title: 'おすすめMV'
},
newAlbum: {
title: 'アルバム',
empty: '新しいアルバムがありません'
},
recommendNewMusic: {
title: '新曲速報'
},
privateContent: {
title: '独占配信'
},
djProgram: {
title: 'おすすめラジオ'
},
homeHero: {
dailyRecommend: '毎日のおすすめ',
songs: '曲',
playNow: '今すぐ再生',
intelligenceMode: 'インテリジェンスモード',
intelligenceModeOn: '再生中',
intelligenceModeDesc: 'スマート推薦を開始',
intelligenceModeActiveDesc: 'あなたの好みに基づくスマート推薦',
startIntelligence: '開始',
stopIntelligence: '停止',
playing: '再生中',
toplistDesc: 'トレンド',
mvDesc: 'ミュージックビデオ',
playlistDesc: '厳選プレイリスト',
personalFm: 'パーソナルFM',
discoverMusic: '新しい音楽を発見',
personalFmDesc: 'あなたの好みに基づいて',
recentPlays: '最近再生した曲',
viewAll: 'すべて表示',
followedArtists: 'フォロー中',
newSongs: '曲の新曲',
fromFollowedArtists: 'フォロー中のアーティストから',
recommendNewMusic: 'おすすめ新曲',
newSongExpress: '新曲速報',
discoverNewReleases: '最新リリースを見つけよう',
hotPlaylists: '人気プレイリスト',
hotArtists: '人気アーティスト',
hotArtistsTitle: '人気アーティスト',
hotArtistsDesc: '今最も人気のあるアーティスト',
fmTrash: '嫌い',
fmNext: '次へ',
quickNav: {
myFavorite: 'お気に入り',
playHistory: '再生履歴',
myProfile: 'マイページ',
toplist: 'ランキング'
}
},
searchBar: {
login: 'ログイン',
toLogin: 'ログインへ',
logout: 'ログアウト',
set: '設定',
theme: 'テーマ',
restart: '再起動',
refresh: '更新',
currentVersion: '現在のバージョン',
searchPlaceholder: '何かを検索してみましょう...',
zoom: 'ページズーム',
zoom100: '標準ズーム100%',
resetZoom: 'クリックしてズームをリセット',
zoomDefault: '標準ズーム',
tabPlaylist: 'プレイリスト',
tabMv: 'MV',
tabCharts: 'チャート',
cancelSearch: 'キャンセル',
intelligenceMode: '心動モード',
exitIntelligence: '心動モードを終了'
},
titleBar: {
closeTitle: '閉じる方法を選択してください',
minimizeToTray: 'トレイに最小化',
exitApp: 'アプリを終了',
rememberChoice: '選択を記憶する',
closeApp: 'アプリを閉じる'
},
userPlayList: {
title: '{name}のよく聞く音楽'
},
musicList: {
searchSongs: '楽曲を検索',
noSearchResults: '関連する楽曲が見つかりませんでした',
switchToNormal: 'デフォルトレイアウトに切り替え',
switchToCompact: 'コンパクトレイアウトに切り替え',
playAll: 'すべて再生',
collect: 'お気に入り',
collectSuccess: 'お気に入りに追加しました',
cancelCollectSuccess: 'お気に入りから削除しました',
operationFailed: '操作に失敗しました',
cancelCollect: 'お気に入りから削除',
addToPlaylist: 'プレイリストに追加',
addToPlaylistSuccess: 'プレイリストに追加しました',
songsAlreadyInPlaylist: '楽曲は既にプレイリストに存在します',
locateCurrent: '再生中の曲を表示',
historyRecommend: '履歴の日次推薦',
fetchDatesFailed: '日付リストの取得に失敗しました',
fetchSongsFailed: '楽曲リストの取得に失敗しました',
noSongs: '楽曲がありません'
},
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: 'リンクを追加',
options: 'オプション',
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: '検索',
album: 'アルバム',
localMusic: 'ローカル音楽',
pages: {
toplist: {
desc: '最も権威ある音楽チャート、今一番ホットな音楽を発見'
},
mv: {
desc: '素晴らしい動画コンテンツを探索',
loadingMore: 'もっと読み込み中...',
noMore: '— すべて読み込みました —',
area: {
all: 'すべて',
mainland: '中国大陸',
hktw: '香港・台湾',
western: '欧米',
japan: '日本',
korea: '韓国'
}
},
list: {
desc: 'もっと素敵なプレイリストを発見',
dailyRecommend: 'デイリーおすすめ'
},
search: {
desc: '今最もホットな検索トレンドを探索'
},
album: {
area: {
all: 'すべて',
chinese: '中華圏',
western: '欧米',
korea: '韓国',
japan: '日本'
}
}
}
};

View File

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

View File

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

View File

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

View File

@@ -1,49 +0,0 @@
export default {
title: '再生履歴',
heatmapTitle: 'ヒートマップ',
playCount: '{count}',
getHistoryFailed: '履歴の取得に失敗しました',
tabs: {
all: 'すべての記録',
local: 'ローカル記録',
cloud: 'クラウド記録'
},
categoryTabs: {
songs: '楽曲',
playlists: 'プレイリスト',
albums: 'アルバム',
podcasts: 'ポッドキャスト'
},
podcastTabs: {
episodes: 'エピソード',
radios: 'ラジオ'
},
noDescription: '説明なし',
noData: '記録なし',
getCloudRecordFailed: 'クラウド記録の取得に失敗しました',
needLogin: 'cookieを使用してログインしてクラウド記録を表示できます',
merging: '記録を統合中...',
heatmap: {
title: '再生ヒートマップ',
loading: 'データを読み込み中...',
unit: '回再生',
footerText: 'ホバーして詳細を表示',
playCount: '{count} 回再生',
topSongs: 'その日の人気曲',
times: '回',
totalPlays: '総再生回数',
activeDays: 'アクティブ日数',
noData: '再生記録がありません',
colorTheme: 'カラーテーマ',
colors: {
green: 'グリーン',
blue: 'ブルー',
orange: 'オレンジ',
purple: 'パープル',
red: 'レッド'
},
mostPlayedSong: '最も再生された曲',
mostActiveDay: '最もアクティブな日',
latestNightSong: '深夜に再生した曲'
}
};

View File

@@ -1,13 +0,0 @@
export default {
title: 'ローカル音楽',
scanFolder: 'フォルダをスキャン',
removeFolder: 'フォルダを削除',
scanning: 'スキャン中...',
scanComplete: 'スキャン完了',
playAll: 'すべて再生',
search: 'ローカル音楽を検索',
emptyState: 'ローカル音楽がありません。フォルダを選択してスキャンしてください。',
fileNotFound: 'ファイルが見つからないか、移動されました',
rescan: '再スキャン',
songCount: '{count} 曲'
};

View File

@@ -1,65 +0,0 @@
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のログインページを開きます。ログイン完了後、ウィンドウを閉じてください',
loginFailed: 'ログイン失敗',
phoneRequired: '電話番号を入力してください',
passwordRequired: 'パスワードを入力してください',
phoneLoginFailed:
'電話番号でのログインに失敗しました。電話番号とパスワードが正しいか確認してください',
qrCheckFailed: 'QRコードの状態確認に失敗しました。リフレッシュして再試行してください',
qrLoading: 'QRコードを読み込み中...',
qrExpired: 'QRコードの期限が切れました。クリックしてリフレッシュしてください',
qrExpiredShort: 'QRコード期限切れ',
qrExpiredWarning: 'QRコードの期限が切れました。クリックして新しいQRコードを取得してください',
qrScanned: 'QRコードがスキャンされました。スマートフォンでログインを確認してください',
qrScannedShort: 'スキャン済み',
qrScannedInfo: 'QRコードがスキャンされました。スマートフォンでログインを確認してください',
qrConfirmed: 'ログイン成功、リダイレクト中...',
qrGenerating: 'QRコードを生成中...'
},
qrTitle: 'NetEase Cloud Music QRコードログイン',
uidWarning:
'注意UIDログインはユーザーの公開情報を表示するためのみ使用でき、ログイン権限が必要な機能にはアクセスできません。'
};

View File

@@ -1,148 +0,0 @@
export default {
nowPlaying: '再生中',
playlist: 'プレイリスト',
lyrics: '歌詞',
previous: '前へ',
play: '再生',
pause: '一時停止',
next: '次へ',
volumeUp: '音量を上げる',
volumeDown: '音量を下げる',
mute: 'ミュート',
unmute: 'ミュート解除',
songNum: '楽曲総数:{num}',
addCorrection: '{num}秒早める',
subtractCorrection: '{num}秒遅らせる',
playFailed: '現在の楽曲の再生に失敗しました。次の曲を再生します',
parseFailedPlayNext: '楽曲の解析に失敗しました。次の曲を再生します',
consecutiveFailsError:
'再生エラーが発生しました。ネットワークの問題または無効な音源の可能性があります。プレイリストを切り替えるか、後でもう一度お試しください',
playListEnded: 'プレイリストの最後に到達しました',
autoResumed: '自動的に再生を再開しました',
resumeFailed: '再生の再開に失敗しました。手動でお試しください',
playMode: {
sequence: '順次再生',
loop: 'リピート再生',
random: 'ランダム再生'
},
fullscreen: {
enter: 'フルスクリーン',
exit: 'フルスクリーン終了'
},
close: '閉じる',
modeHint: {
single: 'リピート再生',
list: '自動で次の曲を再生'
},
lrc: {
noLrc: '歌詞がありません。お楽しみください',
noAutoScroll: '本歌詞は自動スクロールをサポートしていません'
},
reparse: {
title: '解析音源を選択',
desc: '音源をクリックして直接解析します。次回この楽曲を再生する際は選択した音源を使用します',
success: '再解析成功',
failed: '再解析失敗',
warning: '音源を選択してください',
bilibiliNotSupported: 'Bilibili動画は再解析をサポートしていません',
processing: '解析中...',
clear: 'カスタム音源をクリア',
customApiFailed: 'カスタムAPIの解析に失敗しました。内蔵音源を試しています...',
customApiError: 'カスタムAPIのリクエストでエラーが発生しました。内蔵音源を試しています...'
},
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: 'その他の設定',
intelligenceMode: {
title: 'インテリジェンスモード',
needCookieLogin: 'Cookie方式でログインしてからインテリジェンスモードを使用してください',
noFavoritePlaylist: '「お気に入りの音楽」プレイリストが見つかりません',
noLikedSongs: 'まだ「いいね」した楽曲がありません',
loading: 'インテリジェンスモードを読み込み中',
success: '{count} 曲を読み込みました',
failed: 'インテリジェンスモードのリスト取得に失敗しました',
error: 'インテリジェンスモードの再生でエラーが発生しました'
}
},
eq: {
title: 'イコライザー',
reset: 'リセット',
on: 'オン',
off: 'オフ',
bass: '低音',
midrange: '中音',
treble: '高音',
presets: {
flat: 'フラット',
pop: 'ポップ',
rock: 'ロック',
classical: 'クラシック',
jazz: 'ジャズ',
electronic: 'エレクトロニック',
hiphop: 'ヒップホップ',
rb: 'R&B',
metal: 'メタル',
vocal: 'ボーカル',
dance: 'ダンス',
acoustic: 'アコースティック',
custom: 'カスタム'
}
},
// プレイヤー設定
settings: {
title: '再生設定',
playbackSpeed: '再生速度'
},
// タイマー機能関連
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

@@ -1,40 +0,0 @@
export default {
podcast: 'ポッドキャスト',
mySubscriptions: '購読中',
discover: '発見',
categories: 'カテゴリー',
todayPerfered: '今日のおすすめ',
recommended: 'おすすめ',
hotRanking: '人気',
newRanking: '新着',
subscribeCount: '購読者',
programCount: 'エピソード',
subscribe: '購読',
subscribed: '購読中',
unsubscribe: '購読解除',
unsubscribed: '購読を解除しました',
subscribeSuccess: '購読しました',
unsubscribeFailed: '購読解除に失敗しました',
subscribeFailed: '購読に失敗しました',
radioDetail: 'ラジオ詳細',
programList: 'エピソード一覧',
playProgram: '再生',
recentPlayed: '最近再生',
listeners: 'リスナー',
noSubscriptions: '購読なし',
goDiscover: 'ポッドキャストを探す',
searchPodcast: 'ポッドキャスト検索',
category: 'カテゴリー',
all: 'すべて',
dj: 'パーソナリティ',
episodes: '話',
playAll: 'すべて再生',
popularCategories: '人気カテゴリー',
allCategories: 'すべてのカテゴリー',
categoryRadios: 'カテゴリーラジオ',
exploreCategoryRadios: 'もっと素晴らしいラジオを探す',
hotRadios: '人気ラジオ',
noCategoryRadios: 'このカテゴリーにはラジオがありません',
searchPlaceholder: 'ポッドキャスト、エピソードを検索...',
searchResults: '検索結果'
};

View File

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

View File

@@ -1,520 +0,0 @@
export default {
theme: 'テーマ',
language: '言語',
regard: 'について',
logout: 'ログアウト',
sections: {
basic: '基本設定',
playback: '再生設定',
application: 'アプリケーション設定',
network: 'ネットワーク設定',
system: 'システム管理',
donation: '寄付サポート',
about: 'について'
},
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: '가나다라마 바사아자차 카타파하'
},
gpuAcceleration: 'GPUアクセラレーション',
gpuAccelerationDesc:
'ハードウェアアクセラレーションを有効または無効にします。レンダリングパフォーマンスを向上させますが、GPU負荷が増える可能性があります',
gpuAccelerationRestart: 'GPUアクセラレーション設定の変更はアプリの再起動後に有効になります',
gpuAccelerationChangeSuccess:
'GPUアクセラレーション設定を更新しました。アプリの再起動後に有効になります',
gpuAccelerationChangeError: 'GPUアクセラレーション設定の更新に失敗しました',
tabletMode: 'タブレットモード',
tabletModeDesc:
'タブレットモードを有効にすると、モバイルデバイスでPCスタイルのインターフェースを使用できます'
},
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: 'アプリを再起動した際に自動的に再生を継続するかどうか',
audioDevice: 'オーディオ出力デバイス',
audioDeviceDesc: 'スピーカー、ヘッドホン、Bluetoothデバイスなどの出力先を選択',
testAudio: 'テスト',
selectAudioDevice: '出力デバイスを選択',
showStatusBar: 'ステータスバーコントロール機能を表示するかどうか',
showStatusBarContent:
'Macのステータスバーに音楽コントロール機能を表示できます再起動後に有効',
fallbackParser: '代替解析サービス (GD音楽台)',
fallbackParserDesc:
'「GD音楽台」にチェックが入っていて、通常の音源で再生できない場合、このサービスが使用されます。',
parserGD: 'GD 音楽台 (内蔵)',
parserCustom: 'カスタム API',
sourceLabels: {
migu: 'Migu',
kugou: 'Kugou',
kuwo: 'Kuwo',
pyncmd: 'NetEase (内蔵)',
qq: 'QQ Music',
joox: 'JOOX',
bilibili: 'Bilibili',
gdmusic: 'GD 音楽台',
lxMusic: 'LX Music',
custom: 'カスタム API'
},
customApi: {
sectionTitle: 'カスタム API 設定',
enableHint:
'カスタム API を有効にするには、まずカスタム API をインポートする必要があります。',
importConfig: 'JSON設定をインポート',
currentSource: '現在の音源',
notImported: 'カスタム音源はまだインポートされていません。',
importSuccess: '音源のインポートに成功しました: {name}',
importFailed: 'インポートに失敗しました: {message}',
status: {
imported: 'カスタム音源インポート済み',
notImported: '未インポート'
}
},
lxMusic: {
tabs: {
sources: '音源選択',
lxMusic: '落雪音源',
customApi: 'カスタムAPI'
},
scripts: {
title: 'インポート済みのスクリプト',
importLocal: 'ローカルインポート',
importOnline: 'オンラインインポート',
urlPlaceholder: '落雪音源スクリプトのURLを入力',
importBtn: 'インポート',
empty: 'インポート済みの落雪音源はありません',
notConfigured: '未設定(落雪音源タブで設定してください)',
importHint: '互換性のあるカスタムAPIプラグインをインポートして音源を拡張します',
noScriptWarning: '先に落雪音源スクリプトをインポートしてください',
noSelectionWarning: '先に落雪音源を選択してください',
notFound: '音源が存在しません',
switched: '音源を切り替えました: {name}',
deleted: '音源を削除しました: {name}',
enterUrl: 'スクリプトURLを入力してください',
invalidUrl: '無効なURL形式',
invalidScript: '無効な落雪音源スクリプトですglobalThis.lxが見つかりません',
nameRequired: '名前を空にすることはできません',
renameSuccess: '名前を変更しました'
}
}
},
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: 'キャッシュをクリア',
diskCache: 'ディスクキャッシュ',
diskCacheDesc: '再生した音楽と歌詞をローカルディスクへ保存し、再生速度を向上します',
cacheDirectory: 'キャッシュディレクトリ',
cacheDirectoryDesc: '音楽・歌詞キャッシュの保存先を指定',
selectDirectory: 'ディレクトリ選択',
openDirectory: 'ディレクトリを開く',
cacheMaxSize: 'キャッシュ上限',
cacheMaxSizeDesc: '上限に達すると古いキャッシュを自動削除します',
cleanupPolicy: 'クリーンアップポリシー',
cleanupPolicyDesc: 'キャッシュ上限到達時の自動削除ルール',
cleanupPolicyOptions: {
lru: '最近未使用優先',
fifo: '先入れ先出し'
},
cacheStatus: 'キャッシュ状態',
cacheStatusDesc: '使用量 {used} / 上限 {limit}',
cacheStatusDetail: '音楽 {musicCount} 曲、歌詞 {lyricCount} 曲',
manageDiskCache: '手動キャッシュクリア',
manageDiskCacheDesc: '種類ごとにキャッシュを削除',
clearMusicCache: '音楽キャッシュを削除',
clearLyricCache: '歌詞キャッシュを削除',
clearAllCache: 'すべて削除',
switchDirectoryMigrateTitle: '既存キャッシュを検出',
switchDirectoryMigrateContent: '旧ディレクトリのキャッシュを新ディレクトリへ移行しますか?',
switchDirectoryMigrateConfirm: '移行する',
switchDirectoryDestroyTitle: '旧キャッシュを削除',
switchDirectoryDestroyContent: '移行しない場合、旧ディレクトリのキャッシュを削除しますか?',
switchDirectoryDestroyConfirm: '削除する',
switchDirectoryKeepOld: '旧キャッシュを保持',
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: 'クリア成功。一部の設定は再起動後に有効になります',
diskCacheClearSuccess: 'ディスクキャッシュを削除しました',
diskCacheClearFailed: 'ディスクキャッシュの削除に失敗しました',
diskCacheStatsLoadFailed: 'キャッシュ状態の取得に失敗しました',
switchDirectorySuccess: 'キャッシュディレクトリを切り替えました(旧キャッシュは保持)',
switchDirectoryFailed: 'キャッシュディレクトリの切り替えに失敗しました',
switchDirectoryMigrated: 'キャッシュディレクトリを切り替え、{count} 件を移行しました',
switchDirectoryDestroyed:
'キャッシュディレクトリを切り替え、旧キャッシュ {count} 件を削除しました'
}
},
about: {
version: 'バージョン',
checkUpdate: '更新を確認',
checking: '確認中...',
latest: '現在最新バージョンです',
hasUpdate: '新しいバージョンが見つかりました',
gotoUpdate: '更新へ',
manualUpdate: '手動更新',
gotoGithub: 'Githubへ',
author: '作者',
authorDesc: 'algerkong スターを付けてください🌟',
messages: {
checkError: '更新確認に失敗しました。後でもう一度お試しください'
}
},
validation: {
selectProxyProtocol: 'プロキシプロトコルを選択してください',
proxyHost: 'プロキシアドレスを入力してください',
portNumber: '有効なポート番号を入力してください1-65535'
},
lyricSettings: {
title: '歌詞設定',
tabs: {
display: '表示',
interface: 'インターフェース',
typography: 'テキスト',
background: '背景',
mobile: 'モバイル'
},
pureMode: 'ピュアモード',
hideCover: 'カバーを非表示',
centerDisplay: '中央表示',
showTranslation: '翻訳を表示',
hideLyrics: '歌詞を非表示',
hidePlayBar: '再生バーを非表示',
hideMiniPlayBar: 'ミニ再生バーを非表示',
showMiniPlayBar: 'ミニ再生バーを表示',
backgroundTheme: '背景テーマ',
themeOptions: {
default: 'デフォルト',
light: 'ライト',
dark: 'ダーク'
},
fontSize: 'フォントサイズ',
fontSizeMarks: {
small: '小',
medium: '中',
large: '大'
},
fontWeight: 'フォントの太さ',
fontWeightMarks: {
thin: '細い',
normal: '通常',
bold: '太い'
},
letterSpacing: '文字間隔',
letterSpacingMarks: {
compact: 'コンパクト',
default: 'デフォルト',
loose: 'ゆったり'
},
lineHeight: '行の高さ',
lineHeightMarks: {
compact: 'コンパクト',
default: 'デフォルト',
loose: 'ゆったり'
},
contentWidth: 'コンテンツ幅',
mobileLayout: 'モバイルレイアウト',
layoutOptions: {
default: 'デフォルト',
ios: 'iOSスタイル',
android: 'Androidスタイル'
},
mobileCoverStyle: 'カバースタイル',
coverOptions: {
record: 'レコード',
square: '正方形',
full: 'フルスクリーン'
},
lyricLines: '歌詞行数',
mobileUnavailable: 'この設定はモバイルでのみ利用可能です',
// 背景設定
background: {
useCustomBackground: 'カスタム背景を使用',
backgroundMode: '背景モード',
modeOptions: {
solid: '単色',
gradient: 'グラデーション',
image: '画像',
css: 'CSS'
},
solidColor: '色を選択',
presetColors: 'プリセットカラー',
customColor: 'カスタムカラー',
gradientEditor: 'グラデーションエディター',
gradientColors: 'グラデーションカラー',
gradientDirection: 'グラデーション方向',
directionOptions: {
toBottom: '上から下',
toRight: '左から右',
toBottomRight: '左上から右下',
angle45: '45度',
toTop: '下から上',
toLeft: '右から左'
},
addColor: '色を追加',
removeColor: '色を削除',
imageUpload: '画像をアップロード',
imagePreview: '画像プレビュー',
clearImage: '画像をクリア',
imageBlur: 'ぼかし',
imageBrightness: '明るさ',
customCss: 'カスタム CSS スタイル',
customCssPlaceholder: 'CSSスタイルを入力、例: background: linear-gradient(...)',
customCssHelp: '任意のCSS background プロパティをサポート',
reset: 'デフォルトにリセット',
fileSizeLimit: '画像サイズ制限: 20MB',
invalidImageFormat: '無効な画像形式',
imageTooLarge: '画像が大きすぎます。20MB未満の画像を選択してください'
}
},
translationEngine: '歌詞翻訳エンジン',
translationEngineOptions: {
none: 'オフ',
opencc: 'OpenCC 繁体字化'
},
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: 'ショートカットをカスタマイズ',
summaryReady: 'ショートカット設定は保存可能です',
summaryRecording: '新しいショートカットを記録中です',
summaryBlocked: '競合または無効な項目を修正してください',
platformHintMac: 'macOS では CommandOrControl は Cmd と表示されます',
platformHintWindows: 'Windows では CommandOrControl は Ctrl と表示されます',
platformHintLinux: 'Linux では CommandOrControl は Ctrl と表示されます',
platformHintGeneric: 'CommandOrControl はOSに応じて自動変換されます',
enabledCount: '有効',
recordingTip: '欄をクリックしてキー入力。Escでキャンセル、Deleteで無効化',
shortcutConflict: 'ショートカットの競合',
inputPlaceholder: 'クリックしてショートカットを入力',
clickToRecord: 'クリックしてキーを入力',
recording: '記録中...',
resetShortcuts: 'デフォルトに戻す',
restoreSingle: '復元',
disableAll: 'すべて無効',
enableAll: 'すべて有効',
groups: {
playback: '再生操作',
sound: '音量とお気に入り',
window: 'ウィンドウ'
},
togglePlay: '再生/一時停止',
togglePlayDesc: '現在の再生状態を切り替えます',
prevPlay: '前の曲',
prevPlayDesc: '前の曲に切り替えます',
nextPlay: '次の曲',
nextPlayDesc: '次の曲に切り替えます',
volumeUp: '音量を上げる',
volumeUpDesc: 'プレイヤー音量を上げます',
volumeDown: '音量を下げる',
volumeDownDesc: 'プレイヤー音量を下げます',
toggleFavorite: 'お気に入り/お気に入り解除',
toggleFavoriteDesc: '現在の曲をお気に入り切り替えします',
toggleWindow: 'ウィンドウ表示/非表示',
toggleWindowDesc: 'メインウィンドウを表示/非表示にします',
scopeGlobal: 'グローバル',
scopeApp: 'アプリ内',
enabled: '有効',
disabled: '無効',
issueInvalid: '無効な組み合わせ',
issueReserved: 'システム予約',
registrationWarningTitle: '以下のショートカットは登録できませんでした',
registrationOccupied: 'システムまたは他アプリで使用中',
registrationInvalid: 'ショートカット形式が無効',
messages: {
resetSuccess: 'デフォルトのショートカットに戻しました。保存を忘れずに',
conflict: '競合するショートカットがあります。再設定してください',
saveSuccess: 'ショートカット設定を保存しました',
saveError: 'ショートカットの保存に失敗しました。再試行してください',
saveValidationError: 'ショートカット検証に失敗しました。内容を確認してください',
partialRegistered: '保存しましたが、一部のグローバルショートカットは登録されませんでした',
cancelEdit: '変更をキャンセルしました',
clearToDisable: 'このショートカットを無効にしました',
invalidShortcut: '無効なショートカットです。有効な組み合わせを入力してください',
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

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

View File

@@ -1,53 +0,0 @@
export default {
profile: {
followers: 'フォロワー',
following: 'フォロー中',
level: 'レベル'
},
playlist: {
created: '作成したプレイリスト',
mine: '私が作成した',
trackCount: '{count}曲',
playCount: '{count}回再生'
},
tabs: {
created: '作成',
favorite: 'お気に入り',
album: 'アルバム'
},
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

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

View File

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

View File

@@ -1,336 +0,0 @@
export default {
more: '더 보기',
homeListItem: {
loading: '로딩 중...'
},
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: '나중에 업데이트',
checking: '업데이트 확인 중...',
prepareDownload: '다운로드 준비 중...',
downloading: '다운로드 중...',
readyToInstall: '업데이트 패키지 다운로드가 완료되었습니다. 지금 설치할 수 있습니다',
nowUpdate: '지금 업데이트',
downloadFailed: '다운로드 실패, 다시 시도하거나 수동으로 다운로드해주세요',
startFailed: '다운로드 시작 실패, 다시 시도하거나 수동으로 다운로드해주세요',
autoUpdateFailed: '자동 업데이트에 실패했습니다',
openOfficialSite: '공식 페이지에서 업데이트',
manualFallbackHint:
'자동 업데이트에 실패하면 공식 릴리스 페이지에서 최신 버전을 다운로드할 수 있습니다.',
noDownloadUrl: '현재 시스템에 적합한 설치 패키지를 찾을 수 없습니다. 수동으로 다운로드해주세요',
installConfirmTitle: '업데이트 설치',
installConfirmContent: '앱을 닫고 업데이트를 설치하시겠습니까?',
manualInstallTip:
'앱을 닫은 후 설치 프로그램이 정상적으로 나타나지 않으면 다운로드 폴더에서 파일을 찾아 수동으로 열어주세요.',
yesInstall: '지금 설치',
noThanks: '나중에 설치',
fileLocation: '파일 위치',
copy: '경로 복사',
copySuccess: '경로가 클립보드에 복사됨',
copyFailed: '복사 실패',
backgroundDownload: '백그라운드 다운로드'
},
disclaimer: {
title: '이용 안내',
warning:
'본 앱은 개발 테스트 버전으로 기능이 아직 미흡하며, 다수의 문제와 버그가 존재할 수 있습니다. 학습 및 교류 목적으로만 사용하십시오.',
item1:
'본 앱은 개인의 학습, 연구 및 기술 교류 목적으로만 사용되며, 상업적 용도로 사용하지 마십시오.',
item2:
'다운로드 후 24시간 이내에 삭제해 주십시오. 장기 사용을 원하시면 정품 음악 서비스를 이용해 주십시오.',
item3:
'본 앱을 사용함으로써 관련 위험을 이해하고 감수하는 것으로 간주합니다. 개발자는 어떠한 손실에 대해서도 책임을 지지 않습니다.',
agree: '숙지하였으며 이에 동의합니다',
disagree: '동의하지 않음 및 정지'
},
donate: {
title: '개발자 지원',
subtitle: '여러분의 지원이 저의 원동력입니다',
tip: '후원은 완전히 자율적입니다. 후원하지 않더라도 모든 기능을 정상적으로 사용할 수 있습니다. 이해와 지원에 감사드립니다!',
wechat: 'WeChat',
alipay: 'Alipay',
wechatQR: 'WeChat 결제 코드',
alipayQR: 'Alipay 결제 코드',
scanTip: '휴대전화로 위 QR 코드를 스캔하여 후원해 주세요',
enterApp: '앱 시작하기',
noForce: '후원은 강제가 아닙니다. 클릭하여 시작할 수 있습니다'
},
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: '이번 주 인기 음악',
empty: '추천 플레이리스트가 없습니다'
},
dailyRecommend: {
title: '일일 추천',
badge: '추천',
empty: '추천 곡이 없습니다',
intelligenceHint: '하트 모드를 켜서 더 좋아하는 음악을 발견하세요'
},
recommendMV: {
title: '추천 MV'
},
newAlbum: {
title: '앨범',
empty: '새 앨범이 없습니다'
},
recommendNewMusic: {
title: '신곡 속보'
},
privateContent: {
title: '독점 콘텐츠'
},
djProgram: {
title: '추천 라디오'
},
homeHero: {
dailyRecommend: '일일 추천',
songs: '곡',
playNow: '지금 재생',
intelligenceMode: '하트 모드',
intelligenceModeOn: '재생 중',
intelligenceModeDesc: '스마트 추천 시작',
intelligenceModeActiveDesc: '취향에 맞는 스마트 추천',
startIntelligence: '시작',
stopIntelligence: '중지',
playing: '재생 중',
toplistDesc: '인기 차트',
mvDesc: '뮤직비디오',
playlistDesc: '엄선된 플레이리스트',
personalFm: '개인 FM',
discoverMusic: '새로운 음악 발견',
personalFmDesc: '취향에 맞춘 추천',
recentPlays: '최근 재생',
viewAll: '전체 보기',
followedArtists: '팔로우 아티스트',
newSongs: '곡의 신곡',
fromFollowedArtists: '팔로우한 아티스트의 신곡',
recommendNewMusic: '추천 신곡',
newSongExpress: '신곡 속보',
discoverNewReleases: '최신 발매 곡을 발견하세요',
hotPlaylists: '인기 플레이리스트',
hotArtists: '인기 아티스트',
hotArtistsTitle: '인기 아티스트',
hotArtistsDesc: '지금 가장 인기 있는 아티스트',
fmTrash: '싫어요',
fmNext: '다음',
quickNav: {
myFavorite: '내 즐겨찾기',
playHistory: '재생 기록',
myProfile: '내 프로필',
toplist: '순위'
}
},
searchBar: {
login: '로그인',
toLogin: '로그인하기',
logout: '로그아웃',
set: '설정',
theme: '테마',
restart: '재시작',
refresh: '새로고침',
currentVersion: '현재 버전',
searchPlaceholder: '검색해보세요...',
zoom: '페이지 확대/축소',
zoom100: '표준 확대/축소 100%',
resetZoom: '클릭하여 확대/축소 재설정',
zoomDefault: '표준 확대/축소',
tabPlaylist: '플레이리스트',
tabMv: 'MV',
tabCharts: '차트',
cancelSearch: '취소',
intelligenceMode: '심쿵 모드',
exitIntelligence: '심쿵 모드 종료'
},
titleBar: {
closeTitle: '닫기 방법을 선택해주세요',
minimizeToTray: '트레이로 최소화',
exitApp: '앱 종료',
rememberChoice: '선택 기억하기',
closeApp: '앱 닫기'
},
userPlayList: {
title: '{name}의 자주 듣는 음악'
},
musicList: {
searchSongs: '곡 검색',
noSearchResults: '관련 곡을 찾을 수 없습니다',
switchToNormal: '기본 레이아웃으로 전환',
switchToCompact: '컴팩트 레이아웃으로 전환',
playAll: '모두 재생',
collect: '수집',
collectSuccess: '수집 성공',
cancelCollectSuccess: '수집 취소 성공',
operationFailed: '작업 실패',
cancelCollect: '수집 취소',
addToPlaylist: '재생 목록에 추가',
addToPlaylistSuccess: '재생 목록에 추가 성공',
songsAlreadyInPlaylist: '곡이 이미 재생 목록에 있습니다',
locateCurrent: '현재 재생 곡 찾기',
historyRecommend: '일일 기록 권장',
fetchDatesFailed: '날짜를 가져오지 못했습니다',
fetchSongsFailed: '곡을 가져오지 못했습니다',
noSongs: '노래 없음'
},
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: '링크 추가',
options: '옵션',
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: '검색',
album: '앨범',
localMusic: '로컬 음악',
pages: {
toplist: {
desc: '가장 권위 있는 음악 차트, 지금 가장 핫한 음악을 발견하세요'
},
mv: {
desc: '멋진 영상 콘텐츠 탐색',
loadingMore: '더 불러오는 중...',
noMore: '— 모든 콘텐츠 로드 완료 —',
area: {
all: '전체',
mainland: '중국 대륙',
hktw: '홍콩/대만',
western: '서양',
japan: '일본',
korea: '한국'
}
},
list: {
desc: '더 많은 멋진 플레이리스트를 발견하세요',
dailyRecommend: '오늘의 추천'
},
search: {
desc: '지금 가장 핫한 검색 트렌드를 탐색하세요'
},
album: {
area: {
all: '전체',
chinese: '중화권',
western: '서양',
korea: '한국',
japan: '일본'
}
}
}
};

View File

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

View File

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

View File

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

View File

@@ -1,49 +0,0 @@
export default {
title: '재생 기록',
heatmapTitle: '히트맵',
playCount: '{count}',
getHistoryFailed: '기록 가져오기 실패',
tabs: {
all: '전체 기록',
local: '로컬 기록',
cloud: '클라우드 기록'
},
categoryTabs: {
songs: '곡',
playlists: '플레이리스트',
albums: '앨범',
podcasts: '팟캐스트'
},
podcastTabs: {
episodes: '에피소드',
radios: '라디오'
},
noDescription: '설명 없음',
noData: '기록 없음',
getCloudRecordFailed: '클라우드 기록 가져오기 실패',
needLogin: 'cookie를 사용하여 로그인하여 클라우드 기록을 볼 수 있습니다',
merging: '기록 병합 중...',
heatmap: {
title: '재생 히트맵',
loading: '데이터 로딩 중...',
unit: '회 재생',
footerText: '마우스를 올려서 자세히 보기',
playCount: '{count}회 재생',
topSongs: '오늘의 인기곡',
times: '회',
totalPlays: '총 재생 횟수',
activeDays: '활동 일수',
noData: '재생 기록이 없습니다',
colorTheme: '색상 테마',
colors: {
green: '그린',
blue: '블루',
orange: '오렌지',
purple: '퍼플',
red: '레드'
},
mostPlayedSong: '가장 많이 재생한 노래',
mostActiveDay: '가장 활발한 날',
latestNightSong: '가장 늘게 재생한 노래'
}
};

View File

@@ -1,13 +0,0 @@
export default {
title: '로컬 음악',
scanFolder: '폴더 스캔',
removeFolder: '폴더 제거',
scanning: '스캔 중...',
scanComplete: '스캔 완료',
playAll: '모두 재생',
search: '로컬 음악 검색',
emptyState: '로컬 음악이 없습니다. 폴더를 선택하여 스캔하세요.',
fileNotFound: '파일을 찾을 수 없거나 이동되었습니다',
rescan: '다시 스캔',
songCount: '{count}곡'
};

View File

@@ -1,64 +0,0 @@
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:
'넷이즈 클라우드 뮤직 로그인 페이지를 열겠습니다. 로그인 완료 후 창을 닫아주세요',
loginFailed: '로그인 실패',
phoneRequired: '휴대폰 번호를 입력하세요',
passwordRequired: '비밀번호를 입력하세요',
phoneLoginFailed: '휴대폰 번호 로그인 실패, 휴대폰 번호와 비밀번호가 올바른지 확인하세요',
qrCheckFailed: 'QR코드 상태 확인 실패, 새로고침하여 다시 시도하세요',
qrLoading: 'QR코드 로딩 중...',
qrExpired: 'QR코드가 만료되었습니다. 클릭하여 새로고침하세요',
qrExpiredShort: 'QR코드 만료됨',
qrExpiredWarning: 'QR코드가 만료되었습니다. 클릭하여 새로운 QR코드를 받으세요',
qrScanned: 'QR코드가 스캔되었습니다. 휴대폰에서 로그인을 확인하세요',
qrScannedShort: '스캔됨',
qrScannedInfo: 'QR코드가 스캔되었습니다. 휴대폰에서 로그인을 확인하세요',
qrConfirmed: '로그인 성공, 리다이렉트 중...',
qrGenerating: 'QR코드를 생성 중...'
},
qrTitle: '넷이즈 클라우드 뮤직 QR코드 로그인',
uidWarning:
'주의: UID 로그인은 사용자 공개 정보를 확인하는 데만 사용할 수 있으며, 로그인 권한이 필요한 기능에 액세스할 수 없습니다.'
};

View File

@@ -1,146 +0,0 @@
export default {
nowPlaying: '현재 재생 중',
playlist: '재생 목록',
lyrics: '가사',
previous: '이전',
play: '재생',
pause: '일시정지',
next: '다음',
volumeUp: '볼륨 증가',
volumeDown: '볼륨 감소',
mute: '음소거',
unmute: '음소거 해제',
songNum: '총 곡 수: {num}',
addCorrection: '{num}초 앞당기기',
subtractCorrection: '{num}초 지연',
playFailed: '현재 곡 재생 실패, 다음 곡 재생',
parseFailedPlayNext: '곡 분석 실패, 다음 곡 재생',
consecutiveFailsError:
'재생 오류가 발생했습니다. 네트워크 문제 또는 유효하지 않은 음원일 수 있습니다. 재생 목록을 변경하거나 나중에 다시 시도하세요',
playListEnded: '재생 목록의 마지막 곡에 도달했습니다',
autoResumed: '자동으로 재생이 재개되었습니다',
resumeFailed: '재생 재개에 실패했습니다. 수동으로 시도해 주세요',
playMode: {
sequence: '순차 재생',
loop: '한 곡 반복',
random: '랜덤 재생'
},
fullscreen: {
enter: '전체화면',
exit: '전체화면 종료'
},
close: '닫기',
modeHint: {
single: '한 곡 반복',
list: '자동으로 다음 곡 재생'
},
lrc: {
noLrc: '가사가 없습니다. 음악을 감상해주세요',
noAutoScroll: '본 가사는 자동 스크롤을 지원하지 않습니다'
},
reparse: {
title: '음원 선택',
desc: '음원을 클릭하여 직접 분석하세요. 다음에 이 곡을 재생할 때 선택한 음원을 사용합니다',
success: '재분석 성공',
failed: '재분석 실패',
warning: '음원을 선택해주세요',
bilibiliNotSupported: 'B站 비디오는 재분석을 지원하지 않습니다',
processing: '분석 중...',
clear: '사용자 정의 음원 지우기',
customApiFailed: '사용자 정의 API 분석 실패, 기본 음원을 시도합니다...',
customApiError: '사용자 정의 API 요청 오류, 기본 음원을 시도합니다...'
},
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: '고급 설정',
intelligenceMode: {
title: '인텔리전스 모드',
needCookieLogin: '쿠키 방식으로 로그인한 후 인텔리전스 모드를 사용할 수 있습니다',
noFavoritePlaylist: '내가 좋아하는 음악 재생목록을 찾을 수 없습니다',
noLikedSongs: '아직 좋아한 노래가 없습니다',
loading: '인텔리전스 모드를 불러오는 중',
success: '총 {count}곡을 불러왔습니다',
failed: '인텔리전스 모드 목록을 가져오는 데 실패했습니다',
error: '인텔리전스 모드 재생 오류'
}
},
eq: {
title: '이퀄라이저',
reset: '재설정',
on: '켜기',
off: '끄기',
bass: '저음',
midrange: '중음',
treble: '고음',
presets: {
flat: '플랫',
pop: '팝',
rock: '록',
classical: '클래식',
jazz: '재즈',
electronic: '일렉트로닉',
hiphop: '힙합',
rb: 'R&B',
metal: '메탈',
vocal: '보컬',
dance: '댄스',
acoustic: '어쿠스틱',
custom: '사용자 정의'
}
},
// 플레이어 설정
settings: {
title: '재생 설정',
playbackSpeed: '재생 속도'
},
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

@@ -1,40 +0,0 @@
export default {
podcast: '팟캐스트',
mySubscriptions: '내 구독',
discover: '발견',
categories: '카테고리',
todayPerfered: '오늘의 추천',
recommended: '추천',
hotRanking: '인기',
newRanking: '신규',
subscribeCount: '구독자',
programCount: '에피소드',
subscribe: '구독',
subscribed: '구독 중',
unsubscribe: '구독 취소',
unsubscribed: '구독이 취소되었습니다',
subscribeSuccess: '구독되었습니다',
unsubscribeFailed: '구독 취소에 실패했습니다',
subscribeFailed: '구독에 실패했습니다',
radioDetail: '라디오 상세',
programList: '에피소드 목록',
playProgram: '재생',
recentPlayed: '최근 재생',
listeners: '청취자',
noSubscriptions: '구독 없음',
goDiscover: '팟캐스트 찾기',
searchPodcast: '팟캐스트 검색',
category: '카테고리',
all: '전체',
dj: 'DJ',
episodes: '화',
playAll: '전체 재생',
popularCategories: '인기 카테고리',
allCategories: '모든 카테고리',
categoryRadios: '카테고리 라디오',
exploreCategoryRadios: '더 많은 멋진 라디오 탐색',
hotRadios: '인기 라디오',
noCategoryRadios: '이 카테고리에 라디오가 없습니다',
searchPlaceholder: '팟캐스트, 에피소드 검색...',
searchResults: '검색 결과'
};

View File

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

View File

@@ -1,521 +0,0 @@
export default {
theme: '테마',
language: '언어',
regard: '정보',
logout: '로그아웃',
sections: {
basic: '기본 설정',
playback: '재생 설정',
application: '애플리케이션 설정',
network: '네트워크 설정',
system: '시스템 관리',
donation: '후원 지원',
about: '정보'
},
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: '가나다라마 바사아자차 카타파하'
},
gpuAcceleration: 'GPU 가속',
gpuAccelerationDesc:
'GPU 가속을 사용하면 애니메이션이 빠르게 재생되고 애니메이션이 느리게 재생되는 것보다 느릴 수 있습니다.',
gpuAccelerationRestart: 'GPU 가속 설정을 변경하면 애플리케이션을 다시 시작해야 합니다',
gpuAccelerationChangeSuccess:
'GPU 가속 설정이 업데이트되었습니다. 애플리케이션을 다시 시작하여 적용하십시오',
gpuAccelerationChangeError: 'GPU 가속 설정 업데이트에 실패했습니다',
tabletMode: '태블릿 모드',
tabletModeDesc:
'태블릿 모드를 사용하면 모바일 기기에서 PC 스타일의 인터페이스를 사용할 수 있습니다'
},
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: '앱을 다시 열 때 자동으로 재생을 계속할지 여부',
audioDevice: '오디오 출력 장치',
audioDeviceDesc: '스피커, 헤드폰 또는 블루투스 장치와 같은 오디오 출력 장치 선택',
testAudio: '테스트',
selectAudioDevice: '출력 장치 선택',
showStatusBar: '상태바 제어 기능 표시 여부',
showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)',
fallbackParser: '대체 분석 서비스 (GD Music)',
fallbackParserDesc:
'"GD Music"을 선택하고 일반 음원을 사용할 수 없을 때 이 서비스를 사용합니다.',
parserGD: 'GD Music (내장)',
parserCustom: '사용자 지정 API',
// 음원 라벨
sourceLabels: {
migu: 'Migu',
kugou: 'Kugou',
kuwo: 'Kuwo',
pyncmd: 'NetEase (내장)',
qq: 'QQ Music',
joox: 'JOOX',
bilibili: 'Bilibili',
gdmusic: 'GD Music',
lxMusic: 'LX Music',
custom: '사용자 지정 API'
},
customApi: {
sectionTitle: '사용자 지정 API 설정',
importConfig: 'JSON 설정 가져오기',
currentSource: '현재 음원',
notImported: '아직 사용자 지정 음원을 가져오지 않았습니다.',
importSuccess: '음원 가져오기 성공: {name}',
importFailed: '가져오기 실패: {message}',
enableHint: '사용하려면 먼저 JSON 구성 파일을 가져오세요',
status: {
imported: '사용자 지정 음원 가져옴',
notImported: '가져오지 않음'
}
},
lxMusic: {
tabs: {
sources: '음원 선택',
lxMusic: '낙설 음원',
customApi: '사용자 정의 API'
},
scripts: {
title: '가져온 스크립트',
importLocal: '로컬 가져오기',
importOnline: '온라인 가져오기',
urlPlaceholder: '낙설 음원 스크립트 URL 입력',
importBtn: '가져오기',
empty: '가져온 낙설 음원이 없습니다',
notConfigured: '설정되지 않음 (낙설 음원 탭에서 설정하세요)',
importHint: '소스 확장을 위해 호환되는 사용자 정의 API 플러그인을 가져옵니다',
noScriptWarning: '먼저 낙설 음원 스크립트를 가져오세요',
noSelectionWarning: '먼저 낙설 음원 소스를 선택하세요',
notFound: '음원이 존재하지 않습니다',
switched: '음원으로 전환되었습니다: {name}',
deleted: '음원이 삭제되었습니다: {name}',
enterUrl: '스크립트 URL을 입력하세요',
invalidUrl: '유효하지 않은 URL 형식',
invalidScript: '유효하지 않은 낙설 음원 스크립트입니다 (globalThis.lx 코드를 찾을 수 없음)',
nameRequired: '이름은 비워둘 수 없습니다',
renameSuccess: '이름이 변경되었습니다'
}
}
},
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: '캐시 지우기',
diskCache: '디스크 캐시',
diskCacheDesc: '재생한 음악과 가사를 로컬 디스크에 캐시하여 재생 속도를 높입니다',
cacheDirectory: '캐시 디렉터리',
cacheDirectoryDesc: '음악 및 가사 캐시 저장 경로를 사용자 지정',
selectDirectory: '디렉터리 선택',
openDirectory: '디렉터리 열기',
cacheMaxSize: '캐시 용량 제한',
cacheMaxSizeDesc: '용량 제한 도달 시 오래된 캐시를 자동 정리합니다',
cleanupPolicy: '정리 정책',
cleanupPolicyDesc: '캐시 용량 제한 도달 시 적용할 자동 정리 규칙',
cleanupPolicyOptions: {
lru: '최근 사용 안 함 우선',
fifo: '선입선출'
},
cacheStatus: '캐시 상태',
cacheStatusDesc: '사용량 {used} / 제한 {limit}',
cacheStatusDetail: '음악 {musicCount}곡, 가사 {lyricCount}곡',
manageDiskCache: '수동 디스크 캐시 정리',
manageDiskCacheDesc: '캐시 유형별로 정리',
clearMusicCache: '음악 캐시 정리',
clearLyricCache: '가사 캐시 정리',
clearAllCache: '전체 캐시 정리',
switchDirectoryMigrateTitle: '기존 캐시가 감지되었습니다',
switchDirectoryMigrateContent: '기존 캐시를 새 디렉터리로 마이그레이션할까요?',
switchDirectoryMigrateConfirm: '마이그레이션',
switchDirectoryDestroyTitle: '기존 캐시 삭제',
switchDirectoryDestroyContent:
'마이그레이션하지 않을 경우, 이전 디렉터리의 캐시 파일을 삭제할까요?',
switchDirectoryDestroyConfirm: '삭제',
switchDirectoryKeepOld: '기존 캐시 유지',
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: '지우기 성공, 일부 설정은 재시작 후 적용됩니다',
diskCacheClearSuccess: '디스크 캐시를 정리했습니다',
diskCacheClearFailed: '디스크 캐시 정리에 실패했습니다',
diskCacheStatsLoadFailed: '캐시 상태를 불러오지 못했습니다',
switchDirectorySuccess: '캐시 디렉터리가 변경되었습니다. 기존 캐시는 유지됩니다',
switchDirectoryFailed: '캐시 디렉터리 변경에 실패했습니다',
switchDirectoryMigrated: '캐시 디렉터리를 변경하고 {count}개 파일을 마이그레이션했습니다',
switchDirectoryDestroyed: '캐시 디렉터리를 변경하고 기존 캐시 {count}개 파일을 삭제했습니다'
}
},
about: {
version: '버전',
checkUpdate: '업데이트 확인',
checking: '확인 중...',
latest: '현재 최신 버전입니다',
hasUpdate: '새 버전 발견',
gotoUpdate: '업데이트하러 가기',
manualUpdate: '수동 업데이트',
gotoGithub: 'Github로 이동',
author: '작성자',
authorDesc: 'algerkong 별점🌟 부탁드려요',
messages: {
checkError: '업데이트 확인 실패, 나중에 다시 시도하세요'
}
},
validation: {
selectProxyProtocol: '프록시 프로토콜을 선택하세요',
proxyHost: '프록시 주소를 입력하세요',
portNumber: '유효한 포트 번호를 입력하세요 (1-65535)'
},
lyricSettings: {
title: '가사 설정',
tabs: {
display: '표시',
interface: '인터페이스',
typography: '텍스트',
background: '배경',
mobile: '모바일'
},
pureMode: '순수 모드',
hideCover: '커버 숨기기',
centerDisplay: '중앙 표시',
showTranslation: '번역 표시',
hideLyrics: '가사 숨기기',
hidePlayBar: '재생바 숨기기',
hideMiniPlayBar: '미니 재생바 숨기기',
showMiniPlayBar: '미니 재생바 표시',
backgroundTheme: '배경 테마',
themeOptions: {
default: '기본',
light: '밝음',
dark: '어둠'
},
fontSize: '폰트 크기',
fontSizeMarks: {
small: '작음',
medium: '중간',
large: '큼'
},
fontWeight: '글꼴 두께',
fontWeightMarks: {
thin: '가늘게',
normal: '보통',
bold: '굵게'
},
letterSpacing: '글자 간격',
letterSpacingMarks: {
compact: '좁음',
default: '기본',
loose: '넓음'
},
lineHeight: '줄 높이',
lineHeightMarks: {
compact: '좁음',
default: '기본',
loose: '넓음'
},
contentWidth: '콘텐츠 너비',
mobileLayout: '모바일 레이아웃',
layoutOptions: {
default: '기본',
ios: 'iOS 스타일',
android: '안드로이드 스타일'
},
mobileCoverStyle: '커버 스타일',
coverOptions: {
record: '레코드',
square: '정사각형',
full: '전체화면'
},
lyricLines: '가사 줄 수',
mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다',
// 배경 설정
background: {
useCustomBackground: '사용자 정의 배경 사용',
backgroundMode: '배경 모드',
modeOptions: {
solid: '단색',
gradient: '그라데이션',
image: '이미지',
css: 'CSS'
},
solidColor: '색상 선택',
presetColors: '프리셋 색상',
customColor: '사용자 정의 색상',
gradientEditor: '그라데이션 편집기',
gradientColors: '그라데이션 색상',
gradientDirection: '그라데이션 방향',
directionOptions: {
toBottom: '위에서 아래로',
toRight: '왼쪽에서 오른쪽으로',
toBottomRight: '왼쪽 위에서 오른쪽 아래로',
angle45: '45도',
toTop: '아래에서 위로',
toLeft: '오른쪽에서 왼쪽으로'
},
addColor: '색상 추가',
removeColor: '색상 제거',
imageUpload: '이미지 업로드',
imagePreview: '이미지 미리보기',
clearImage: '이미지 지우기',
imageBlur: '흐림',
imageBrightness: '밝기',
customCss: '사용자 정의 CSS 스타일',
customCssPlaceholder: 'CSS 스타일 입력, 예: background: linear-gradient(...)',
customCssHelp: '모든 CSS background 속성 지원',
reset: '기본값으로 재설정',
fileSizeLimit: '이미지 크기 제한: 20MB',
invalidImageFormat: '잘못된 이미지 형식',
imageTooLarge: '이미지가 너무 큽니다. 20MB 미만의 이미지를 선택하세요'
}
},
translationEngine: '가사 번역 엔진',
translationEngineOptions: {
none: '닫기',
opencc: 'OpenCC 중국어 번체'
},
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: '단축키 사용자 정의',
summaryReady: '단축키 구성이 저장 가능한 상태입니다',
summaryRecording: '새 단축키 조합을 입력 중입니다',
summaryBlocked: '충돌 또는 잘못된 항목을 먼저 수정하세요',
platformHintMac: 'macOS에서는 CommandOrControl이 Cmd로 표시됩니다',
platformHintWindows: 'Windows에서는 CommandOrControl이 Ctrl로 표시됩니다',
platformHintLinux: 'Linux에서는 CommandOrControl이 Ctrl로 표시됩니다',
platformHintGeneric: 'CommandOrControl은 운영체제에 맞게 자동 변환됩니다',
enabledCount: '활성화됨',
recordingTip: '필드를 클릭 후 조합키 입력, Esc 취소, Delete 비활성화',
shortcutConflict: '단축키 충돌',
inputPlaceholder: '클릭하여 단축키 입력',
clickToRecord: '클릭 후 단축키 입력',
recording: '입력 중...',
resetShortcuts: '기본값 복원',
restoreSingle: '복원',
disableAll: '모두 비활성화',
enableAll: '모두 활성화',
groups: {
playback: '재생 제어',
sound: '볼륨 및 즐겨찾기',
window: '창 제어'
},
togglePlay: '재생/일시정지',
togglePlayDesc: '현재 재생 상태를 전환합니다',
prevPlay: '이전 곡',
prevPlayDesc: '이전 곡으로 이동합니다',
nextPlay: '다음 곡',
nextPlayDesc: '다음 곡으로 이동합니다',
volumeUp: '볼륨 증가',
volumeUpDesc: '플레이어 볼륨을 높입니다',
volumeDown: '볼륨 감소',
volumeDownDesc: '플레이어 볼륨을 낮춥니다',
toggleFavorite: '즐겨찾기/즐겨찾기 취소',
toggleFavoriteDesc: '현재 곡 즐겨찾기를 전환합니다',
toggleWindow: '창 표시/숨기기',
toggleWindowDesc: '메인 창을 빠르게 표시/숨김합니다',
scopeGlobal: '전역',
scopeApp: '앱 내',
enabled: '활성화',
disabled: '비활성화',
issueInvalid: '잘못된 조합',
issueReserved: '시스템 예약',
registrationWarningTitle: '다음 단축키는 등록되지 않았습니다',
registrationOccupied: '시스템 또는 다른 앱에서 사용 중',
registrationInvalid: '단축키 형식이 잘못됨',
messages: {
resetSuccess: '기본 단축키로 복원되었습니다. 저장을 잊지 마세요',
conflict: '충돌하는 단축키가 있습니다. 다시 설정하세요',
saveSuccess: '단축키 설정이 저장되었습니다',
saveError: '단축키 저장 실패, 다시 시도하세요',
saveValidationError: '단축키 검증에 실패했습니다. 설정을 확인하세요',
partialRegistered: '저장되었지만 일부 전역 단축키는 등록되지 않았습니다',
cancelEdit: '수정이 취소되었습니다',
clearToDisable: '해당 단축키가 비활성화되었습니다',
invalidShortcut: '잘못된 단축키입니다. 유효한 조합을 입력하세요',
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

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

View File

@@ -1,53 +0,0 @@
export default {
profile: {
followers: '팔로워',
following: '팔로잉',
level: '레벨'
},
playlist: {
created: '생성한 플레이리스트',
mine: '내가 만든',
trackCount: '{count}곡',
playCount: '{count}회 재생'
},
tabs: {
created: '생성',
favorite: '즐겨찾기',
album: '앨범'
},
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

@@ -1,5 +0,0 @@
export default {
hotSongs: '热门歌曲',
albums: '专辑',
description: '艺人介绍'
};

View File

@@ -1,62 +0,0 @@
export default {
play: '播放',
next: '下一首',
previous: '上一首',
volume: '音量',
settings: '设置',
search: '搜索',
loading: '加载中...',
loadingMore: '加载更多...',
alipay: '支付宝',
wechat: '微信支付',
on: '开启',
off: '关闭',
show: '显示',
hide: '隐藏',
confirm: '确认',
cancel: '取消',
clear: '清空',
configure: '配置',
open: '打开',
modify: '修改',
success: '操作成功',
error: '操作失败',
warning: '警告',
info: '提示',
save: '保存',
delete: '删除',
refresh: '刷新',
retry: '重试',
reset: '重置',
loadFailed: '加载失败',
noData: '暂无数据',
back: '返回',
copySuccess: '已复制到剪贴板',
copyFailed: '复制失败',
validation: {
required: '此项是必填的',
invalidInput: '输入无效',
selectRequired: '请选择一个选项',
numberRange: '请输入 {min} 到 {max} 之间的数字'
},
viewMore: '查看更多',
noMore: '没有更多了',
selectAll: '全选',
playAll: '播放全部',
expand: '展开',
collapse: '收起',
songCount: '{count}首',
language: '语言',
today: '今天',
yesterday: '昨天',
tray: {
show: '显示',
quit: '退出',
playPause: '播放/暂停',
prev: '上一首',
next: '下一首',
pause: '暂停',
play: '播放',
favorite: '收藏'
}
};

View File

@@ -1,329 +0,0 @@
export default {
more: '更多',
homeListItem: {
loading: '加载中...'
},
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: '暂不更新',
checking: '检查更新中...',
prepareDownload: '准备下载...',
downloading: '下载中...',
readyToInstall: '更新包已下载完成,可以立即安装',
nowUpdate: '立即更新',
downloadFailed: '下载失败,请重试或手动下载',
startFailed: '启动下载失败,请重试或手动下载',
autoUpdateFailed: '自动更新失败',
openOfficialSite: '前往官网更新',
manualFallbackHint: '自动更新失败后,可前往官网下载安装最新版本。',
noDownloadUrl: '未找到适合当前系统的安装包,请手动下载',
installConfirmTitle: '安装更新',
installConfirmContent: '是否关闭应用并安装更新?',
manualInstallTip: '如果关闭应用后没有正常弹出安装程序,请至下载文件夹查找文件并手动打开。',
yesInstall: '立即安装',
noThanks: '稍后安装',
fileLocation: '文件位置',
copy: '复制路径',
copySuccess: '路径已复制到剪贴板',
copyFailed: '复制失败',
backgroundDownload: '后台下载'
},
disclaimer: {
title: '使用须知',
warning: '本应用为开发测试版本,功能尚不完善,可能存在较多问题和 Bug仅供学习交流使用。',
item1: '本应用仅供个人学习、研究和技术交流使用,请勿用于任何商业用途。',
item2: '请在下载后 24 小时内删除,如需长期使用请支持正版音乐服务。',
item3: '使用本应用即表示您理解并承担相关风险,开发者不对任何损失负责。',
agree: '我已阅读并同意',
disagree: '不同意并退出'
},
donate: {
title: '支持开发者',
subtitle: '您的支持是我前进的动力',
tip: '捐赠完全自愿,不捐赠也可以正常使用所有功能,感谢您的理解与支持!',
wechat: '微信',
alipay: '支付宝',
wechatQR: '微信收款码',
alipayQR: '支付宝收款码',
scanTip: '请使用手机扫描上方二维码进行捐赠',
enterApp: '进入应用',
noForce: '不强制捐赠,点击即可进入'
},
coffee: {
title: '请我喝咖啡',
alipay: '支付宝',
wechat: '微信支付',
alipayQR: '支付宝收款码',
wechatQR: '微信收款码',
coffeeDesc: '一杯咖啡,一份支持',
coffeeDescLinkText: '查看更多',
groupText: '微信公众号AlgerMusic',
messages: {
copySuccess: '已复制到剪贴板'
},
donateList: '请我喝咖啡'
},
playlistType: {
title: '歌单分类',
showAll: '显示全部',
hide: '隐藏一些'
},
recommendAlbum: {
title: '最新专辑'
},
recommendSinger: {
title: '每日推荐',
songlist: '每日推荐列表'
},
recommendSonglist: {
title: '本周最热音乐',
empty: '暂无推荐歌单'
},
dailyRecommend: {
title: '每日推荐',
badge: '推荐',
empty: '暂无推荐歌曲',
intelligenceHint: '开启心动模式,发现更多喜欢的音乐'
},
recommendMV: {
title: '推荐MV'
},
newAlbum: {
title: '专辑',
empty: '暂无新专辑'
},
recommendNewMusic: {
title: '新歌速递'
},
privateContent: {
title: '独家放送'
},
djProgram: {
title: '推荐电台'
},
homeHero: {
dailyRecommend: '每日推荐',
songs: '首',
playNow: '立即播放',
intelligenceMode: '心动模式',
intelligenceModeOn: '心动中',
intelligenceModeDesc: '开启智能推荐播放',
intelligenceModeActiveDesc: '根据你的喜好智能推荐',
startIntelligence: '开启心动',
stopIntelligence: '关闭心动',
playing: '播放中',
toplistDesc: '热门榜单',
mvDesc: '音乐视频',
playlistDesc: '精选歌单',
personalFm: '私人FM',
discoverMusic: '发现新音乐',
personalFmDesc: '根据你的喜好推荐',
recentPlays: '最近播放',
viewAll: '查看全部',
followedArtists: '关注歌手',
newSongs: '首新歌',
fromFollowedArtists: '来自你关注的歌手',
recommendNewMusic: '推荐新音乐',
newSongExpress: '新歌速递',
discoverNewReleases: '发现最新发行的好歌',
hotPlaylists: '精选歌单',
hotArtists: '热门歌手',
hotArtistsTitle: '热门艺人',
hotArtistsDesc: '当下最受欢迎的歌手',
fmTrash: '不喜欢',
fmNext: '下一首',
quickNav: {
myFavorite: '我的收藏',
playHistory: '播放历史',
myProfile: '我的主页',
toplist: '排行榜'
}
},
searchBar: {
login: '登录',
toLogin: '去登录',
logout: '退出登录',
set: '设置',
theme: '主题',
restart: '重启',
refresh: '刷新',
currentVersion: '当前版本',
searchPlaceholder: '搜索点什么吧...',
zoom: '页面缩放',
zoom100: '标准缩放100%',
resetZoom: '点击重置缩放',
zoomDefault: '标准缩放',
tabPlaylist: '播放列表',
tabMv: 'MV',
tabCharts: '排行榜',
cancelSearch: '取消',
intelligenceMode: '心动模式',
exitIntelligence: '退出心动模式'
},
titleBar: {
closeTitle: '请选择关闭方式',
minimizeToTray: '最小化到托盘',
exitApp: '退出应用',
rememberChoice: '记住我的选择',
closeApp: '关闭应用'
},
userPlayList: {
title: '{name}的常听'
},
musicList: {
searchSongs: '搜索歌曲',
noSearchResults: '没有找到相关歌曲',
switchToNormal: '切换到默认布局',
switchToCompact: '切换到紧凑布局',
playAll: '播放全部',
collect: '收藏',
collectSuccess: '收藏成功',
cancelCollectSuccess: '取消收藏成功',
operationFailed: '操作失败',
cancelCollect: '取消收藏',
addToPlaylist: '添加到播放列表',
addToPlaylistSuccess: '添加到播放列表成功',
songsAlreadyInPlaylist: '歌曲已存在于播放列表中',
locateCurrent: '定位当前播放',
historyRecommend: '历史日推',
fetchDatesFailed: '获取日期列表失败',
fetchSongsFailed: '获取歌曲列表失败',
noSongs: '暂无歌曲'
},
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: '添加链接',
options: '选项',
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: '搜索',
album: '专辑',
localMusic: '本地音乐',
pages: {
toplist: {
desc: '最具权威的音乐榜单,发现当下最热门的音乐'
},
mv: {
desc: '探索精彩视频内容',
loadingMore: '加载更多中...',
noMore: '— 已加载全部内容 —',
area: {
all: '全部',
mainland: '内地',
hktw: '港台',
western: '欧美',
japan: '日本',
korea: '韩国'
}
},
list: {
desc: '发现更多好听的歌单',
dailyRecommend: '每日推荐'
},
search: {
desc: '探索当下最热门的搜索趋势'
},
album: {
area: {
all: '全部',
chinese: '华语',
western: '欧美',
korea: '韩国',
japan: '日本'
}
}
}
};

View File

@@ -1,8 +0,0 @@
export default {
description: '您的捐赠将用于支持开发和维护工作,包括但不限于服务器维护、域名续费等。',
message: '留言时可留下您的邮箱或 github名称。',
refresh: '刷新列表',
toDonateList: '请我喝咖啡',
noMessage: '暂无留言',
title: '捐赠列表'
};

View File

@@ -1,113 +0,0 @@
export default {
title: '下载管理',
localMusic: '本地音乐',
count: '共 {count} 首歌曲',
clearAll: '清空记录',
settings: '设置',
tabs: {
downloading: '下载中',
downloaded: '已下载'
},
empty: {
noTasks: '暂无下载任务',
noDownloaded: '暂无已下载歌曲',
noDownloadedHint: '去下载你喜欢的歌曲吧'
},
progress: {
total: '总进度: {progress}%'
},
status: {
downloading: '下载中',
completed: '已完成',
failed: '失败',
unknown: '未知',
queued: '排队中',
paused: '已暂停',
cancelled: '已取消'
},
action: {
pause: '暂停',
resume: '恢复',
cancel: '取消',
cancelAll: '取消全部',
retrying: '重新获取链接...'
},
batch: {
complete: '下载完成:成功 {success}/{total} 首',
allComplete: '全部下载完成'
},
artist: {
unknown: '未知歌手'
},
delete: {
title: '删除确认',
message: '确定要删除歌曲 "{filename}" 吗?此操作不可恢复。',
confirm: '确定删除',
cancel: '取消',
success: '删除成功',
failed: '删除失败',
fileNotFound: '文件不存在或已被移动,已从记录中移除',
recordRemoved: '文件删除失败,但已从记录中移除'
},
clear: {
title: '清空下载记录',
message: '确定要清空所有下载记录吗?此操作不会删除已下载的音乐文件,但将清空所有记录。',
confirm: '确定清空',
cancel: '取消',
success: '下载记录已清空',
failed: '清空下载记录失败'
},
message: {
downloadComplete: '{filename} 下载完成',
downloadFailed: '{filename} 下载失败: {error}'
},
loading: '加载中...',
playStarted: '开始播放: {name}',
playFailed: '播放失败: {name}',
path: {
copy: '复制路径',
copied: '路径已复制到剪贴板',
copyFailed: '复制路径失败'
},
settingsPanel: {
title: '下载设置',
path: '下载位置',
pathDesc: '设置音乐文件下载保存的位置',
pathPlaceholder: '请选择下载路径',
noPathSelected: '请先选择下载路径',
select: '选择文件夹',
open: '打开文件夹',
saveLyric: '单独保存歌词文件',
saveLyricDesc: '下载歌曲时同时保存一份 .lrc 歌词文件',
fileFormat: '文件名格式',
fileFormatDesc: '设置下载音乐时的文件命名格式',
customFormat: '自定义格式',
separator: '分隔符',
separators: {
dash: '空格-空格',
underscore: '下划线',
space: '空格'
},
dragToArrange: '拖动排序或使用箭头按钮调整顺序:',
formatVariables: '可用变量',
preview: '预览效果:',
concurrency: '最大并发数',
concurrencyDesc: '同时下载的最大歌曲数量1-5',
saveSuccess: '下载设置已保存',
presets: {
songArtist: '歌曲名 - 歌手名',
artistSong: '歌手名 - 歌曲名',
songOnly: '仅歌曲名'
},
components: {
songName: '歌曲名',
artistName: '歌手名',
albumName: '专辑名'
}
},
error: {
incomplete: '文件下载不完整',
urlExpired: '下载链接已过期,正在重新获取',
resumeFailed: '恢复下载失败'
}
};

View File

@@ -1,13 +0,0 @@
export default {
title: '我的收藏',
count: '共 {count} 首',
batchDownload: '批量下载',
download: '下载 ({count})',
emptyTip: '还没有收藏歌曲',
downloadSuccess: '下载完成',
downloadFailed: '下载失败',
downloading: '正在下载中,请稍候...',
selectSongsFirst: '请先选择要下载的歌曲',
descending: '降',
ascending: '升'
};

View File

@@ -1,49 +0,0 @@
export default {
title: '播放历史',
heatmapTitle: '热力图',
playCount: '{count}',
getHistoryFailed: '获取历史记录失败',
categoryTabs: {
songs: '歌曲',
playlists: '歌单',
albums: '专辑',
podcasts: '播客'
},
podcastTabs: {
episodes: '节目',
radios: '电台'
},
tabs: {
all: '全部记录',
local: '本地记录',
cloud: '云端记录'
},
getCloudRecordFailed: '获取云端记录失败',
needLogin: '请使用cookie登录以查看云端记录',
merging: '正在合并记录...',
noDescription: '暂无描述',
noData: '暂无记录',
heatmap: {
title: '播放热力图',
loading: '正在加载数据...',
unit: '次播放',
footerText: '鼠标悬停查看详细信息',
playCount: '播放 {count} 次',
topSongs: '当天热门歌曲',
times: '次',
totalPlays: '总播放次数',
activeDays: '活跃天数',
noData: '暂无播放记录',
colorTheme: '配色方案',
colors: {
green: '绿色',
blue: '蓝色',
orange: '橙色',
purple: '紫色',
red: '红色'
},
mostPlayedSong: '播放最多的歌曲',
mostActiveDay: '最活跃的一天',
latestNightSong: '最晚播放的歌曲'
}
};

View File

@@ -1,13 +0,0 @@
export default {
title: '本地音乐',
scanFolder: '扫描文件夹',
removeFolder: '移除文件夹',
scanning: '正在扫描...',
scanComplete: '扫描完成',
playAll: '播放全部',
search: '搜索本地音乐',
emptyState: '暂无本地音乐,请先选择文件夹进行扫描',
fileNotFound: '文件不存在或已被移动',
rescan: '重新扫描',
songCount: '{count} 首歌曲'
};

View File

@@ -1,62 +0,0 @@
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: '登录成功',
loginFailed: '登录失败',
tokenLoginSuccess: 'Cookie登录成功',
uidLoginSuccess: 'UID登录成功',
loadError: '加载登录信息时出错',
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

@@ -1,146 +0,0 @@
export default {
nowPlaying: '正在播放',
playlist: '播放列表',
lyrics: '歌词',
previous: '上一个',
play: '播放',
pause: '暂停',
next: '下一个',
volumeUp: '音量增加',
volumeDown: '音量减少',
mute: '静音',
unmute: '取消静音',
songNum: '歌曲总数:{num}',
addCorrection: '提前 {num} 秒',
subtractCorrection: '延迟 {num} 秒',
playFailed: '当前歌曲播放失败,播放下一首',
parseFailedPlayNext: '歌曲解析失败,播放下一首',
consecutiveFailsError: '播放遇到错误,可能是网络波动或解析源失效,请切换播放列表或稍后重试',
playListEnded: '已播放到列表最后一首',
autoResumed: '已自动恢复播放',
resumeFailed: '恢复播放失败,请手动点击播放',
playMode: {
sequence: '顺序播放',
loop: '单曲循环',
random: '随机播放'
},
fullscreen: {
enter: '全屏',
exit: '退出全屏'
},
close: '关闭',
modeHint: {
single: '单曲循环',
list: '自动播放下一个'
},
lrc: {
noLrc: '暂无歌词, 请欣赏',
noAutoScroll: '本歌词不支持自动滚动'
},
reparse: {
title: '选择解析音源',
desc: '点击音源直接进行解析,下次播放此歌曲时将使用所选音源',
success: '重新解析成功',
failed: '重新解析失败',
warning: '请选择一个音源',
bilibiliNotSupported: 'B站视频不支持重新解析',
processing: '解析中...',
clear: '清除自定义音源',
customApiFailed: '自定义API解析失败正在尝试使用内置音源...',
customApiError: '自定义API请求出错正在尝试使用内置音源...'
},
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: '更多设置',
intelligenceMode: {
title: '心动模式',
needCookieLogin: '请使用 Cookie 方式登录后使用心动模式',
noFavoritePlaylist: '未找到我喜欢的音乐歌单',
noLikedSongs: '您还没有喜欢的歌曲',
loading: '正在加载心动模式',
success: '已加载 {count} 首歌曲',
failed: '获取心动模式列表失败',
error: '心动模式播放出错'
}
},
eq: {
title: '均衡器',
reset: '重置',
on: '开启',
off: '关闭',
bass: '低音',
midrange: '中音',
treble: '高音',
presets: {
flat: '平坦',
pop: '流行',
rock: '摇滚',
classical: '古典',
jazz: '爵士',
electronic: '电子',
hiphop: '嘻哈',
rb: 'R&B',
metal: '金属',
vocal: '人声',
dance: '舞曲',
acoustic: '原声',
custom: '自定义'
}
},
// 播放器设置
settings: {
title: '播放设置',
playbackSpeed: '播放速度'
},
// 定时关闭功能相关
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

@@ -1,40 +0,0 @@
export default {
podcast: '播客',
mySubscriptions: '我的订阅',
discover: '发现',
categories: '分类',
todayPerfered: '今日优选',
recommended: '推荐电台',
hotRanking: '热门榜',
newRanking: '新晋榜',
subscribeCount: '订阅',
programCount: '期节目',
subscribe: '订阅',
subscribed: '已订阅',
unsubscribe: '取消订阅',
unsubscribed: '已取消订阅',
subscribeSuccess: '订阅成功',
unsubscribeFailed: '取消订阅失败',
subscribeFailed: '订阅失败',
radioDetail: '电台详情',
programList: '节目列表',
playProgram: '播放节目',
recentPlayed: '最近播放',
listeners: '收听',
noSubscriptions: '暂无订阅',
goDiscover: '去发现播客',
searchPodcast: '搜索播客',
category: '分类',
all: '全部',
dj: '主播',
episodes: '期',
playAll: '播放全部',
popularCategories: '热门分类',
allCategories: '全部分类',
categoryRadios: '分类电台',
exploreCategoryRadios: '探索更多精彩电台',
hotRadios: '热门电台',
noCategoryRadios: '该分类暂无电台',
searchPlaceholder: '搜索播客、电台节目...',
searchResults: '搜索结果'
};

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