904b9b3d-c0eb-42f3-acef-958.../Sources/support/Localization.swift

131 lines
3.8 KiB
Swift
Raw Permalink Normal View History

v2.0.0: full rewrite with multi-source NowPlaying Replaces the v1.0.0 shell (MediaRemote-only, non-functional on macOS 15.4+) with a layered design that handles four playback sources with sticky source priority routing: NowPlayingState (orchestrator, @MainActor, 3s poll + notifications) ├─ MediaRemote (private framework, dlopen) ├─ Spotify AppleScript (desktop) ├─ Apple Music AppleScript (desktop) └─ Chrome JS injection (YouTube / SoundCloud / web music) UI: - Large album art with color-extracted gradient background - Title / artist / album + source badge - Draggable seek bar with hover-grow affordance - Prev / Play·Pause (56pt lime button) / Next controls - Header slot: 20x20 icon + 3-bar pseudo-spectrum that pulses while playing - Bi-lingual (zh / en), follows host appLanguage Graceful degradation: - Host < v2.1.7 → upgrade banner (NSAppleEventsUsageDescription required) - QQ Music / NetEase / Kugou detected → "desktop unsupported, try web version" - Empty state with hint to play something in supported apps Build layout: Sources/ root (MioPlugin.swift contract + MusicPlugin principal) Sources/sources/ data sources Sources/ui/ SwiftUI views Sources/support/ ChineseAppDetector / HostVersionCheck / Localization build.sh now recursively finds .swift under Sources/. Breaking-ish: plugin id ("music-player") and bundle ID preserved. Users on v1.0.0 can upgrade in place via the plugin store. Requires: MioIsland host >= v2.1.7 for full functionality. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 18:27:21 +00:00
//
// Localization.swift
// MusicPlugin
//
// Minimal zh/en string map for the Now Playing plugin. Follows the
// same pattern as StatsPlugin: the host app's `appLanguage`
// UserDefault is the single source of truth, with an "auto" fallback
// to system locale.
//
import Foundation
enum L10n {
/// "zh" when the user has selected Chinese (explicitly or via system
/// locale fallback), "en" otherwise. Two discrete cases, no third.
static var language: String {
isChinese ? "zh" : "en"
}
static var isChinese: Bool {
let setting = UserDefaults.standard.string(forKey: "appLanguage") ?? "auto"
switch setting {
case "zh": return true
case "en": return false
default:
if let code = Locale.current.language.languageCode?.identifier,
code.hasPrefix("zh") {
return true
}
if let pref = Locale.preferredLanguages.first,
pref.hasPrefix("zh") {
return true
}
return false
}
}
// MARK: - Empty state
static var nothingPlaying: String {
isChinese ? "暂无播放" : "Nothing playing"
}
static var nothingPlayingHint: String {
isChinese
? "在 Spotify、Apple Music 或 Chrome 里播放音乐"
: "Play something in Spotify, Apple Music, or Chrome"
}
// MARK: - Host version too old
static var hostUpgradeTitle: String {
isChinese ? "需要 Mio Island v2.1.7+" : "Mio Island v2.1.7+ required"
}
static var hostUpgradeHint: String {
isChinese
? "请升级主 app 以启用完整功能"
: "Please upgrade Mio Island to unlock full plugin features"
}
// MARK: - Chinese app running detection
static func chineseAppTitle(_ appName: String) -> String {
isChinese ? "检测到 \(appName) 运行" : "\(appName) detected"
}
static var chineseAppHint: String {
isChinese
? "桌面端暂不支持曲目抓取,试试打开网页版"
: "Desktop version not supported. Try the web version in Chrome."
}
// MARK: - Small bits used around the card
/// Separator glyph placed between artist and album in compact layouts.
static var byArtist: String {
isChinese ? "" : "by"
}
/// Short label used near the playback source badge.
static var sourceLabel: String {
isChinese ? "来源" : "Source"
}
/// "Now Playing" heading for the expanded card.
static var nowPlayingHeading: String {
isChinese ? "正在播放" : "Now Playing"
}
/// Accessibility / tooltip labels for transport controls.
static var playTooltip: String {
isChinese ? "播放" : "Play"
}
static var pauseTooltip: String {
isChinese ? "暂停" : "Pause"
}
static var previousTooltip: String {
isChinese ? "上一首" : "Previous"
}
static var nextTooltip: String {
isChinese ? "下一首" : "Next"
}
/// Fallback strings shown when NowPlayingState has a blank field but
/// we still need to render something (e.g. while the first Chrome
/// query is in flight).
static var unknownTitle: String {
isChinese ? "未知曲目" : "Unknown Title"
}
static var unknownArtist: String {
isChinese ? "未知艺术家" : "Unknown Artist"
}
v2.2.1: real lyrics via LRCLIB + stream envelope fix + TimelineView vinyl Critical fix · adapter stream was silently empty v2.2.0 parsed the stream subprocess's JSON at the wrong layer. The `stream` mode wraps every emit as: {"type":"data","diff":<bool>,"payload":{title,...}} but Swift was decoding as AdapterStreamPayload directly (the shape used by `get`, which is flat). Result: every `stream rx` had title="" because the real data was nested inside payload, so hasTrack was always false and onUpdate never fired. Users saw "nothing playing" even while Apple Music was running. New AdapterStreamEnvelope decodes the wrapper, extracts payload, and also honours `diff: false` to reset currentInfo before merging (stale fields from the previous track were otherwise leaking). Added bootstrap path · cold start with music already playing When the adapter subprocess is spawned AFTER Apple Music is already playing, the stream's initial emit can be an empty baseline. A parallel one-shot `perl adapter.pl get` at spawn+300ms catches the current track immediately. Added file-based debug log at /tmp/mio-plugin-music-debug.log NSLog / os_log are unreliably filtered on macOS 15, and we can't attach Xcode to a plugin loaded from a signed host. A line-oriented log at a fixed path is the one channel that's always readable post- mortem. Lines include stream rx / bootstrap / parse failures. Real lyrics · LRCLIB integration - New LyricsService fetches synced lyrics via https://lrclib.net/api/get (exact) + /search (fallback), parses LRC format with regex [mm:ss.xx]. In-memory LRU cache (32 entries, 1-hour TTL). Negative-caches "not found" so obscure tracks don't re-hit the API every render. - NowPlayingState gains syncedLyrics + currentLyricIndex @Published. applyAdapterUpdate detects track changes and refreshes lyrics detached; the 1s playback timer updates currentLyricIndex. - DesktopLyricsViews replaces the placeholder text with real lyric text from syncedLyrics[currentLyricIndex ± 1]. Falls back to sensible dots when no lyrics loaded / instrumentals. Bonus · robust vinyl spin via TimelineView withAnimation(.linear.repeatForever) loses the animation when SwiftUI re-creates the view (window hide/show, style switch). Replaced with TimelineView driven by wall-clock — angle = (elapsed * 45°/s) % 360. Smooth across hours, no drift, pauses correctly via `paused: !isPlaying`. Borrowed approach from Atoll (github.com/Ebullioscopic/Atoll) MusicManager.swift:756-935: same LRCLIB endpoints, same LRC regex shape, same per-second sync model. Credited in LICENSE-THIRD-PARTY. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 23:55:04 +00:00
static var floatLyricsTooltip: String {
isChinese ? "悬浮歌词窗 · 点击切换显示" : "Floating lyrics window · toggle visibility"
}
static var lyricsPlaceholder: String {
isChinese ? "歌词暂未接入 · 等待真实数据源" : "Lyrics not wired yet — placeholder"
}
static var lyricsStyleLabel: String {
isChinese ? "样式" : "Style"
}
v2.0.0: full rewrite with multi-source NowPlaying Replaces the v1.0.0 shell (MediaRemote-only, non-functional on macOS 15.4+) with a layered design that handles four playback sources with sticky source priority routing: NowPlayingState (orchestrator, @MainActor, 3s poll + notifications) ├─ MediaRemote (private framework, dlopen) ├─ Spotify AppleScript (desktop) ├─ Apple Music AppleScript (desktop) └─ Chrome JS injection (YouTube / SoundCloud / web music) UI: - Large album art with color-extracted gradient background - Title / artist / album + source badge - Draggable seek bar with hover-grow affordance - Prev / Play·Pause (56pt lime button) / Next controls - Header slot: 20x20 icon + 3-bar pseudo-spectrum that pulses while playing - Bi-lingual (zh / en), follows host appLanguage Graceful degradation: - Host < v2.1.7 → upgrade banner (NSAppleEventsUsageDescription required) - QQ Music / NetEase / Kugou detected → "desktop unsupported, try web version" - Empty state with hint to play something in supported apps Build layout: Sources/ root (MioPlugin.swift contract + MusicPlugin principal) Sources/sources/ data sources Sources/ui/ SwiftUI views Sources/support/ ChineseAppDetector / HostVersionCheck / Localization build.sh now recursively finds .swift under Sources/. Breaking-ish: plugin id ("music-player") and bundle ID preserved. Users on v1.0.0 can upgrade in place via the plugin store. Requires: MioIsland host >= v2.1.7 for full functionality. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 18:27:21 +00:00
}