diff --git a/README.md b/README.md index 544f728..ffb0cf2 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@

English | - 简体中文 | - 繁體中文 + 简体中文

echo is a native desktop music player and Spotify client written in Rust. echo brings your entire Spotify library — liked songs, playlists, albums, and the artists you follow — plus your local music files into one fast, keyboard-friendly app, with full playback control, synced lyrics, and dynamic theming. A companion terminal client (`spotify`) ships in the same install for when you'd rather live in the terminal. diff --git a/README.zh.md b/README.zh.md index 44a6978..a4ebf32 100644 --- a/README.zh.md +++ b/README.zh.md @@ -7,8 +7,7 @@

English | - 简体中文 | - 繁體中文 + 简体中文

echo 是一款用 Rust 编写的原生桌面音乐播放器和 Spotify 客户端。echo 将您的整个 Spotify 库——喜欢的歌曲、播放列表、专辑以及关注的艺术家——加上本地音乐文件汇集到一个快速、键盘友好的应用中,提供完整的播放控制、同步歌词和动态主题。同一安装包还附带一个终端客户端(`spotify`),在您想待在终端里时随时可用。 diff --git a/README.zht.md b/README.zht.md deleted file mode 100644 index 1d532fc..0000000 --- a/README.zht.md +++ /dev/null @@ -1,300 +0,0 @@ -

- - - ECHO-RS - - -

-

- English | - 简体中文 | - 繁體中文 -

- -echo 是一款用 Rust 編寫的原生桌面音樂播放器和 Spotify 用戶端。echo 將您的整個 Spotify 庫——喜歡的歌曲、播放清單、專輯以及追蹤的藝術家——加上本地音樂檔案匯集到一個快速、鍵盤友善的應用程式中,提供完整的播放控制、同步歌詞和動態主題。同一安裝套件還附帶一個終端機用戶端(`spotify`),在您想待在終端機裡時隨時可用。 - -![echo 桌面應用程式](assets/echo-desktop.png) - -## 功能特性 - -- **原生桌面應用程式**:基於 GPUI(Zed 的 UI 框架)建置,介面快速且由 GPU 加速,可在 Windows、macOS 和 Linux 上執行。既可以用滑鼠操作,也可以完全用鍵盤驅動。 -- **完整的音樂庫**:一個側邊欄收納播放清單、專輯和追蹤的藝術家,並支援熱門曲目、最近播放和熱門藝術家檢視。 -- **全面的播放控制**:在正在播放列中即可播放/暫停、上一曲/下一曲、跳轉進度、隨機播放、重複、音量、佇列和裝置切換。 -- **同步歌詞**:時間同步的歌詞可直接顯示在播放列中,或以全螢幕檢視呈現。 -- **最新動態**:來自您追蹤藝術家的近期專輯與單曲資訊流,最多每 6 小時重新整理一次。 -- **極速喜歡的歌曲**:採用全域快取架構。您整個喜歡的歌曲庫會快取在本地(`~/.config/echo/liked_songs.json`),實現零延遲的捲動瀏覽,即使有數千首儲存的曲目也毫無壓力。保持同步通常只需一次請求;完整重讀會限速進行、可斷點續傳,並在 Spotify 限流時自動退避。 -- **庫管理**:建立、重新命名、刪除播放清單並將其組織到資料夾中;在自己的播放清單中重新排列曲目順序。 -- **本地音樂支援**:掃描本地音樂資料夾,播放本地檔案,建立也可引用 Spotify 曲目的本地播放清單。 -- **搜尋**:快速全域搜尋(`ctrl-k`),涵蓋 Spotify 目錄和已掃描的本地曲目。 -- **動態主題**:內建多套主題,並支援即時主題編輯——參見[主題](#主題)。 -- **終端機用戶端**:同一安裝也會將功能完備的 `spotify` TUI 加入您的 `PATH`——參見[終端機用戶端 (TUI)](#終端機用戶端-tui)。 - -## 設定 - -1. **Spotify Premium**:需要使用 Spotify Premium 帳戶才能透過 Spotify Web API 進行播放控制。 -2. **Spotify 開發者應用**: - - 前往 [Spotify 開發者儀表板](https://developer.spotify.com/dashboard/)。 - - 建立一個應用並取得您的 `Client ID` 和 `Client Secret`。 - - 將 `http://127.0.0.1:8888/callback` 新增到應用的 Redirect URIs 中。 - - echo 還使用 `http://127.0.0.1:8989/login` 進行內部第一方 Spotify 工作階段。 - -### 安裝 - -一行指令即可完成安裝:桌面應用程式**與** `spotify` 終端機指令。 - -**Linux 與 macOS** - -```bash -curl -fsSL https://github.com/and2049/echo/releases/latest/download/install.sh | sh -``` - -**Windows**(PowerShell) - -```powershell -irm https://github.com/and2049/echo/releases/latest/download/install.ps1 | iex -``` - -兩者皆不需系統管理員權限,都會將 `spotify` 加入 `PATH`(安裝後請開啟新終端機),並把桌面應用程式加入開始功能表、Launchpad 或應用程式選單。 - -| 平台 | 安裝位置 | -| --- | --- | -| Windows | `%LOCALAPPDATA%\Programs\echo`(透過發佈的 MSI 安裝,x64) | -| macOS | `/Applications/echo.app`,並將 `spotify` 連結至 `~/.local/bin`(Apple Silicon) | -| Linux | `~/.local/share/echo`,並將兩個指令連結至 `~/.local/bin`(x86_64) | - -指定版本或解除安裝: - -```bash -curl -fsSL https://github.com/and2049/echo/releases/latest/download/install.sh | sh -s -- --version 0.4.6 -curl -fsSL https://github.com/and2049/echo/releases/latest/download/install.sh | sh -s -- --uninstall -``` - -```powershell -& ([scriptblock]::Create((irm https://github.com/and2049/echo/releases/latest/download/install.ps1))) -Version 0.4.6 -& ([scriptblock]::Create((irm https://github.com/and2049/echo/releases/latest/download/install.ps1))) -Uninstall -``` - -解除安裝不會刪除 `~/.config/echo` 中的設定。 - -在 Linux 上,桌面應用程式相依於數個系統程式庫——Debian/Ubuntu 下: - -```bash -sudo apt-get install libasound2 libdbus-1-3 libssl3 \ - libfontconfig1 libxkbcommon0 libxkbcommon-x11-0 libwayland-client0 libx11-xcb1 -``` - -桌面環境通常已經帶有其中大部分。算繪優先使用 Vulkan,並可回退到 OpenGL,因此 -`libvulkan1` 與顯示卡驅動程式值得安裝,但並非必要。 - -#### 更新 - -首次安裝之後,echo 可自我更新——不需重新安裝,也不需系統管理員權限: - -```bash -spotify upgrade # 更新至最新版本 -spotify upgrade --check # 僅檢查是否有可用更新 -spotify upgrade 0.4.6 # 更新至指定版本 -``` - -桌面應用程式也可透過**設定 → 更新 → 檢查更新**完成同樣操作。兩者都會就地取代執行檔與內建主題,並提示重新啟動。 - -### 從原始碼建置 - -複製倉庫並使用 Cargo 建置: - -**Linux 相依性**(Ubuntu/Debian): - -```bash -sudo apt-get install -y --no-install-recommends \ - libasound2-dev libdbus-1-dev pkg-config libssl-dev \ - libfontconfig-dev libwayland-dev libx11-xcb-dev libxkbcommon-x11-dev -``` - -```bash -git clone https://github.com/and2049/echo.git -cd echo -cargo build --release -p echo-desktop -p spotify-tui -``` - -執行桌面應用程式或終端機用戶端: - -```bash -./target/release/echo-desktop # 桌面應用程式 -./target/release/spotify # 終端機用戶端 -``` - -裸的 `cargo build --release`(或 `cargo run`)只建置 `spotify` 終端機用戶端——它是工作區的預設成員——因此要建置桌面應用程式請額外指定 `-p echo-desktop`。 - -> 終端機指令名為 `spotify`——之前的名稱與 shell 內建指令 `echo` 衝突。設定與快取仍位於 `~/.config/echo/`,因此現有環境在改名後可繼續使用。在 Windows 上,如果官方 Spotify 用戶端的目錄恰好在您的 `PATH` 中,請確保 `%USERPROFILE%\.cargo\bin`(或您安裝此執行檔的位置)排在前面。 - -首次執行時,echo 會提示您輸入 `Client ID` 和 `Client Secret`,然後開啟瀏覽器以透過 Spotify 進行身分驗證。 - -## 桌面應用程式 - -桌面應用程式支援滑鼠操作——點擊播放清單、藝術家或曲目即可開啟,使用正在播放列中的控制項,還可拖曳曲目來重排自己的播放清單。它也完全可以用鍵盤驅動。任何時候按 `?` 可查看應用程式內快捷鍵面板,按 `ctrl-,` 開啟設定,按 `t` 切換主題。快捷鍵與下方的終端機用戶端一致。 - -**導覽**:`j` / `k` / `↓` / `↑` 移動,`gg` / `G` 跳到開頭 / 結尾,`ctrl-b` / `ctrl-f` 翻頁,`ctrl-u` / `ctrl-d` 翻半頁,`enter` / `z` 開啟,`h` / `esc` 返回,`←` / `→` 在窗格間移動焦點,`tab` 切換分頁,`gc` 跳轉到正在播放的曲目。 - -**播放**:`space` 播放/暫停,`[` / `]`(或 `ctrl-←` / `ctrl-→`)上一曲 / 下一曲,`,` / `.` 跳轉進度,`-` / `=` 音量,`shift-M` 靜音,`s` / `r` 隨機播放 / 重複,`shift-D` 裝置選單,`shift-L` 全螢幕歌詞,`ctrl-shift-L` 播放列內歌詞,`shift-F` 沉浸檢視。 - -**庫操作**:`l` 喜歡/取消喜歡,`a` 新增至播放清單,`shift-A` 曲目操作,`q` / `shift-Q` 佇列,`shift-J` / `shift-K` 在自己的播放清單中移動曲目,`dd` 刪除,`v` 選取範圍,`m` 釘選,`c` / `e` 建立 / 重新命名。 - -**尋找內容**:`ctrl-k` 全域搜尋,`/` 篩選目前清單,`n` / `shift-N` 下一個 / 上一個符合項目,`:` 指令列,`t` 主題,`?` 說明,`ctrl-q` 結束。 - -關閉視窗時 echo 會繼續播放:在 Windows 和 Linux 上隱藏到系統匣圖示(點擊圖示恢復視窗,或選擇結束),在 macOS 上保留在 Dock 中。可用 `:tray off` 或「設定 → 視窗」關閉此行為;`ctrl-q` 始終結束。再次啟動 echo 會把正在執行的實例帶到前景,而不是開啟第二個。 - -`:` 指令列接受與終端機用戶端相同的指令——參見[命令](#命令)。 - -## 終端機用戶端 (TUI) - -同一安裝還附帶 `spotify`,一個功能完備的終端機用戶端,支援在終端機中直接算繪影像。它與桌面應用程式共用 echo 的快取、設定、播放引擎和指令。 - -![echo 終端機用戶端](assets/echo-tui.png) - -- **終端機影像支援**:直接在終端機中呈現高品質專輯封面和播放清單封面(支援 Kitty、Sixel 以及區塊式回退方案)。 -- **桌面應用程式的全部功能**:相同的音樂庫、搜尋、本地音樂、播放控制和 `:` 指令,完全由鍵盤驅動。 - -echo 主要由鍵盤驅動。 - -### 全域導航 -- `j` / `k` 或 `Down` / `Up`:向下 / 向上移動 -- `gg` / `G`:跳到第一個 / 最後一個項目 -- `Ctrl-b` / `Ctrl-f` 或 `Page Up` / `Page Down`:移動一頁 -- `Ctrl-u` / `Ctrl-d`:移動半頁 -- `Ctrl-l`:清除並完整重繪 TUI -- `gc`:跳轉到正在播放的曲目或其可用上下文 -- `Enter` 或 `z`:選擇項目 / 開啟播放清單 / 播放曲目 -- `h` / `q` / `Esc` / `Backspace`:返回 / 關閉對話方塊 / 清除搜尋 -- `Tab`:切換分頁(例如,播放清單 ↔ 專輯,搜尋曲目 ↔ 搜尋專輯) -- `:`:進入命令模式 -- `/`:在曲目清單中搜尋 -- `f`:全域搜尋 -- `n` / `N`:跳轉到清單中的下一個 / 上一個搜尋結果 - -### 播放控制 -- `Space`:播放 / 暫停 -- `]` / `>`:下一曲 -- `[` / `<`:上一曲 -- `,` / `.`:向後 / 向前快轉 5 秒 -- `0`:跳到目前曲目開頭 -- `M`(Shift + m):靜音 / 回復之前的音量 -- `s`:切換隨機播放 -- `r`:切換重複模式(關閉 → 單曲循環 → 清單循環) -- `=` / `-`:音量增大 / 減小(1%) -- `+` / `_`:音量增大 / 減小(5%) -- `D`(Shift + d):開啟裝置選擇選單 -- `L`(Shift + l):切換全螢幕同步歌詞介面 -- `Ctrl + Shift + L`:切換精簡同步歌詞檢視 - -### 曲目與庫操作 -- `l`:喜歡 / 取消喜歡所選曲目 -- `A`(Shift + a):開啟懸停曲目的操作選單(若未聚焦在曲目頁面則開啟目前播放曲目的選單) -- `p`:將剪下的播放清單貼上到資料夾中 -- `a`:將所選曲目新增到播放清單 / 將所選專輯新增到庫中 -- `q`:將目前懸停的曲目加入佇列 -- `Q`(Shift + q):開啟佇列檢視 -- `m`:釘選 / 取消釘選播放清單 -- `T`(Shift + t):切換資料庫封面縮圖(在播放清單 / 專輯名稱旁顯示封面) -- `c`:快速建立新播放清單 -- `e`:快速重新命名播放清單或資料夾 -- `v`:進入視覺模式以進行多選 -- `d`(雙擊):刪除播放清單/資料夾,或從自訂播放清單中移除曲目 -- `x`:剪下播放清單(以便移動到資料夾中) -- `J` / `K`(Shift + j / k,桌面版):在自己播放清單中將所選曲目下移 / 上移(也支援拖放);需要原始排序方式 -- `R`(Shift + r):強制重新整理 - -曲目操作選單會根據來源自動調整。Spotify 曲目支援複製連結、喜歡和專輯入庫等操作。本地曲目支援複製絕對路徑和在系統檔案管理員中顯示檔案。兩種來源都保留專輯/藝術家導覽、插入播放清單和加入佇列等操作(如適用)。 - -## 命令 - -在命令模式(`:`)下,您可以使用以下指令: -- `:search `:搜尋曲目或專輯。 -- `:newplaylist `:建立新播放清單。 -- `:newlocalplaylist `:建立儲存在本機的本地播放清單。 -- `:localpath `:設定本地音樂資料夾並掃描。路徑必須為絕對路徑,支援 macOS、Windows 和 Linux。 -- `:rescanlocal`:重新掃描已設定的本地音樂資料夾。 -- `:newfolder `:建立新資料夾以組織播放清單。 -- `:delfolder`:刪除目前選中的資料夾。 -- `:rename `:重新命名目前選中的播放清單或資料夾。 -- `:sort `:對播放清單庫進行排序。 -- `:sort `:完全在記憶體中對目前曲目清單排序。 -- `:seek `:跳轉到絕對位置或按相對偏移跳轉。 -- `:sleep <30m|1h|off>`:延遲後暫停播放(睡眠定時器)。 -- `:mute`:靜音播放或回復之前的音量。 -- `:open [spotify-url-or-uri]`:開啟 Spotify 曲目、專輯、藝術家或播放清單。不帶參數時從剪貼簿讀取。 -- `:relative `:設定曲目清單中 Vim 風格的相對行號。 -- `:redraw`:在終端機輸出異常後清除並完整重繪 TUI。 -- `:theme `:切換應用主題。 -- `:lang `:切換語言。 -- `:album`:跳轉到目前選中曲目所屬的專輯。 -- `:queue`:開啟佇列檢視。 -- `:clearqueue`:清空手動加入佇列的歌曲(僅在此裝置上播放時可用)。 -- `:vis`:切換音訊視覺化器。 -- `:visbins `:設定音訊視覺化器頻率條數量(5-32)。 -- `:pixelate `:在專輯封面上啟用復古 8 位元像素風格。設定為 0 可停用,或例如 16 以獲得像素化效果。 -- `:backdrop `:選擇桌面應用沉浸檢視背後的動態畫面(設定中也可選擇)。 -- `:tray [on|off]`:桌面應用程式的關閉按鈕是隱藏到系統匣還是直接結束(設定中也可選擇)。 -- `:thumbs [on|off]`:切換資料庫側欄中的封面縮圖。封面快取於 `~/.config/echo/thumbs/`,之後啟動時即時載入。 -- `:index `:設定曲目索引基數(從 1 開始或從 0 開始)。 -- `:quit`、`:q`、`:qa`、`:wq`:結束應用程式。 - -## 自訂快捷鍵 - -在 `~/.config/echo/config.toml` 的 `[library]` 下新增 `keybindings` 表,可以覆蓋或新增語義化映射。支援單鍵、修飾鍵組合(如 `ctrl-f`)以及兩鍵序列。未映射的按鍵保留 echo 的預設行為。 - -```toml -[library.keybindings] -"s d" = "sort_duration" -"s a" = "sort_artist" -"ctrl-j" = "half_page_down" -"ctrl-k" = "half_page_up" -";" = "seek_forward" -``` - -可用動作為 `first`、`last`、`page_up`、`page_down`、`half_page_up`、`half_page_down`、`current_context`、`play_pause`、`next`、`previous`、`shuffle`、`repeat`、`seek_backward`、`seek_forward`、`seek_start`、`mute`、`sort_original`、`sort_title`、`sort_artist`、`sort_album`、`sort_duration`、`sort_added`、`reverse_tracks`、`redraw` 和 `toggle_thumbnails`。 - -曲目排序與導覽僅作用於已載入的資料,不會向 Spotify 發送請求。導覽歷史最多保留 20 個記憶體中的檢視,因此返回之前的曲目清單通常無需重新取得。 - -## 主題 - -主題位於 `themes/*.toml`,採用扁平列表格式:九個基礎顏色之後是桌面應用程式使用的十二個衍生顏色,每一項都有明確的註解說明其用途。您可以自由修改數值,或修改基礎顏色後執行 `python themes/generate_desktop.py` 重新計算衍生顏色。衍生鍵是可選的——缺少的鍵會依其註解中命名的公式計算,同時也可接受 `[desktop]` 表用於覆蓋。若要進行視覺化迭代,可執行 `python tools/theme-preview/serve.py` 在瀏覽器中開啟桌面視窗的即時模擬,每次儲存都會重新著色——無需重新建置。顏色可以從任一方向編輯:在編輯器中修改 toml 檔案,或在預覽圖例中點擊任意顏色,用挑色器調整並直接寫回檔案(其中的「重新計算衍生色」按鈕會為目前主題重新執行產生器)。 - -## 音訊品質 - -echo 以 320 kbps 串流播放並套用音量標準化,與 Spotify 桌面應用程式的預設行為一致。這些選項位於 `~/.config/echo/config.toml` 的 `[library]` 下,並在下次啟動時生效。 - -```toml -[library] -bitrate = 320 # 96、160 或 320 -normalisation = true # 平衡曲目之間的響度,類似 Spotify 應用程式。 -normalisation_pregain = 3.0 # 標準化後加回的分貝數。若播放過小聲請調高。 -``` - -標準化會依每首曲目的 ReplayGain 值進行衰減,現代母帶通常有幾個分貝。`normalisation_pregain` 會把這部分餘裕加回來,使播放音量與 Spotify 應用程式相當。增益作用於 librespot 的動態限制器之前,因此調高不會產生削波。設定 `normalisation = false` 可完全略過增益環節,獲得位元精確的滿幅輸出,代價是曲目之間會出現響度跳變。 - -音量完全在使用者端套用——Spotify 串流以滿幅抵達,由 echo 自行衰減,因此在其他 Spotify 用戶端中裝置的音量滑桿不起作用。Spotify 和本地播放使用相同的三次方音量曲線,因此無論播放哪個來源,相同的百分比聽起來一樣,且兩者在 100% 時均為單位增益。 - -只要裝置支援,echo 就會以立體聲 44.1 kHz(librespot 的原始取樣率,因此無需重新取樣)開啟輸出裝置。不提供 44.1 kHz 的裝置(大多數 Windows 端點預設 48 kHz)會回退到裝置自身的預設取樣率。 - -實際開啟的端點會寫入工作目錄下的 `echo-debug-audio-spotify.log`,本地檔案則寫入 `echo-debug-audio-local.log`: - -``` -device=Headphones (WH-1000XM5) channels=2 sample_rate=48000 format=F32 -``` - -## 本地音樂 - -本地音樂支援與 Spotify 分開。使用 `:localpath ` 選擇 echo 應掃描的資料夾。支援的副檔名為 `mp3`、`wav`、`flac`、`ogg`、`m4a` 和 `aac`;echo 會遞迴掃描並讀取標題、藝術家、專輯、時長和封面圖(如有)。echo 在啟動時會重新整理已設定的本地資料夾,並在執行期間監視其中的音訊/封面圖變化;`:rescanlocal` 仍可作為手動回退方案使用。 - -本地播放清單儲存在本地,不是 Spotify 播放清單。它們可以包含本地曲目和 Spotify 曲目引用。Spotify 播放清單不能包含本地曲目。本地隨機播放、重複、音量、佇列和播放/暫停由 echo 的本地播放引擎處理。 - -嵌入式封面圖會被優先使用。如果曲目沒有嵌入式封面圖,echo 會查找資料夾中的封面圖,例如 `cover.jpg`、`folder.jpg` 或 `front.png`。 - -## 疑難排解 - -- **主題顏色渲染問題 (Windows)**:在「預設值」設定檔的「外觀」設定中,停用「調整難以分辨的文字」。 -- **影像無法渲染**:封面使用半格儲存格繪製,只需要終端機支援全彩——所有現代終端機都已具備。 -- **快取不同步**:其他裝置上喜歡的歌曲會在啟動時或開啟喜歡的歌曲時出現(最多每 15 分鐘檢查一次)。在其他裝置上取消喜歡的歌曲也會在同一時機被發現,並觸發背景重讀整個庫;被限流的重讀會從中斷處繼續。在 echo 關閉時刪除 `~/.config/echo/liked_songs.json` 可強制完整重讀。 -- **本地檔案遺失**:如果檔案在掃描後被刪除或移動,執行 `:rescanlocal` 以重新整理本地庫。 -- **音訊聽起來像單聲道或悶悶的(藍牙耳機)**:Windows 將藍牙耳機暴露為兩個輸出裝置——立體聲的「耳機」(A2DP)端點,以及限制在 16 kHz 的單聲道「免持」(HFP)端點。每當有應用程式開啟麥克風時,Windows 就會切換到免持模式。檢查 `echo-debug-audio-spotify.log`:如果報告 `channels=1`,請結束占用麥克風的程式,並將立體聲端點設為預設輸出裝置。 -- **設定檔路徑**:`~/.config/echo/config.toml`(儲存權杖和偏好設定)、`~/.config/echo/cache.json`(儲存庫快取和喜歡狀態)、`~/.config/echo/liked_songs.json`(儲存喜歡的歌曲列表)、`~/.config/echo/local_library.json` 和 `~/.config/echo/local_playlists.json`。 diff --git a/crates/echo-core/src/commands.rs b/crates/echo-core/src/commands.rs index 8fdfab7..d293246 100644 --- a/crates/echo-core/src/commands.rs +++ b/crates/echo-core/src/commands.rs @@ -30,7 +30,7 @@ pub const COMMANDS: &[(&str, &str)] = &[ ("vis", "Toggle the audio visualizer"), ("visbins <5-32>", "Visualizer frequency bands"), ("album", "Jump to the selected track's album"), - ("lang ", "Switch language"), + ("lang ", "Switch language"), ("newplaylist ", "Create a Spotify playlist"), ("newlocalplaylist ", "Create a local playlist"), ( @@ -104,12 +104,7 @@ fn generate_command_suggestions(state: &AppState) -> Vec { .collect() } "lang" => { - let options = vec![ - "en".to_string(), - "zh".to_string(), - "zh-CN".to_string(), - "zh-TW".to_string(), - ]; + let options = vec!["en".to_string(), "zh".to_string(), "zh-CN".to_string()]; options .into_iter() .filter(|o| o.starts_with(arg_str)) @@ -565,11 +560,7 @@ fn execute(state: &mut AppState, cmd: &str) -> Option { } "lang" => { if let Some(lang_code) = args.next() { - if lang_code == "en" - || lang_code == "zh" - || lang_code == "zh-CN" - || lang_code == "zh-TW" - { + if lang_code == "en" || lang_code == "zh" || lang_code == "zh-CN" { state.ui.library_config.language = lang_code.to_string(); state.save_library_config(); state.ui.status_message = Some( diff --git a/crates/echo-core/src/i18n.rs b/crates/echo-core/src/i18n.rs index 977e6f9..468b28d 100644 --- a/crates/echo-core/src/i18n.rs +++ b/crates/echo-core/src/i18n.rs @@ -10,13 +10,10 @@ pub fn init() { serde_json::from_str(include_str!("../../../locales/en.json")).unwrap(); let zh: serde_json::Value = serde_json::from_str(include_str!("../../../locales/zh-CN.json")).unwrap(); - let zh_tw: serde_json::Value = - serde_json::from_str(include_str!("../../../locales/zh-TW.json")).unwrap(); map.insert("en", en); map.insert("zh", zh.clone()); map.insert("zh-CN", zh); - map.insert("zh-TW", zh_tw); let _ = TRANSLATIONS.set(map); } @@ -56,7 +53,7 @@ mod tests { fn setup_uri_keys_present_in_all_locales() { init(); let map = TRANSLATIONS.get().unwrap(); - for lang in ["en", "zh-CN", "zh-TW"] { + for lang in ["en", "zh-CN"] { let setup = &map[lang]["desktop"]["setup"]; for key in ["step2", "copy", "copied"] { assert!(setup[key].is_string(), "{lang} missing desktop.setup.{key}"); @@ -68,7 +65,7 @@ mod tests { fn tray_keys_present_in_all_locales() { init(); let map = TRANSLATIONS.get().unwrap(); - for lang in ["en", "zh-CN", "zh-TW"] { + for lang in ["en", "zh-CN"] { let desktop = &map[lang]["desktop"]; for key in ["window", "tray", "tray_desc"] { assert!( diff --git a/crates/echo-desktop/src/views.rs b/crates/echo-desktop/src/views.rs index d18dcb8..7198d75 100644 --- a/crates/echo-desktop/src/views.rs +++ b/crates/echo-desktop/src/views.rs @@ -5387,7 +5387,6 @@ pub fn settings_modal( vec![ ("English".into(), "lang en".into(), language == "en"), ("简体".into(), "lang zh-CN".into(), language == "zh-CN"), - ("繁體".into(), "lang zh-TW".into(), language == "zh-TW"), ], cx, ), diff --git a/locales/zh-TW.json b/locales/zh-TW.json deleted file mode 100644 index 0a7105f..0000000 --- a/locales/zh-TW.json +++ /dev/null @@ -1,342 +0,0 @@ -{ - "ui": { - "library": "資料庫", - "discography_all": "全部", - "discography_albums": "專輯", - "discography_singles": "單曲與EP", - "discography_appears_on": "參與作品", - "search": "搜尋", - "queue": "播放佇列", - "queue_next_in": "佇列中的歌曲", - "queue_next_from": "接下來播放:{}", - "queue_next_up": "接下來播放", - "playlists": "播放清單", - "albums": "專輯", - "browse": "瀏覽", - "tracks": "歌曲", - "artist": "歌手", - "artists": "歌手", - "album": "專輯", - "source": "來源", - "local": "本機", - "spotify": "Spotify", - "duration": "時長", - "playing": " 正在播放 (裝置: {} | 隨機: {} | 循環: {} | 音量: {}%) ", - "on": "開", - "off": "關" - }, - "help": { - "press_help": "按 '?' 查看幫助", - "no_results": "未找到結果" - }, - "messages": { - "playlist_updated": "播放清單已更新", - "followed_playlist": "已將播放清單收藏至音樂庫", - "visual_block": "可視塊選擇模式", - "fetching": "載入中...", - "fetching_album": "正在載入專輯...", - "fetching_playlist": "正在載入播放清單...", - "saved_to_library": "已將 '{}' 儲存到資料庫", - "removed_from_library": "已將 '{}' 從資料庫中移除", - "created_playlist": "已建立播放清單 '{}'", - "deleted_playlists": "已刪除 {} 個播放清單", - "unfollowed_playlist": "已取消關注播放清單", - "renamed_playlist": "已重新命名為 '{}'", - "added_to_playlist": "已將 {} 首歌曲新增到 '{}'", - "removed_from_playlist": "已將 {} 首歌曲從 '{}' 中移除", - "added_to_queue": "已將 {} 首歌曲新增到播放佇列", - "queue_cleared": "已清空播放佇列", - "history_cleared": "已清除播放歷史", - "clear_queue_needs_device": "僅在此裝置上播放時才能清空佇列", - "added_to_liked": "已新增到喜歡的歌曲", - "reorder_requires_original": "排序模式下無法調整順序(:sort original)", - "sleep_timer_expired": "睡眠計時器 — 播放已暫停", - "unknown_theme": "未知主題: {}", - "unknown_language": "未知語言: {}", - "language_set": "語言已設定為 {}" - }, - "prompts": { - "search": "搜尋: ", - "create_playlist": "新播放清單名稱: ", - "rename_playlist": "重新命名播放清單: ", - "theme": "主題: ", - "lang": "語言: ", - "remove_from_liked": "是否從喜歡的歌曲中移除? (y/n)" - }, - "actions": { - "go_to_album": "前往專輯", - "go_to_artist": "前往歌手", - "add_to_playlist": "新增到播放清單", - "add_to_queue": "新增到播放佇列", - "clear_queue": "清空佇列", - "unlike_track": "取消喜歡", - "like_track": "喜歡這首歌", - "title": " 操作: {} " - }, - "desktop": { - "edit_details": "編輯詳細資料", - "name": "名稱", - "description": "描述", - "public": "公開", - "private": "私人", - "save": "儲存", - "cancel": "取消", - "more": "更多", - "copy_link": "複製連結", - "link_copied": "已複製 Spotify 連結", - "add_to_queue": "加入播放佇列", - "save_to_library": "收藏至音樂庫", - "remove_from_library": "從音樂庫移除", - "new_playlist": "新增播放清單", - "filter_playlists": "篩選播放清單", - "duplicate_one": "「{song}」已在「{playlist}」中", - "duplicate_many": "{n} 首歌曲已在「{playlist}」中", - "add_anyway": "仍然加入", - "skip_duplicates": "略過重複歌曲", - "home": "首頁", - "home_morning": "早安", "home_afternoon": "午安", "home_evening": "晚安", - "home_quick_picks": "快捷精選", "home_liked_songs": "喜歡的歌曲", "home_made_for_you": "為你打造", - "home_recently_played": "最近播放", "home_top_artists": "常聽藝人", "home_top_songs": "常聽歌曲", "home_new_releases": "最新發行", - "home_artist": "藝人", "home_loading": "載入中", "home_empty": "暫無內容", - "context": { - "playlist": "播放清單", "public": "公開清單", "private": "私人清單", "collaborative": "協作清單", - "album": "專輯", "single": "單曲", "compilation": "合輯", - "with": "與 {names} 共建", "and": " 和 ", "songs": "{n} 首歌曲,{total}", - "hours_minutes": "{a} 小時 {b} 分鐘", "minutes_seconds": "{a} 分 {b} 秒", - "unknown_date": "—", "date_format": "%Y年%-m月%-d日", - "seconds_ago": "{n} 秒前", "seconds_one_ago": "{n} 秒前", - "minutes_ago": "{n} 分鐘前", "minutes_one_ago": "{n} 分鐘前", - "hours_ago": "{n} 小時前", "hours_one_ago": "{n} 小時前", - "days_ago": "{n} 天前", "days_one_ago": "{n} 天前", - "weeks_ago": "{n} 週前", "weeks_one_ago": "{n} 週前", - "title": "標題", "album_column": "專輯", "added_by": "新增者", "date_added": "新增日期", "explicit": "E" - }, - "top_tracks": "熱門曲目", - "recently_played": "最近播放", - "recent": "最近", - "local_badge": "本機", - "popular": "熱門單曲", - "albums_section": "專輯", - "top_artists": "熱門藝人", - "whats_new": "最新發行", - "whats_new_checking": "正在檢查藝人 {done}/{total}…", - "whats_new_empty": "追蹤的藝人暫無新發行", - "range_short": "近4週", - "range_medium": "近6個月", - "range_long": "全部時間", - "follow": "追蹤", - "following": "追蹤中", - "search_placeholder": "想聽點什麼?", - "search_all": "全部", - "search_songs": "歌曲", - "search_top_result": "最佳結果", - "search_show_all": "顯示全部", - "search_artist_type": "藝人", - "search_album_type": "專輯 • {credit}", - "search_playlist_type": "播放清單 • {credit}", - "search_song_type": "歌曲 • {credit}", - "recent_searches": "最近搜尋", - "clear_recent_searches": "清除最近搜尋", - "search_no_results": "找不到「{query}」的搜尋結果", - "search_no_results_hint": "請檢查拼字,或嘗試減少關鍵字。", - "loading": "載入中…", - "loading_artist": "正在載入歌手…", - "loading_albums": "正在載入專輯…", - "loading_lyrics": "正在載入歌詞…", - "queue_upcoming": "{} 首待播", - "queue_empty": "播放佇列為空", - "recent_empty": "還沒有播放記錄", - "drag_songs": "{} 首歌曲", - "no_results_tab": "此分頁沒有結果", - "no_lyrics": "這首歌沒有歌詞", - "no_lyrics_found": "未找到歌詞。", - "lyrics_title": "歌詞 — {}", - "lyrics_panel": "歌詞", - "now_playing": "正在播放", - "playing_on": "正在 {} 上播放", - "close_panel": "關閉面板", - "theme": "主題", - "no_themes": "未找到主題", - "nothing_playing": "未在播放", - "playing_from": "正在播放:{}", - "playing_from_album": "專輯", - "playing_from_playlist": "播放清單", - "added_to_queue": "已新增到播放佇列", - "saved_next_launch": "已儲存 — 下次啟動生效", - "visual_hint": "q 加入佇列 · a 新增到播放清單 · dd 刪除 · esc 取消", - "devices_title": "連接到裝置", - "devices_none": "未找到裝置 — Spotify 是否已在某處開啟?", - "playlist_add_title": "新增到播放清單", - "playlist_add_none": "沒有你可以編輯的播放清單", - "sort": { - "original": "原始順序", - "title": "標題", - "added": "加入日期", - "reverse": "倒序" - }, - "help": { - "title": "按鍵與命令", - "esc_close": "esc 關閉", - "commands": "命令 — 按 :", - "nav": "導航", - "playback": "播放", - "library": "音樂庫", - "finding": "尋找", - "move": "向下 / 向上移動", - "first_last": "第一項 / 最後一項", - "page": "上翻頁 / 下翻頁", - "half_page": "上翻半頁 / 下翻半頁", - "open": "開啟或播放所選項", - "back": "返回,或關閉目前彈窗", - "history": "歷史後退 / 前進", - "to_sidebar": "返回側邊欄", - "toggle_sidebar": "顯示 / 隱藏側邊欄", - "tabs": "切換分頁", - "jump_playing": "跳到正在播放的歌曲", - "count": "將下一個動作重複 N 次", - "pane_focus": "聚焦側邊欄 / 曲目清單", - "confirm": "確認開啟的提示", - "like": "喜歡 / 取消喜歡該曲目", - "move_track": "下移 / 上移曲目(自己的播放清單)", - "search_command": "從命令列搜尋", - "play_pause": "播放 / 暫停", - "prev_next": "上一首 / 下一首", - "seek": "後退 / 前進 5 秒", - "seek_start": "跳到開頭", - "volume": "音量減 / 加", - "mute": "靜音", - "shuffle_repeat": "隨機 / 循環", - "devices": "裝置", - "lyrics": "歌詞", - "lyrics_bar": "在播放列顯示歌詞", - "immersive": "沉浸檢視", - "add_playlist": "新增到播放清單(或收藏專輯)", - "track_actions": "曲目操作", - "queue": "加入佇列 / 開啟佇列", - "delete": "刪除 — 按兩次後確認", - "select_range": "選擇範圍;shift-點擊 亦可", - "pin": "釘選 / 取消釘選", - "new_rename": "新增播放清單 / 重新命名", - "refresh": "重新整理", - "search": "搜尋 Spotify", - "filter": "過濾已載入的曲目清單", - "next_match": "下一個 / 上一個符合", - "command_bar": "命令列", - "themes": "主題", - "settings": "設定", - "this_help": "本說明", - "quit": "結束" - }, - "tray": { - "show": "顯示 echo" - }, - "settings": { - "title": "設定", - "subtitle": "這裡的所有設定也都是 : 命令", - "appearance": "外觀", - "library": "音樂庫", - "playback": "播放", - "audio": "音質", - "local": "本地音樂", - "language": "語言", - "visualizer": "音訊視覺化", - "visualizer_desc": "播放列中的頻譜條", - "bins": "視覺化頻段數", - "pixelate": "封面像素化", - "pixelate_desc": "復古 8 位元專輯封面;關閉為全解析度", - "backdrop": "沉浸背景", - "backdrop_desc": "全螢幕播放檢視的背景", - "backdrops": { - "lights": "光斑", - "mesh": "漸層網", - "aurora": "極光", - "vinyl": "黑膠", - "nebula": "星雲" - }, - "numbering": "曲目編號", - "numbering_desc": "首列為 1 或 0", - "relative": "相對行號", - "relative_desc": "Vim 風格,相對所選曲目的偏移", - "window": "視窗", - "tray": "關閉按鈕最小化到系統匣", - "tray_desc": "echo 繼續播放,可從系統匣圖示重新開啟", - "order": "清單排序", - "order_desc": "自訂即拖曳排列的順序", - "order_custom": "自訂", - "order_alpha": "按字母", - "order_creator": "按建立者", - "quality": "串流音質", - "quality_desc": "kbps — 下次啟動生效", - "normalisation": "音量均衡", - "normalisation_desc": "平衡曲目間的響度 — 下次啟動生效", - "pregain": "均衡預增益", - "pregain_desc": "均衡後補償的 dB — 播放偏小聲時可調高", - "folder_empty": "未設定資料夾 — 請輸入絕對路徑", - "folder_desc": "要掃描的資料夾 — 必須為絕對路徑。Enter 套用。", - "rescan": "立即重新掃描", - "updates": { - "auto": "自動更新", - "auto_desc": "在背景下載新版本;重新啟動後完成更新", - "auto_on": "自動更新已開啟", - "auto_off": "自動更新已關閉", - "auto_usage": "用法:autoupdate [on|off]", - "restart_now": "立即重新啟動", - "later": "稍後", - "restart_failed": "無法重新啟動 echo。請關閉後重新開啟。", - "section": "更新", - "version": "版本", - "dev": "(開發版本)", - "label": "軟體更新", - "desc": "將取代已安裝的應用程式 — 重新啟動後生效", - "check": "檢查更新", - "checking": "檢查中…", - "uptodate": "已是最新", - "install": "更新至 {}", - "downloading": "下載中 {}%", - "restart": "重新啟動以完成", - "installed": "已安裝版本 {} — 重新啟動 echo 後生效", - "retry": "重試", - "open_releases": "開啟發布頁面", - "failed": "更新檢查未完成" - } - }, - "setup": { - "title": "連接 Spotify", - "step1": "1. 在 Spotify 開發者面板中建立一個應用程式。", - "step2": "2. 將下方 Redirect URI 新增到應用程式:", - "copy": "複製", - "copied": "已複製", - "step3": "3. 在下方貼上應用程式的 Client ID 和 Secret(ctrl-v)。", - "dashboard": "開啟 Spotify 開發者面板 ↗", - "client_id": "Client ID", - "client_secret": "Client Secret", - "save": "儲存並連接", - "waiting": "等待 Spotify 憑證…", - "authenticating": "正在透過 Spotify 驗證……請在瀏覽器中完成登入" - }, - "menu": { - "open": "開啟", - "remove_library": "從音樂庫移除", - "rename": "重新命名", - "refresh": "重新整理", - "delete_folder": "刪除資料夾", - "pin": "釘選", - "unpin": "取消釘選", - "remove_folder": "移出資料夾", - "delete_playlist": "刪除播放清單", - "remove_playlist": "從播放清單移除" - }, - "prompt": { - "delete_folder": "刪除資料夾「{}」?其中的播放清單將回到音樂庫。", - "delete_playlist": "刪除「{}」?將從你的音樂庫中移除。", - "this_playlist": "此播放清單", - "remove_album": "從收藏專輯中移除「{}」?", - "this_album": "此專輯", - "remove_tracks": "從此播放清單中移除所選曲目?", - "remove_liked": "從喜歡的歌曲中移除這首歌?", - "delete": "刪除", - "remove": "移除", - "cancel": "取消" - } - } -}