diff --git a/custom-api-readme.md b/custom-api-readme.md new file mode 100644 index 0000000..b5bae12 --- /dev/null +++ b/custom-api-readme.md @@ -0,0 +1,62 @@ +## 🎵 自定义音源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" +} +``` \ No newline at end of file diff --git a/src/i18n/lang/en-US/settings.ts b/src/i18n/lang/en-US/settings.ts index b5d3ebf..d7d0554 100644 --- a/src/i18n/lang/en-US/settings.ts +++ b/src/i18n/lang/en-US/settings.ts @@ -81,6 +81,19 @@ export default { showStatusBar: 'Show Status Bar', showStatusBarContent: 'You can display the music control function in your mac status bar (effective after a restart)' + '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', + + customApi: { + importConfig: 'Import JSON Config', + currentSource: 'Current Source', + notImported: 'No custom source imported yet.', + importSuccess: 'Successfully imported source: {name}', + importFailed: 'Import failed: {message}', + }, }, application: { closeAction: 'Close Action', diff --git a/src/i18n/lang/ja-JP/settings.ts b/src/i18n/lang/ja-JP/settings.ts index 182bf4d..12658ec 100644 --- a/src/i18n/lang/ja-JP/settings.ts +++ b/src/i18n/lang/ja-JP/settings.ts @@ -1,322 +1,335 @@ -export default { - theme: 'テーマ', - language: '言語', - regard: 'について', - logout: 'ログアウト', - sections: { - basic: '基本設定', - playback: '再生設定', - application: 'アプリケーション設定', - network: 'ネットワーク設定', - system: 'システム管理', - donation: '寄付サポート', - regard: 'について' - }, - basic: { - themeMode: 'テーマモード', - themeModeDesc: 'ライト/ダークテーマの切り替え', - autoTheme: 'システムに従う', - manualTheme: '手動切り替え', - language: '言語設定', - languageDesc: '表示言語を切り替え', - tokenManagement: 'Cookie管理', - tokenManagementDesc: 'NetEase Cloud MusicログインCookieを管理', - tokenStatus: '現在のCookieステータス', - tokenSet: '設定済み', - tokenNotSet: '未設定', - setToken: 'Cookieを設定', - modifyToken: 'Cookieを変更', - clearToken: 'Cookieをクリア', - font: 'フォント設定', - fontDesc: 'フォントを選択します。前に配置されたフォントが優先されます', - fontScope: { - global: 'グローバル', - lyric: '歌詞のみ' - }, - animation: 'アニメーション速度', - animationDesc: 'アニメーションを有効にするかどうか', - animationSpeed: { - slow: '非常に遅い', - normal: '通常', - fast: '非常に速い' - }, - fontPreview: { - title: 'フォントプレビュー', - chinese: '中国語', - english: 'English', - japanese: '日本語', - korean: '韓国語', - chineseText: '静夜思 床前明月光 疑是地上霜', - englishText: 'The quick brown fox jumps over the lazy dog', - japaneseText: 'あいうえお かきくけこ さしすせそ', - koreanText: '가나다라마 바사아자차 카타파하' - } - }, - playback: { - quality: '音質設定', - qualityDesc: '音楽再生の音質を選択(NetEase Cloud VIP)', - qualityOptions: { - standard: '標準', - higher: '高音質', - exhigh: '超高音質', - lossless: 'ロスレス', - hires: 'Hi-Res', - jyeffect: 'HD サラウンド', - sky: 'イマーシブサラウンド', - dolby: 'Dolby Atmos', - jymaster: '超高解像度マスター' - }, - musicSources: '音源設定', - musicSourcesDesc: '音楽解析に使用する音源プラットフォームを選択', - musicSourcesWarning: '少なくとも1つの音源プラットフォームを選択する必要があります', - musicUnblockEnable: '音楽解析を有効にする', - musicUnblockEnableDesc: '有効にすると、再生できない音楽の解析を試みます', - configureMusicSources: '音源を設定', - selectedMusicSources: '選択された音源:', - noMusicSources: '音源が選択されていません', - gdmusicInfo: 'GD音楽台は複数のプラットフォーム音源を自動解析し、最適な結果を自動選択できます', - autoPlay: '自動再生', - autoPlayDesc: 'アプリを再起動した際に自動的に再生を継続するかどうか', - showStatusBar: 'ステータスバーコントロール機能を表示するかどうか', - showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます(再起動後に有効)' - }, - application: { - closeAction: '閉じる動作', - closeActionDesc: 'ウィンドウを閉じる際の動作を選択', - closeOptions: { - ask: '毎回確認', - minimize: 'トレイに最小化', - close: '直接終了' - }, - shortcut: 'ショートカット設定', - shortcutDesc: 'グローバルショートカットをカスタマイズ', - download: 'ダウンロード管理', - downloadDesc: 'ダウンロードリストボタンを常に表示するかどうか', - unlimitedDownload: '無制限ダウンロード', - unlimitedDownloadDesc: '有効にすると音楽を無制限でダウンロードします(ダウンロード失敗の可能性があります)。デフォルトは300曲制限', - downloadPath: 'ダウンロードディレクトリ', - downloadPathDesc: '音楽ファイルのダウンロード場所を選択', - remoteControl: 'リモートコントロール', - remoteControlDesc: 'リモートコントロール機能を設定' - }, - network: { - apiPort: '音楽APIポート', - apiPortDesc: '変更後はアプリの再起動が必要です', - proxy: 'プロキシ設定', - proxyDesc: '音楽にアクセスできない場合はプロキシを有効にできます', - proxyHost: 'プロキシアドレス', - proxyHostPlaceholder: 'プロキシアドレスを入力してください', - proxyPort: 'プロキシポート', - proxyPortPlaceholder: 'プロキシポートを入力してください', - realIP: 'realIP設定', - realIPDesc: '制限により、このプロジェクトは海外での使用が制限されます。realIPパラメータを使用して国内IPを渡すことで解決できます', - messages: { - proxySuccess: 'プロキシ設定を保存しました。アプリ再起動後に有効になります', - proxyError: '入力が正しいかどうか確認してください', - realIPSuccess: '実IPアドレス設定を保存しました', - realIPError: '有効なIPアドレスを入力してください' - } - }, - system: { - cache: 'キャッシュ管理', - cacheDesc: 'キャッシュをクリア', - cacheClearTitle: 'クリアするキャッシュタイプを選択してください:', - cacheTypes: { - history: { - label: '再生履歴', - description: '再生した楽曲の記録をクリア' - }, - favorite: { - label: 'お気に入り記録', - description: 'ローカルのお気に入り楽曲記録をクリア(クラウドのお気に入りには影響しません)' - }, - user: { - label: 'ユーザーデータ', - description: 'ログイン情報とユーザー関連データをクリア' - }, - settings: { - label: 'アプリ設定', - description: 'アプリのすべてのカスタム設定をクリア' - }, - downloads: { - label: 'ダウンロード記録', - description: 'ダウンロード履歴をクリア(ダウンロード済みファイルは削除されません)' - }, - resources: { - label: '音楽リソース', - description: '読み込み済みの音楽ファイル、歌詞などのリソースキャッシュをクリア' - }, - lyrics: { - label: '歌詞リソース', - description: '読み込み済みの歌詞リソースキャッシュをクリア' - } - }, - restart: '再起動', - restartDesc: 'アプリを再起動', - messages: { - clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります' - } - }, - about: { - version: 'バージョン', - checkUpdate: '更新を確認', - checking: '確認中...', - latest: '現在最新バージョンです', - hasUpdate: '新しいバージョンが見つかりました', - gotoUpdate: '更新へ', - gotoGithub: 'Githubへ', - author: '作者', - authorDesc: 'algerkong スターを付けてください🌟', - messages: { - checkError: '更新確認に失敗しました。後でもう一度お試しください' - } - }, - validation: { - selectProxyProtocol: 'プロキシプロトコルを選択してください', - proxyHost: 'プロキシアドレスを入力してください', - portNumber: '有効なポート番号を入力してください(1-65535)' - }, - lyricSettings: { - title: '歌詞設定', - tabs: { - display: '表示', - interface: 'インターフェース', - typography: 'テキスト', - mobile: 'モバイル' - }, - pureMode: 'ピュアモード', - hideCover: 'カバーを非表示', - centerDisplay: '中央表示', - showTranslation: '翻訳を表示', - hideLyrics: '歌詞を非表示', - hidePlayBar: '再生バーを非表示', - hideMiniPlayBar: 'ミニ再生バーを非表示', - showMiniPlayBar: 'ミニ再生バーを表示', - backgroundTheme: '背景テーマ', - themeOptions: { - default: 'デフォルト', - light: 'ライト', - dark: 'ダーク' - }, - fontSize: 'フォントサイズ', - fontSizeMarks: { - small: '小', - medium: '中', - large: '大' - }, - letterSpacing: '文字間隔', - letterSpacingMarks: { - compact: 'コンパクト', - default: 'デフォルト', - loose: 'ゆったり' - }, - lineHeight: '行の高さ', - lineHeightMarks: { - compact: 'コンパクト', - default: 'デフォルト', - loose: 'ゆったり' - }, - mobileLayout: 'モバイルレイアウト', - layoutOptions: { - default: 'デフォルト', - ios: 'iOSスタイル', - android: 'Androidスタイル' - }, - mobileCoverStyle: 'カバースタイル', - coverOptions: { - record: 'レコード', - square: '正方形', - full: 'フルスクリーン' - }, - lyricLines: '歌詞行数', - mobileUnavailable: 'この設定はモバイルでのみ利用可能です' - }, - themeColor: { - title: '歌詞テーマカラー', - presetColors: 'プリセットカラー', - customColor: 'カスタムカラー', - preview: 'プレビュー効果', - previewText: '歌詞効果', - colorNames: { - 'spotify-green': 'Spotify グリーン', - 'apple-blue': 'Apple ブルー', - 'youtube-red': 'YouTube レッド', - orange: 'バイタルオレンジ', - purple: 'ミステリアスパープル', - pink: 'サクラピンク' - }, - tooltips: { - openColorPicker: 'カラーパレットを開く', - closeColorPicker: 'カラーパレットを閉じる' - }, - placeholder: '#1db954' - }, - shortcutSettings: { - title: 'ショートカット設定', - shortcut: 'ショートカット', - shortcutDesc: 'ショートカットをカスタマイズ', - shortcutConflict: 'ショートカットの競合', - inputPlaceholder: 'クリックしてショートカットを入力', - resetShortcuts: 'デフォルトに戻す', - disableAll: 'すべて無効', - enableAll: 'すべて有効', - togglePlay: '再生/一時停止', - prevPlay: '前の曲', - nextPlay: '次の曲', - volumeUp: '音量を上げる', - volumeDown: '音量を下げる', - toggleFavorite: 'お気に入り/お気に入り解除', - toggleWindow: 'ウィンドウ表示/非表示', - scopeGlobal: 'グローバル', - scopeApp: 'アプリ内', - enabled: '有効', - disabled: '無効', - messages: { - resetSuccess: 'デフォルトのショートカットに戻しました。保存を忘れずに', - conflict: '競合するショートカットがあります。再設定してください', - saveSuccess: 'ショートカット設定を保存しました', - saveError: 'ショートカットの保存に失敗しました。再試行してください', - cancelEdit: '変更をキャンセルしました', - disableAll: 'すべてのショートカットを無効にしました。保存を忘れずに', - enableAll: 'すべてのショートカットを有効にしました。保存を忘れずに' - } - }, - remoteControl: { - title: 'リモートコントロール', - enable: 'リモートコントロールを有効にする', - port: 'サービスポート', - allowedIps: '許可されたIPアドレス', - addIp: 'IPを追加', - emptyListHint: '空のリストはすべてのIPアクセスを許可することを意味します', - saveSuccess: 'リモートコントロール設定を保存しました', - accessInfo: 'リモートコントロールアクセスアドレス:' - }, - cookie: { - title: 'Cookie設定', - description: 'NetEase Cloud MusicのCookieを入力してください:', - placeholder: '完全なCookieを貼り付けてください...', - help: { - format: 'Cookieは通常「MUSIC_U=」で始まります', - source: 'ブラウザの開発者ツールのネットワークリクエストから取得できます', - storage: 'Cookie設定後、自動的にローカルストレージに保存されます' - }, - action: { - save: 'Cookieを保存', - paste: '貼り付け', - clear: 'クリア' - }, - validation: { - required: 'Cookieを入力してください', - format: 'Cookie形式が正しくない可能性があります。MUSIC_Uが含まれているか確認してください' - }, - message: { - saveSuccess: 'Cookieの保存に成功しました', - saveError: 'Cookieの保存に失敗しました', - pasteSuccess: '貼り付けに成功しました', - pasteError: '貼り付けに失敗しました。手動でコピーしてください' - }, - info: { - length: '現在の長さ:{length} 文字' - } - } +export default { + theme: 'テーマ', + language: '言語', + regard: 'について', + logout: 'ログアウト', + sections: { + basic: '基本設定', + playback: '再生設定', + application: 'アプリケーション設定', + network: 'ネットワーク設定', + system: 'システム管理', + donation: '寄付サポート', + regard: 'について' + }, + basic: { + themeMode: 'テーマモード', + themeModeDesc: 'ライト/ダークテーマの切り替え', + autoTheme: 'システムに従う', + manualTheme: '手動切り替え', + language: '言語設定', + languageDesc: '表示言語を切り替え', + tokenManagement: 'Cookie管理', + tokenManagementDesc: 'NetEase Cloud MusicログインCookieを管理', + tokenStatus: '現在のCookieステータス', + tokenSet: '設定済み', + tokenNotSet: '未設定', + setToken: 'Cookieを設定', + modifyToken: 'Cookieを変更', + clearToken: 'Cookieをクリア', + font: 'フォント設定', + fontDesc: 'フォントを選択します。前に配置されたフォントが優先されます', + fontScope: { + global: 'グローバル', + lyric: '歌詞のみ' + }, + animation: 'アニメーション速度', + animationDesc: 'アニメーションを有効にするかどうか', + animationSpeed: { + slow: '非常に遅い', + normal: '通常', + fast: '非常に速い' + }, + fontPreview: { + title: 'フォントプレビュー', + chinese: '中国語', + english: 'English', + japanese: '日本語', + korean: '韓国語', + chineseText: '静夜思 床前明月光 疑是地上霜', + englishText: 'The quick brown fox jumps over the lazy dog', + japaneseText: 'あいうえお かきくけこ さしすせそ', + koreanText: '가나다라마 바사아자차 카타파하' + } + }, + playback: { + quality: '音質設定', + qualityDesc: '音楽再生の音質を選択(NetEase Cloud VIP)', + qualityOptions: { + standard: '標準', + higher: '高音質', + exhigh: '超高音質', + lossless: 'ロスレス', + hires: 'Hi-Res', + jyeffect: 'HD サラウンド', + sky: 'イマーシブサラウンド', + dolby: 'Dolby Atmos', + jymaster: '超高解像度マスター' + }, + musicSources: '音源設定', + musicSourcesDesc: '音楽解析に使用する音源プラットフォームを選択', + musicSourcesWarning: '少なくとも1つの音源プラットフォームを選択する必要があります', + musicUnblockEnable: '音楽解析を有効にする', + musicUnblockEnableDesc: '有効にすると、再生できない音楽の解析を試みます', + configureMusicSources: '音源を設定', + selectedMusicSources: '選択された音源:', + noMusicSources: '音源が選択されていません', + gdmusicInfo: 'GD音楽台は複数のプラットフォーム音源を自動解析し、最適な結果を自動選択できます', + autoPlay: '自動再生', + autoPlayDesc: 'アプリを再起動した際に自動的に再生を継続するかどうか', + showStatusBar: 'ステータスバーコントロール機能を表示するかどうか', + showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます(再起動後に有効)' + showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます(再起動後に有効)', + fallbackParser: '代替解析サービス (GD音楽台)', + fallbackParserDesc: '「GD音楽台」にチェックが入っていて、通常の音源で再生できない場合、このサービスが使用されます。', + parserGD: 'GD音楽台 (内蔵)', + parserCustom: 'カスタムAPI', + + customApi: { + importConfig: 'JSON設定をインポート', + currentSource: '現在の音源', + notImported: 'カスタム音源はまだインポートされていません。', + importSuccess: '音源のインポートに成功しました: {name}', + importFailed: 'インポートに失敗しました: {message}', + }, + }, + application: { + closeAction: '閉じる動作', + closeActionDesc: 'ウィンドウを閉じる際の動作を選択', + closeOptions: { + ask: '毎回確認', + minimize: 'トレイに最小化', + close: '直接終了' + }, + shortcut: 'ショートカット設定', + shortcutDesc: 'グローバルショートカットをカスタマイズ', + download: 'ダウンロード管理', + downloadDesc: 'ダウンロードリストボタンを常に表示するかどうか', + unlimitedDownload: '無制限ダウンロード', + unlimitedDownloadDesc: '有効にすると音楽を無制限でダウンロードします(ダウンロード失敗の可能性があります)。デフォルトは300曲制限', + downloadPath: 'ダウンロードディレクトリ', + downloadPathDesc: '音楽ファイルのダウンロード場所を選択', + remoteControl: 'リモートコントロール', + remoteControlDesc: 'リモートコントロール機能を設定' + }, + network: { + apiPort: '音楽APIポート', + apiPortDesc: '変更後はアプリの再起動が必要です', + proxy: 'プロキシ設定', + proxyDesc: '音楽にアクセスできない場合はプロキシを有効にできます', + proxyHost: 'プロキシアドレス', + proxyHostPlaceholder: 'プロキシアドレスを入力してください', + proxyPort: 'プロキシポート', + proxyPortPlaceholder: 'プロキシポートを入力してください', + realIP: 'realIP設定', + realIPDesc: '制限により、このプロジェクトは海外での使用が制限されます。realIPパラメータを使用して国内IPを渡すことで解決できます', + messages: { + proxySuccess: 'プロキシ設定を保存しました。アプリ再起動後に有効になります', + proxyError: '入力が正しいかどうか確認してください', + realIPSuccess: '実IPアドレス設定を保存しました', + realIPError: '有効なIPアドレスを入力してください' + } + }, + system: { + cache: 'キャッシュ管理', + cacheDesc: 'キャッシュをクリア', + cacheClearTitle: 'クリアするキャッシュタイプを選択してください:', + cacheTypes: { + history: { + label: '再生履歴', + description: '再生した楽曲の記録をクリア' + }, + favorite: { + label: 'お気に入り記録', + description: 'ローカルのお気に入り楽曲記録をクリア(クラウドのお気に入りには影響しません)' + }, + user: { + label: 'ユーザーデータ', + description: 'ログイン情報とユーザー関連データをクリア' + }, + settings: { + label: 'アプリ設定', + description: 'アプリのすべてのカスタム設定をクリア' + }, + downloads: { + label: 'ダウンロード記録', + description: 'ダウンロード履歴をクリア(ダウンロード済みファイルは削除されません)' + }, + resources: { + label: '音楽リソース', + description: '読み込み済みの音楽ファイル、歌詞などのリソースキャッシュをクリア' + }, + lyrics: { + label: '歌詞リソース', + description: '読み込み済みの歌詞リソースキャッシュをクリア' + } + }, + restart: '再起動', + restartDesc: 'アプリを再起動', + messages: { + clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります' + } + }, + about: { + version: 'バージョン', + checkUpdate: '更新を確認', + checking: '確認中...', + latest: '現在最新バージョンです', + hasUpdate: '新しいバージョンが見つかりました', + gotoUpdate: '更新へ', + gotoGithub: 'Githubへ', + author: '作者', + authorDesc: 'algerkong スターを付けてください🌟', + messages: { + checkError: '更新確認に失敗しました。後でもう一度お試しください' + } + }, + validation: { + selectProxyProtocol: 'プロキシプロトコルを選択してください', + proxyHost: 'プロキシアドレスを入力してください', + portNumber: '有効なポート番号を入力してください(1-65535)' + }, + lyricSettings: { + title: '歌詞設定', + tabs: { + display: '表示', + interface: 'インターフェース', + typography: 'テキスト', + mobile: 'モバイル' + }, + pureMode: 'ピュアモード', + hideCover: 'カバーを非表示', + centerDisplay: '中央表示', + showTranslation: '翻訳を表示', + hideLyrics: '歌詞を非表示', + hidePlayBar: '再生バーを非表示', + hideMiniPlayBar: 'ミニ再生バーを非表示', + showMiniPlayBar: 'ミニ再生バーを表示', + backgroundTheme: '背景テーマ', + themeOptions: { + default: 'デフォルト', + light: 'ライト', + dark: 'ダーク' + }, + fontSize: 'フォントサイズ', + fontSizeMarks: { + small: '小', + medium: '中', + large: '大' + }, + letterSpacing: '文字間隔', + letterSpacingMarks: { + compact: 'コンパクト', + default: 'デフォルト', + loose: 'ゆったり' + }, + lineHeight: '行の高さ', + lineHeightMarks: { + compact: 'コンパクト', + default: 'デフォルト', + loose: 'ゆったり' + }, + mobileLayout: 'モバイルレイアウト', + layoutOptions: { + default: 'デフォルト', + ios: 'iOSスタイル', + android: 'Androidスタイル' + }, + mobileCoverStyle: 'カバースタイル', + coverOptions: { + record: 'レコード', + square: '正方形', + full: 'フルスクリーン' + }, + lyricLines: '歌詞行数', + mobileUnavailable: 'この設定はモバイルでのみ利用可能です' + }, + themeColor: { + title: '歌詞テーマカラー', + presetColors: 'プリセットカラー', + customColor: 'カスタムカラー', + preview: 'プレビュー効果', + previewText: '歌詞効果', + colorNames: { + 'spotify-green': 'Spotify グリーン', + 'apple-blue': 'Apple ブルー', + 'youtube-red': 'YouTube レッド', + orange: 'バイタルオレンジ', + purple: 'ミステリアスパープル', + pink: 'サクラピンク' + }, + tooltips: { + openColorPicker: 'カラーパレットを開く', + closeColorPicker: 'カラーパレットを閉じる' + }, + placeholder: '#1db954' + }, + shortcutSettings: { + title: 'ショートカット設定', + shortcut: 'ショートカット', + shortcutDesc: 'ショートカットをカスタマイズ', + shortcutConflict: 'ショートカットの競合', + inputPlaceholder: 'クリックしてショートカットを入力', + resetShortcuts: 'デフォルトに戻す', + disableAll: 'すべて無効', + enableAll: 'すべて有効', + togglePlay: '再生/一時停止', + prevPlay: '前の曲', + nextPlay: '次の曲', + volumeUp: '音量を上げる', + volumeDown: '音量を下げる', + toggleFavorite: 'お気に入り/お気に入り解除', + toggleWindow: 'ウィンドウ表示/非表示', + scopeGlobal: 'グローバル', + scopeApp: 'アプリ内', + enabled: '有効', + disabled: '無効', + messages: { + resetSuccess: 'デフォルトのショートカットに戻しました。保存を忘れずに', + conflict: '競合するショートカットがあります。再設定してください', + saveSuccess: 'ショートカット設定を保存しました', + saveError: 'ショートカットの保存に失敗しました。再試行してください', + cancelEdit: '変更をキャンセルしました', + disableAll: 'すべてのショートカットを無効にしました。保存を忘れずに', + enableAll: 'すべてのショートカットを有効にしました。保存を忘れずに' + } + }, + remoteControl: { + title: 'リモートコントロール', + enable: 'リモートコントロールを有効にする', + port: 'サービスポート', + allowedIps: '許可されたIPアドレス', + addIp: 'IPを追加', + emptyListHint: '空のリストはすべてのIPアクセスを許可することを意味します', + saveSuccess: 'リモートコントロール設定を保存しました', + accessInfo: 'リモートコントロールアクセスアドレス:' + }, + cookie: { + title: 'Cookie設定', + description: 'NetEase Cloud MusicのCookieを入力してください:', + placeholder: '完全なCookieを貼り付けてください...', + help: { + format: 'Cookieは通常「MUSIC_U=」で始まります', + source: 'ブラウザの開発者ツールのネットワークリクエストから取得できます', + storage: 'Cookie設定後、自動的にローカルストレージに保存されます' + }, + action: { + save: 'Cookieを保存', + paste: '貼り付け', + clear: 'クリア' + }, + validation: { + required: 'Cookieを入力してください', + format: 'Cookie形式が正しくない可能性があります。MUSIC_Uが含まれているか確認してください' + }, + message: { + saveSuccess: 'Cookieの保存に成功しました', + saveError: 'Cookieの保存に失敗しました', + pasteSuccess: '貼り付けに成功しました', + pasteError: '貼り付けに失敗しました。手動でコピーしてください' + }, + info: { + length: '現在の長さ:{length} 文字' + } + } }; \ No newline at end of file diff --git a/src/i18n/lang/ko-KR/settings.ts b/src/i18n/lang/ko-KR/settings.ts index f5621c2..2539d83 100644 --- a/src/i18n/lang/ko-KR/settings.ts +++ b/src/i18n/lang/ko-KR/settings.ts @@ -1,322 +1,335 @@ -export default { - theme: '테마', - language: '언어', - regard: '정보', - logout: '로그아웃', - sections: { - basic: '기본 설정', - playback: '재생 설정', - application: '애플리케이션 설정', - network: '네트워크 설정', - system: '시스템 관리', - donation: '후원 지원', - regard: '정보' - }, - basic: { - themeMode: '테마 모드', - themeModeDesc: '낮/밤 테마 전환', - autoTheme: '시스템 따라가기', - manualTheme: '수동 전환', - language: '언어 설정', - languageDesc: '표시 언어 전환', - tokenManagement: 'Cookie 관리', - tokenManagementDesc: '넷이즈 클라우드 뮤직 로그인 Cookie 관리', - tokenStatus: '현재 Cookie 상태', - tokenSet: '설정됨', - tokenNotSet: '설정되지 않음', - setToken: 'Cookie 설정', - modifyToken: 'Cookie 수정', - clearToken: 'Cookie 지우기', - font: '폰트 설정', - fontDesc: '폰트 선택, 앞에 있는 폰트를 우선 사용', - fontScope: { - global: '전역', - lyric: '가사만' - }, - animation: '애니메이션 속도', - animationDesc: '애니메이션 활성화 여부', - animationSpeed: { - slow: '매우 느림', - normal: '보통', - fast: '매우 빠름' - }, - fontPreview: { - title: '폰트 미리보기', - chinese: '中文', - english: 'English', - japanese: '日本語', - korean: '한국어', - chineseText: '静夜思 床前明月光 疑是地上霜', - englishText: 'The quick brown fox jumps over the lazy dog', - japaneseText: 'あいうえお かきくけこ さしすせそ', - koreanText: '가나다라마 바사아자차 카타파하' - } - }, - playback: { - quality: '음질 설정', - qualityDesc: '음악 재생 음질 선택 (넷이즈 클라우드 VIP)', - qualityOptions: { - standard: '표준', - higher: '높음', - exhigh: '매우 높음', - lossless: '무손실', - hires: 'Hi-Res', - jyeffect: 'HD 서라운드', - sky: '몰입형 서라운드', - dolby: '돌비 애트모스', - jymaster: '초고화질 마스터' - }, - musicSources: '음원 설정', - musicSourcesDesc: '음악 해석에 사용할 음원 플랫폼 선택', - musicSourcesWarning: '최소 하나의 음원 플랫폼을 선택해야 합니다', - musicUnblockEnable: '음악 해석 활성화', - musicUnblockEnableDesc: '활성화하면 재생할 수 없는 음악을 해석하려고 시도합니다', - configureMusicSources: '음원 구성', - selectedMusicSources: '선택된 음원:', - noMusicSources: '음원이 선택되지 않음', - gdmusicInfo: 'GD 뮤직은 여러 플랫폼 음원을 자동으로 해석하고 최적의 결과를 자동 선택합니다', - autoPlay: '자동 재생', - autoPlayDesc: '앱을 다시 열 때 자동으로 재생을 계속할지 여부', - showStatusBar: '상태바 제어 기능 표시 여부', - showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)' - }, - application: { - closeAction: '닫기 동작', - closeActionDesc: '창을 닫을 때의 동작 선택', - closeOptions: { - ask: '매번 묻기', - minimize: '트레이로 최소화', - close: '직접 종료' - }, - shortcut: '단축키 설정', - shortcutDesc: '전역 단축키 사용자 정의', - download: '다운로드 관리', - downloadDesc: '다운로드 목록 버튼을 항상 표시할지 여부', - unlimitedDownload: '무제한 다운로드', - unlimitedDownloadDesc: '활성화하면 음악을 무제한으로 다운로드합니다 (다운로드 실패가 발생할 수 있음), 기본 제한 300곡', - downloadPath: '다운로드 디렉토리', - downloadPathDesc: '음악 파일의 다운로드 위치 선택', - remoteControl: '원격 제어', - remoteControlDesc: '원격 제어 기능 설정' - }, - network: { - apiPort: '음악 API 포트', - apiPortDesc: '수정 후 앱을 재시작해야 합니다', - proxy: '프록시 설정', - proxyDesc: '음악에 액세스할 수 없을 때 프록시를 활성화할 수 있습니다', - proxyHost: '프록시 주소', - proxyHostPlaceholder: '프록시 주소를 입력하세요', - proxyPort: '프록시 포트', - proxyPortPlaceholder: '프록시 포트를 입력하세요', - realIP: 'realIP 설정', - realIPDesc: '제한으로 인해 이 프로젝트는 해외에서 사용할 때 제한을 받을 수 있으며, realIP 매개변수를 사용하여 국내 IP를 전달하여 해결할 수 있습니다', - messages: { - proxySuccess: '프록시 설정이 저장되었습니다. 앱을 재시작한 후 적용됩니다', - proxyError: '입력이 올바른지 확인하세요', - realIPSuccess: '실제 IP 설정이 저장되었습니다', - realIPError: '유효한 IP 주소를 입력하세요' - } - }, - system: { - cache: '캐시 관리', - cacheDesc: '캐시 지우기', - cacheClearTitle: '지울 캐시 유형을 선택하세요:', - cacheTypes: { - history: { - label: '재생 기록', - description: '재생한 곡 기록 지우기' - }, - favorite: { - label: '즐겨찾기 기록', - description: '로컬 즐겨찾기 곡 기록 지우기 (클라우드 즐겨찾기에는 영향 없음)' - }, - user: { - label: '사용자 데이터', - description: '로그인 정보 및 사용자 관련 데이터 지우기' - }, - settings: { - label: '앱 설정', - description: '앱의 모든 사용자 정의 설정 지우기' - }, - downloads: { - label: '다운로드 기록', - description: '다운로드 기록 지우기 (다운로드된 파일은 삭제되지 않음)' - }, - resources: { - label: '음악 리소스', - description: '로드된 음악 파일, 가사 등 리소스 캐시 지우기' - }, - lyrics: { - label: '가사 리소스', - description: '로드된 가사 리소스 캐시 지우기' - } - }, - restart: '재시작', - restartDesc: '앱 재시작', - messages: { - clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다' - } - }, - about: { - version: '버전', - checkUpdate: '업데이트 확인', - checking: '확인 중...', - latest: '현재 최신 버전입니다', - hasUpdate: '새 버전 발견', - gotoUpdate: '업데이트하러 가기', - gotoGithub: 'Github로 이동', - author: '작성자', - authorDesc: 'algerkong 별점🌟 부탁드려요', - messages: { - checkError: '업데이트 확인 실패, 나중에 다시 시도하세요' - } - }, - validation: { - selectProxyProtocol: '프록시 프로토콜을 선택하세요', - proxyHost: '프록시 주소를 입력하세요', - portNumber: '유효한 포트 번호를 입력하세요 (1-65535)' - }, - lyricSettings: { - title: '가사 설정', - tabs: { - display: '표시', - interface: '인터페이스', - typography: '텍스트', - mobile: '모바일' - }, - pureMode: '순수 모드', - hideCover: '커버 숨기기', - centerDisplay: '중앙 표시', - showTranslation: '번역 표시', - hideLyrics: '가사 숨기기', - hidePlayBar: '재생바 숨기기', - hideMiniPlayBar: '미니 재생바 숨기기', - showMiniPlayBar: '미니 재생바 표시', - backgroundTheme: '배경 테마', - themeOptions: { - default: '기본', - light: '밝음', - dark: '어둠' - }, - fontSize: '폰트 크기', - fontSizeMarks: { - small: '작음', - medium: '중간', - large: '큼' - }, - letterSpacing: '글자 간격', - letterSpacingMarks: { - compact: '좁음', - default: '기본', - loose: '넓음' - }, - lineHeight: '줄 높이', - lineHeightMarks: { - compact: '좁음', - default: '기본', - loose: '넓음' - }, - mobileLayout: '모바일 레이아웃', - layoutOptions: { - default: '기본', - ios: 'iOS 스타일', - android: '안드로이드 스타일' - }, - mobileCoverStyle: '커버 스타일', - coverOptions: { - record: '레코드', - square: '정사각형', - full: '전체화면' - }, - lyricLines: '가사 줄 수', - mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다' - }, - themeColor: { - title: '가사 테마 색상', - presetColors: '미리 설정된 색상', - customColor: '사용자 정의 색상', - preview: '미리보기 효과', - previewText: '가사 효과', - colorNames: { - 'spotify-green': 'Spotify 그린', - 'apple-blue': '애플 블루', - 'youtube-red': 'YouTube 레드', - orange: '활력 오렌지', - purple: '신비 퍼플', - pink: '벚꽃 핑크' - }, - tooltips: { - openColorPicker: '색상 선택기 열기', - closeColorPicker: '색상 선택기 닫기' - }, - placeholder: '#1db954' - }, - shortcutSettings: { - title: '단축키 설정', - shortcut: '단축키', - shortcutDesc: '단축키 사용자 정의', - shortcutConflict: '단축키 충돌', - inputPlaceholder: '클릭하여 단축키 입력', - resetShortcuts: '기본값 복원', - disableAll: '모두 비활성화', - enableAll: '모두 활성화', - togglePlay: '재생/일시정지', - prevPlay: '이전 곡', - nextPlay: '다음 곡', - volumeUp: '볼륨 증가', - volumeDown: '볼륨 감소', - toggleFavorite: '즐겨찾기/즐겨찾기 취소', - toggleWindow: '창 표시/숨기기', - scopeGlobal: '전역', - scopeApp: '앱 내', - enabled: '활성화', - disabled: '비활성화', - messages: { - resetSuccess: '기본 단축키로 복원되었습니다. 저장을 잊지 마세요', - conflict: '충돌하는 단축키가 있습니다. 다시 설정하세요', - saveSuccess: '단축키 설정이 저장되었습니다', - saveError: '단축키 저장 실패, 다시 시도하세요', - cancelEdit: '수정이 취소되었습니다', - disableAll: '모든 단축키가 비활성화되었습니다. 저장을 잊지 마세요', - enableAll: '모든 단축키가 활성화되었습니다. 저장을 잊지 마세요' - } - }, - remoteControl: { - title: '원격 제어', - enable: '원격 제어 활성화', - port: '서비스 포트', - allowedIps: '허용된 IP 주소', - addIp: 'IP 추가', - emptyListHint: '빈 목록은 모든 IP 액세스를 허용함을 의미합니다', - saveSuccess: '원격 제어 설정이 저장되었습니다', - accessInfo: '원격 제어 액세스 주소:' - }, - cookie: { - title: 'Cookie 설정', - description: '넷이즈 클라우드 뮤직의 Cookie를 입력하세요:', - placeholder: '완전한 Cookie를 붙여넣으세요...', - help: { - format: 'Cookie는 일반적으로 "MUSIC_U="로 시작합니다', - source: '브라우저 개발자 도구의 네트워크 요청에서 얻을 수 있습니다', - storage: 'Cookie 설정 후 자동으로 로컬 저장소에 저장됩니다' - }, - action: { - save: 'Cookie 저장', - paste: '붙여넣기', - clear: '지우기' - }, - validation: { - required: 'Cookie를 입력하세요', - format: 'Cookie 형식이 올바르지 않을 수 있습니다. MUSIC_U가 포함되어 있는지 확인하세요' - }, - message: { - saveSuccess: 'Cookie 저장 성공', - saveError: 'Cookie 저장 실패', - pasteSuccess: '붙여넣기 성공', - pasteError: '붙여넣기 실패, 수동으로 복사하세요' - }, - info: { - length: '현재 길이: {length} 문자' - } - } +export default { + theme: '테마', + language: '언어', + regard: '정보', + logout: '로그아웃', + sections: { + basic: '기본 설정', + playback: '재생 설정', + application: '애플리케이션 설정', + network: '네트워크 설정', + system: '시스템 관리', + donation: '후원 지원', + regard: '정보' + }, + basic: { + themeMode: '테마 모드', + themeModeDesc: '낮/밤 테마 전환', + autoTheme: '시스템 따라가기', + manualTheme: '수동 전환', + language: '언어 설정', + languageDesc: '표시 언어 전환', + tokenManagement: 'Cookie 관리', + tokenManagementDesc: '넷이즈 클라우드 뮤직 로그인 Cookie 관리', + tokenStatus: '현재 Cookie 상태', + tokenSet: '설정됨', + tokenNotSet: '설정되지 않음', + setToken: 'Cookie 설정', + modifyToken: 'Cookie 수정', + clearToken: 'Cookie 지우기', + font: '폰트 설정', + fontDesc: '폰트 선택, 앞에 있는 폰트를 우선 사용', + fontScope: { + global: '전역', + lyric: '가사만' + }, + animation: '애니메이션 속도', + animationDesc: '애니메이션 활성화 여부', + animationSpeed: { + slow: '매우 느림', + normal: '보통', + fast: '매우 빠름' + }, + fontPreview: { + title: '폰트 미리보기', + chinese: '中文', + english: 'English', + japanese: '日本語', + korean: '한국어', + chineseText: '静夜思 床前明月光 疑是地上霜', + englishText: 'The quick brown fox jumps over the lazy dog', + japaneseText: 'あいうえお かきくけこ さしすせそ', + koreanText: '가나다라마 바사아자차 카타파하' + } + }, + playback: { + quality: '음질 설정', + qualityDesc: '음악 재생 음질 선택 (넷이즈 클라우드 VIP)', + qualityOptions: { + standard: '표준', + higher: '높음', + exhigh: '매우 높음', + lossless: '무손실', + hires: 'Hi-Res', + jyeffect: 'HD 서라운드', + sky: '몰입형 서라운드', + dolby: '돌비 애트모스', + jymaster: '초고화질 마스터' + }, + musicSources: '음원 설정', + musicSourcesDesc: '음악 해석에 사용할 음원 플랫폼 선택', + musicSourcesWarning: '최소 하나의 음원 플랫폼을 선택해야 합니다', + musicUnblockEnable: '음악 해석 활성화', + musicUnblockEnableDesc: '활성화하면 재생할 수 없는 음악을 해석하려고 시도합니다', + configureMusicSources: '음원 구성', + selectedMusicSources: '선택된 음원:', + noMusicSources: '음원이 선택되지 않음', + gdmusicInfo: 'GD 뮤직은 여러 플랫폼 음원을 자동으로 해석하고 최적의 결과를 자동 선택합니다', + autoPlay: '자동 재생', + autoPlayDesc: '앱을 다시 열 때 자동으로 재생을 계속할지 여부', + showStatusBar: '상태바 제어 기능 표시 여부', + showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)' + showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)', + fallbackParser: '대체 분석 서비스 (GD Music)', + fallbackParserDesc: '"GD Music"을 선택하고 일반 음원을 사용할 수 없을 때 이 서비스를 사용합니다.', + parserGD: 'GD Music (내장)', + parserCustom: '사용자 지정 API', + + customApi: { + importConfig: 'JSON 설정 가져오기', + currentSource: '현재 음원', + notImported: '아직 사용자 지정 음원을 가져오지 않았습니다.', + importSuccess: '음원 가져오기 성공: {name}', + importFailed: '가져오기 실패: {message}', + }, + }, + application: { + closeAction: '닫기 동작', + closeActionDesc: '창을 닫을 때의 동작 선택', + closeOptions: { + ask: '매번 묻기', + minimize: '트레이로 최소화', + close: '직접 종료' + }, + shortcut: '단축키 설정', + shortcutDesc: '전역 단축키 사용자 정의', + download: '다운로드 관리', + downloadDesc: '다운로드 목록 버튼을 항상 표시할지 여부', + unlimitedDownload: '무제한 다운로드', + unlimitedDownloadDesc: '활성화하면 음악을 무제한으로 다운로드합니다 (다운로드 실패가 발생할 수 있음), 기본 제한 300곡', + downloadPath: '다운로드 디렉토리', + downloadPathDesc: '음악 파일의 다운로드 위치 선택', + remoteControl: '원격 제어', + remoteControlDesc: '원격 제어 기능 설정' + }, + network: { + apiPort: '음악 API 포트', + apiPortDesc: '수정 후 앱을 재시작해야 합니다', + proxy: '프록시 설정', + proxyDesc: '음악에 액세스할 수 없을 때 프록시를 활성화할 수 있습니다', + proxyHost: '프록시 주소', + proxyHostPlaceholder: '프록시 주소를 입력하세요', + proxyPort: '프록시 포트', + proxyPortPlaceholder: '프록시 포트를 입력하세요', + realIP: 'realIP 설정', + realIPDesc: '제한으로 인해 이 프로젝트는 해외에서 사용할 때 제한을 받을 수 있으며, realIP 매개변수를 사용하여 국내 IP를 전달하여 해결할 수 있습니다', + messages: { + proxySuccess: '프록시 설정이 저장되었습니다. 앱을 재시작한 후 적용됩니다', + proxyError: '입력이 올바른지 확인하세요', + realIPSuccess: '실제 IP 설정이 저장되었습니다', + realIPError: '유효한 IP 주소를 입력하세요' + } + }, + system: { + cache: '캐시 관리', + cacheDesc: '캐시 지우기', + cacheClearTitle: '지울 캐시 유형을 선택하세요:', + cacheTypes: { + history: { + label: '재생 기록', + description: '재생한 곡 기록 지우기' + }, + favorite: { + label: '즐겨찾기 기록', + description: '로컬 즐겨찾기 곡 기록 지우기 (클라우드 즐겨찾기에는 영향 없음)' + }, + user: { + label: '사용자 데이터', + description: '로그인 정보 및 사용자 관련 데이터 지우기' + }, + settings: { + label: '앱 설정', + description: '앱의 모든 사용자 정의 설정 지우기' + }, + downloads: { + label: '다운로드 기록', + description: '다운로드 기록 지우기 (다운로드된 파일은 삭제되지 않음)' + }, + resources: { + label: '음악 리소스', + description: '로드된 음악 파일, 가사 등 리소스 캐시 지우기' + }, + lyrics: { + label: '가사 리소스', + description: '로드된 가사 리소스 캐시 지우기' + } + }, + restart: '재시작', + restartDesc: '앱 재시작', + messages: { + clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다' + } + }, + about: { + version: '버전', + checkUpdate: '업데이트 확인', + checking: '확인 중...', + latest: '현재 최신 버전입니다', + hasUpdate: '새 버전 발견', + gotoUpdate: '업데이트하러 가기', + gotoGithub: 'Github로 이동', + author: '작성자', + authorDesc: 'algerkong 별점🌟 부탁드려요', + messages: { + checkError: '업데이트 확인 실패, 나중에 다시 시도하세요' + } + }, + validation: { + selectProxyProtocol: '프록시 프로토콜을 선택하세요', + proxyHost: '프록시 주소를 입력하세요', + portNumber: '유효한 포트 번호를 입력하세요 (1-65535)' + }, + lyricSettings: { + title: '가사 설정', + tabs: { + display: '표시', + interface: '인터페이스', + typography: '텍스트', + mobile: '모바일' + }, + pureMode: '순수 모드', + hideCover: '커버 숨기기', + centerDisplay: '중앙 표시', + showTranslation: '번역 표시', + hideLyrics: '가사 숨기기', + hidePlayBar: '재생바 숨기기', + hideMiniPlayBar: '미니 재생바 숨기기', + showMiniPlayBar: '미니 재생바 표시', + backgroundTheme: '배경 테마', + themeOptions: { + default: '기본', + light: '밝음', + dark: '어둠' + }, + fontSize: '폰트 크기', + fontSizeMarks: { + small: '작음', + medium: '중간', + large: '큼' + }, + letterSpacing: '글자 간격', + letterSpacingMarks: { + compact: '좁음', + default: '기본', + loose: '넓음' + }, + lineHeight: '줄 높이', + lineHeightMarks: { + compact: '좁음', + default: '기본', + loose: '넓음' + }, + mobileLayout: '모바일 레이아웃', + layoutOptions: { + default: '기본', + ios: 'iOS 스타일', + android: '안드로이드 스타일' + }, + mobileCoverStyle: '커버 스타일', + coverOptions: { + record: '레코드', + square: '정사각형', + full: '전체화면' + }, + lyricLines: '가사 줄 수', + mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다' + }, + themeColor: { + title: '가사 테마 색상', + presetColors: '미리 설정된 색상', + customColor: '사용자 정의 색상', + preview: '미리보기 효과', + previewText: '가사 효과', + colorNames: { + 'spotify-green': 'Spotify 그린', + 'apple-blue': '애플 블루', + 'youtube-red': 'YouTube 레드', + orange: '활력 오렌지', + purple: '신비 퍼플', + pink: '벚꽃 핑크' + }, + tooltips: { + openColorPicker: '색상 선택기 열기', + closeColorPicker: '색상 선택기 닫기' + }, + placeholder: '#1db954' + }, + shortcutSettings: { + title: '단축키 설정', + shortcut: '단축키', + shortcutDesc: '단축키 사용자 정의', + shortcutConflict: '단축키 충돌', + inputPlaceholder: '클릭하여 단축키 입력', + resetShortcuts: '기본값 복원', + disableAll: '모두 비활성화', + enableAll: '모두 활성화', + togglePlay: '재생/일시정지', + prevPlay: '이전 곡', + nextPlay: '다음 곡', + volumeUp: '볼륨 증가', + volumeDown: '볼륨 감소', + toggleFavorite: '즐겨찾기/즐겨찾기 취소', + toggleWindow: '창 표시/숨기기', + scopeGlobal: '전역', + scopeApp: '앱 내', + enabled: '활성화', + disabled: '비활성화', + messages: { + resetSuccess: '기본 단축키로 복원되었습니다. 저장을 잊지 마세요', + conflict: '충돌하는 단축키가 있습니다. 다시 설정하세요', + saveSuccess: '단축키 설정이 저장되었습니다', + saveError: '단축키 저장 실패, 다시 시도하세요', + cancelEdit: '수정이 취소되었습니다', + disableAll: '모든 단축키가 비활성화되었습니다. 저장을 잊지 마세요', + enableAll: '모든 단축키가 활성화되었습니다. 저장을 잊지 마세요' + } + }, + remoteControl: { + title: '원격 제어', + enable: '원격 제어 활성화', + port: '서비스 포트', + allowedIps: '허용된 IP 주소', + addIp: 'IP 추가', + emptyListHint: '빈 목록은 모든 IP 액세스를 허용함을 의미합니다', + saveSuccess: '원격 제어 설정이 저장되었습니다', + accessInfo: '원격 제어 액세스 주소:' + }, + cookie: { + title: 'Cookie 설정', + description: '넷이즈 클라우드 뮤직의 Cookie를 입력하세요:', + placeholder: '완전한 Cookie를 붙여넣으세요...', + help: { + format: 'Cookie는 일반적으로 "MUSIC_U="로 시작합니다', + source: '브라우저 개발자 도구의 네트워크 요청에서 얻을 수 있습니다', + storage: 'Cookie 설정 후 자동으로 로컬 저장소에 저장됩니다' + }, + action: { + save: 'Cookie 저장', + paste: '붙여넣기', + clear: '지우기' + }, + validation: { + required: 'Cookie를 입력하세요', + format: 'Cookie 형식이 올바르지 않을 수 있습니다. MUSIC_U가 포함되어 있는지 확인하세요' + }, + message: { + saveSuccess: 'Cookie 저장 성공', + saveError: 'Cookie 저장 실패', + pasteSuccess: '붙여넣기 성공', + pasteError: '붙여넣기 실패, 수동으로 복사하세요' + }, + info: { + length: '현재 길이: {length} 문자' + } + } }; \ No newline at end of file diff --git a/src/i18n/lang/zh-CN/settings.ts b/src/i18n/lang/zh-CN/settings.ts index df40155..5f25a5e 100644 --- a/src/i18n/lang/zh-CN/settings.ts +++ b/src/i18n/lang/zh-CN/settings.ts @@ -79,6 +79,21 @@ export default { autoPlayDesc: '重新打开应用时是否自动继续播放', showStatusBar: '是否显示状态栏控制功能', showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)' + showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)', + + fallbackParser: 'GD音乐台(music.gdstudio.xyz)设置', + fallbackParserDesc: 'GD音乐台将自动尝试多个音乐平台进行解析,无需额外配置。优先级高于其他解析方式,但是请求可能较慢。感谢(music.gdstudio.xyz)\n', + parserGD: 'GD 音乐台 (内置)', + parserCustom: '自定义 API', + + // 自定义API相关的提示 + customApi: { + importConfig: '导入 JSON 配置', + currentSource: '当前音源', + notImported: '尚未导入自定义音源。', + importSuccess: '成功导入音源: {name}', + importFailed: '导入失败: {message}', + }, }, application: { closeAction: '关闭行为', diff --git a/src/i18n/lang/zh-Hant/settings.ts b/src/i18n/lang/zh-Hant/settings.ts index 4468c19..17e5a43 100644 --- a/src/i18n/lang/zh-Hant/settings.ts +++ b/src/i18n/lang/zh-Hant/settings.ts @@ -79,6 +79,19 @@ export default { autoPlayDesc: '重新開啟應用程式時是否自動繼續播放', showStatusBar: '是否顯示狀態列控制功能', showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)' + showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)', + fallbackParser: '備用解析服務 (GD音樂台)', + fallbackParserDesc: '當勾選「GD音樂台」且常規音源無法播放時,將使用此服務嘗試解析。', + parserGD: 'GD 音樂台 (內建)', + parserCustom: '自訂 API', + + customApi: { + importConfig: '匯入 JSON 設定', + currentSource: '目前音源', + notImported: '尚未匯入自訂音源。', + importSuccess: '成功匯入音源:{name}', + importFailed: '匯入失敗:{message}', + }, }, application: { closeAction: '關閉行為', diff --git a/src/main/modules/fileManager.ts b/src/main/modules/fileManager.ts index b7e74ed..20b434f 100644 --- a/src/main/modules/fileManager.ts +++ b/src/main/modules/fileManager.ts @@ -275,6 +275,39 @@ export function initializeFileManager() { } } }); + + // 处理导入自定义API插件的请求 + ipcMain.handle('import-custom-api-plugin', async () => { + const result = await dialog.showOpenDialog({ + title: '选择自定义音源配置文件', + filters: [{ name: 'JSON Files', extensions: ['json'] }], + properties: ['openFile'] + }); + + if (result.canceled || result.filePaths.length === 0) { + return null; + } + + const filePath = result.filePaths[0]; + try { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + + // 基础验证,确保它是个合法的JSON并且包含关键字段 + const pluginData = JSON.parse(fileContent); + if (!pluginData.name || !pluginData.apiUrl) { + throw new Error('无效的插件文件,缺少 name 或 apiUrl 字段。'); + } + + return { + name: pluginData.name, + content: fileContent // 返回完整的JSON字符串 + }; + } catch (error: any) { + console.error('读取或解析插件文件失败:', error); + // 向渲染进程抛出错误,以便UI可以显示提示 + throw new Error(`文件读取或解析失败: ${error.message}`); + } + }); } /** diff --git a/src/main/set.json b/src/main/set.json index 59ae862..dfe9406 100644 --- a/src/main/set.json +++ b/src/main/set.json @@ -28,4 +28,7 @@ "contentZoomFactor": 1, "autoTheme": false, "manualTheme": "light" + "manualTheme": "light", + "customApiPlugin": "", + "customApiPluginName": "", } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 4af0cd2..11fb828 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -21,6 +21,7 @@ interface API { onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void; onLanguageChanged: (callback: (locale: string) => void) => void; removeDownloadListeners: () => void; + importCustomApiPlugin: () => Promise<{ name: string; content: string } | null>; invoke: (channel: string, ...args: any[]) => Promise; } diff --git a/src/preload/index.ts b/src/preload/index.ts index c47ac7e..0d021e9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -18,6 +18,7 @@ const api = { sendSong: (data) => ipcRenderer.send('update-current-song', data), unblockMusic: (id, data, enabledSources) => ipcRenderer.invoke('unblock-music', id, data, enabledSources), + importCustomApiPlugin: () => ipcRenderer.invoke('import-custom-api-plugin'), // 歌词窗口关闭事件 onLyricWindowClosed: (callback: () => void) => { ipcRenderer.on('lyric-window-closed', () => callback()); @@ -54,7 +55,7 @@ const api = { return ipcRenderer.invoke(channel, ...args); } return Promise.reject(new Error(`未授权的 IPC 通道: ${channel}`)); - } + }, }; // 创建带类型的ipcRenderer对象,暴露给渲染进程 diff --git a/src/renderer/api/music.ts b/src/renderer/api/music.ts index ea574f7..0f52b73 100644 --- a/src/renderer/api/music.ts +++ b/src/renderer/api/music.ts @@ -10,6 +10,8 @@ import requestMusic from '@/utils/request_music'; import { searchAndGetBilibiliAudioUrl } from './bilibili'; import { parseFromGDMusic } from './gdmusic'; +import { parseFromCustomApi } from './parseFromCustomApi'; +import type { ParsedMusicResult } from './gdmusic'; const { addData, getData, deleteData } = musicDB; @@ -114,7 +116,7 @@ const getBilibiliAudio = async (data: SongResult) => { * @param data 歌曲数据 * @returns 解析结果,失败时返回null */ -const getGDMusicAudio = async (id: number, data: SongResult) => { +const getGDMusicAudio = async (id: number, data: SongResult): Promise => { // <-- 在这里明确声明返回类型 try { const gdResult = await parseFromGDMusic(id, data, '999'); if (gdResult) { @@ -146,19 +148,19 @@ const getUnblockMusicAudio = (id: number, data: SongResult, sources: any[]) => { * @returns 解析结果 */ export const getParsingMusicUrl = async (id: number, data: SongResult) => { - if(isElectron){ + if (isElectron) { const settingStore = useSettingsStore(); // 如果禁用了音乐解析功能,则直接返回空结果 if (!settingStore.setData.enableMusicUnblock) { return Promise.resolve({ data: { code: 404, message: '音乐解析功能已禁用' } }); } - + // 1. 确定使用的音源列表(自定义或全局) const songId = String(id); const savedSourceStr = localStorage.getItem(`song_source_${songId}`); let musicSources: any[] = []; - + try { if (savedSourceStr) { // 使用自定义音源 @@ -168,37 +170,55 @@ export const getParsingMusicUrl = async (id: number, data: SongResult) => { // 使用全局音源设置 musicSources = settingStore.setData.enabledMusicSources || []; console.log(`使用全局音源设置:`, musicSources); - if (musicSources.length > 0) { - return getUnblockMusicAudio(id, data, musicSources); - } } } catch (e) { - console.error('解析音源设置失败,使用全局设置', e); + console.error('解析音源设置失败,回退到默认全局设置', e); musicSources = settingStore.setData.enabledMusicSources || []; } - - // 2. 按优先级解析 - - // 2.1 Bilibili解析(优先级最高) + + const quality = settingStore.setData.musicQuality || 'higher'; + + // 优先级 1: 自定义 API + if (musicSources.includes('custom') && settingStore.setData.customApiPlugin) { + console.log('尝试使用 自定义API 解析...'); + const customResult = await parseFromCustomApi(id, data, quality); + if (customResult) { + return customResult; // 成功则直接返回 + } + console.log('自定义API解析失败,继续尝试其他音源...'); + } + + // 优先级 2: Bilibili if (musicSources.includes('bilibili')) { - return await getBilibiliAudio(data); + console.log('尝试使用 Bilibili 解析...'); + const bilibiliResult = await getBilibiliAudio(data); + if (bilibiliResult?.data?.data?.url) { // 检查返回的 URL 是否有效 + return bilibiliResult; + } + console.log('Bilibili解析失败,继续尝试其他音源...'); } - - // 2.2 GD音乐台解析 + + // 优先级 3: GD 音乐台 if (musicSources.includes('gdmusic')) { + console.log('尝试使用 GD音乐台 解析...'); const gdResult = await getGDMusicAudio(id, data); - if (gdResult) return gdResult; - // GD解析失败,继续下一步 - console.log('GD音乐台解析失败,尝试使用其他音源'); + if (gdResult) { + return gdResult; + } + console.log('GD音乐台解析失败,继续尝试其他音源...'); } - console.log('musicSources', musicSources); - // 2.3 使用unblockMusic解析其他音源 - if (musicSources.length > 0) { - return getUnblockMusicAudio(id, data, musicSources); + + // 优先级 4: UnblockMusic (migu, kugou, pyncmd) + const unblockSources = musicSources.filter( + source => !['custom', 'bilibili', 'gdmusic'].includes(source) + ); + if (unblockSources.length > 0) { + console.log('尝试使用 UnblockMusic 解析:', unblockSources); + return getUnblockMusicAudio(id, data, unblockSources); } } - // 3. 后备方案:使用API请求 + // 后备方案:使用API请求 console.log('无可用音源或不在Electron环境中,使用API请求'); return requestMusic.get('/music', { params: { id } }); }; diff --git a/src/renderer/api/parseFromCustomApi.ts b/src/renderer/api/parseFromCustomApi.ts new file mode 100644 index 0000000..e257739 --- /dev/null +++ b/src/renderer/api/parseFromCustomApi.ts @@ -0,0 +1,106 @@ +import axios from 'axios'; +import {get} from 'lodash'; +import {useSettingsStore} from '@/store'; + +// 从同级目录的 gdmusic.ts 导入类型,确保兼容性 +import type {ParsedMusicResult} from './gdmusic'; + +/** + * 定义自定义API JSON插件的结构 + */ +interface CustomApiPlugin { + name: string; + apiUrl: string; + method?: 'GET' | 'POST'; + params: Record; + qualityMapping?: Record; + responseUrlPath: string; +} + +/** + * 从用户导入的自定义API JSON配置中解析音乐URL + */ +export const parseFromCustomApi = async ( + id: number, + _songData: any, + quality: string = 'higher', + timeout: number = 10000 +): Promise => { + const settingsStore = useSettingsStore(); + const pluginString = settingsStore.setData.customApiPlugin; + + if (!pluginString) { + return null; + } + + let plugin: CustomApiPlugin; + try { + plugin = JSON.parse(pluginString); + if (!plugin.apiUrl || !plugin.params || !plugin.responseUrlPath) { + console.error('自定义API:JSON配置文件格式不正确。'); + return null; + } + } catch (error) { + console.error('自定义API:解析JSON配置文件失败。', error); + return null; + } + + console.log(`自定义API:正在使用插件 [${plugin.name}] 进行解析...`); + + try { + // 1. 准备请求参数,替换占位符 + const finalParams: Record = {}; + for (const [key, value] of Object.entries(plugin.params)) { + if (value === '{songId}') { + finalParams[key] = String(id); + } else if (value === '{quality}') { + // 使用 qualityMapping (如果存在) 进行音质翻译,否则直接使用原quality + finalParams[key] = plugin.qualityMapping?.[quality] ?? quality; + } else { + // 固定值参数 + finalParams[key] = value; + } + } + + // 2. 判断请求方法,默认为GET + const method = plugin.method?.toUpperCase() === 'POST' ? 'POST' : 'GET'; + let response; + + // 3. 根据方法发送不同的请求 + if (method === 'POST') { + console.log('自定义API:发送 POST 请求到:', plugin.apiUrl, '参数:', finalParams); + response = await axios.post(plugin.apiUrl, finalParams, { timeout }); + } else { // 默认为 GET + const finalUrl = `${plugin.apiUrl}?${new URLSearchParams(finalParams).toString()}`; + console.log('自定义API:发送 GET 请求到:', finalUrl); + response = await axios.get(finalUrl, { timeout }); + } + + // 4. 使用 lodash.get 安全地从响应数据中提取URL + const musicUrl = get(response.data, plugin.responseUrlPath); + + if (musicUrl && typeof musicUrl === 'string') { + console.log('自定义API:成功获取URL!'); + // 5. 组装成应用所需的标准格式并返回 + return { + data: { + data: { + url: musicUrl, + br: parseInt(quality) * 1000, + size: 0, + md5: '', + platform: plugin.name.toLowerCase().replace(/\s/g, ''), + gain: 0 + }, + params: { id, type: 'song' } + } + }; + } else { + console.error('自定义API:根据路径未能从响应中找到URL:', plugin.responseUrlPath); + return null; + } + } catch (error) { + console.error(`自定义API [${plugin.name}] 执行失败:`, error); + return null; + } +}; \ No newline at end of file diff --git a/src/renderer/components/settings/MusicSourceSettings.vue b/src/renderer/components/settings/MusicSourceSettings.vue index a235343..c55c027 100644 --- a/src/renderer/components/settings/MusicSourceSettings.vue +++ b/src/renderer/components/settings/MusicSourceSettings.vue @@ -1,65 +1,87 @@ + \ No newline at end of file diff --git a/src/renderer/store/modules/player.ts b/src/renderer/store/modules/player.ts index 6f99a0a..1b9bc46 100644 --- a/src/renderer/store/modules/player.ts +++ b/src/renderer/store/modules/player.ts @@ -78,10 +78,14 @@ export const isBilibiliIdMatch = (id1: string | number, id2: string | number): b // 提取公共函数:获取B站视频URL export const getSongUrl = async ( - id: string | number, - songData: SongResult, - isDownloaded: boolean = false + id: string | number, + songData: SongResult, + isDownloaded: boolean = false ) => { + const numericId = typeof id === 'string' ? parseInt(id, 10) : id; + const settingsStore = useSettingsStore(); + const { message } = createDiscreteApi(['message']); // 引入 message API 用于提示 + try { if (songData.playMusicUrl) { return songData.playMusicUrl; @@ -92,8 +96,8 @@ export const getSongUrl = async ( if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) { try { songData.playMusicUrl = await getBilibiliAudioUrl( - songData.bilibiliData.bvid, - songData.bilibiliData.cid + songData.bilibiliData.bvid, + songData.bilibiliData.cid ); return songData.playMusicUrl; } catch (error) { @@ -104,14 +108,48 @@ export const getSongUrl = async ( return songData.playMusicUrl || ''; } - const numericId = typeof id === 'string' ? parseInt(id, 10) : id; - // 检查是否有自定义音源设置 + // ==================== 自定义API最优先 ==================== + // 检查用户是否在全局设置中启用了 'custom' 音源 + const globalSources = settingsStore.setData.enabledMusicSources || []; + const useCustomApiGlobally = globalSources.includes('custom'); + + // 检查歌曲是否有专属的 'custom' 音源设置 const songId = String(id); - const savedSource = localStorage.getItem(`song_source_${songId}`); + const savedSourceStr = localStorage.getItem(`song_source_${songId}`); + let useCustomApiForSong = false; + if (savedSourceStr) { + try { + const songSources = JSON.parse(savedSourceStr); + useCustomApiForSong = songSources.includes('custom'); + } catch (e) { /* ignore parsing error */ } + } + // 如果全局或歌曲专属设置中启用了自定义API,则最优先尝试 + if ( (useCustomApiGlobally || useCustomApiForSong) && settingsStore.setData.customApiPlugin) { + console.log(`优先级 1: 尝试使用自定义API解析歌曲 ${id}...`); + try { + // 直接从 api 目录导入 parseFromCustomApi 函数 + const { parseFromCustomApi } = await import('@/api/parseFromCustomApi'); + const customResult = await parseFromCustomApi(numericId, cloneDeep(songData), settingsStore.setData.musicQuality || 'higher'); + + if (customResult && customResult.data && customResult.data.data && customResult.data.data.url) { + console.log('自定义API解析成功!'); + if (isDownloaded) return customResult.data.data as any; + return customResult.data.data.url; + } else { + // 自定义API失败,给出提示,然后继续走默认流程 + console.log('自定义API解析失败,将使用默认降级流程...'); + message.warning('自定义API解析失败,正在尝试使用内置音源...'); // 给用户一个提示 + } + } catch (error) { + console.error('调用自定义API时发生错误:', error); + message.error('自定义API请求出错,正在尝试使用内置音源...'); + } + } + // 如果自定义API失败或未启用,则执行【原有】的解析流程 // 如果有自定义音源设置,直接使用getParsingMusicUrl获取URL - if (savedSource && songData.source !== 'bilibili') { + if (savedSourceStr && songData.source !== 'bilibili') { try { console.log(`使用自定义音源解析歌曲 ID: ${songId}`); const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); @@ -129,28 +167,33 @@ export const getSongUrl = async ( // 正常获取URL流程 const { data } = await getMusicUrl(numericId, isDownloaded); - let url = ''; - let songDetail = null; - try { - if (data.data[0].freeTrialInfo || !data.data[0].url) { + if (data && data.data && data.data[0]) { + const songDetail = data.data[0]; + const hasNoUrl = !songDetail.url; + const isTrial = !!songDetail.freeTrialInfo; + + if (hasNoUrl || isTrial) { + console.log(`官方URL无效 (无URL: ${hasNoUrl}, 试听: ${isTrial}),进入内置备用解析...`); const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); - url = res.data.data.url; - songDetail = res.data.data; - } else { - songDetail = data.data[0] as any; + if (isDownloaded) return res?.data?.data as any; + return res?.data?.data?.url || null; } - } catch (error) { - console.error('error', error); - url = data.data[0].url || ''; + + console.log('官方API解析成功!'); + if (isDownloaded) return songDetail as any; + return songDetail.url; } - if (isDownloaded) { - return songDetail; - } - url = url || data.data[0].url; - return url; + + console.log('官方API返回数据结构异常,进入内置备用解析...'); + const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); + if (isDownloaded) return res?.data?.data as any; + return res?.data?.data?.url || null; + } catch (error) { - console.error('error', error); - return null; + console.error('官方API请求失败,进入内置备用解析流程:', error); + const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); + if (isDownloaded) return res?.data?.data as any; + return res?.data?.data?.url || null; } }; diff --git a/src/renderer/store/modules/settings.ts b/src/renderer/store/modules/settings.ts index 214b8e9..85f5c36 100644 --- a/src/renderer/store/modules/settings.ts +++ b/src/renderer/store/modules/settings.ts @@ -64,6 +64,17 @@ export const useSettingsStore = defineStore('settings', () => { // 初始化 setData setData.value = getInitialSettings(); + /** + * 保存导入的自定义API插件 + * @param plugin 包含name和content的对象 + */ + const setCustomApiPlugin = (plugin: { name: string; content: string }) => { + setSetData({ + customApiPlugin: plugin.content, + customApiPluginName: plugin.name + }); + }; + const toggleTheme = () => { if (setData.value.autoTheme) { // 如果是自动模式,切换到手动模式并设置相反的主题 @@ -209,5 +220,7 @@ export const useSettingsStore = defineStore('settings', () => { initializeSettings, initializeTheme, initializeSystemFonts + initializeSystemFonts, + setCustomApiPlugin, }; });