diff --git a/DynamicIsland.xcodeproj/project.pbxproj b/DynamicIsland.xcodeproj/project.pbxproj
index 3d4e01b..fd15f22 100644
--- a/DynamicIsland.xcodeproj/project.pbxproj
+++ b/DynamicIsland.xcodeproj/project.pbxproj
@@ -247,6 +247,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
+ tr,
Base,
hi,
ta,
diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift
index b2cbf86..164d2a0 100644
--- a/DynamicIsland/ContentView.swift
+++ b/DynamicIsland/ContentView.swift
@@ -54,6 +54,7 @@ struct ContentView: View {
@ObservedObject var extensionNotchExperienceManager = ExtensionNotchExperienceManager.shared
@ObservedObject var localSendService = LocalSendService.shared
@State private var downloadManager = DownloadManager.shared
+ @ObservedObject var shelfState = ShelfStateViewModel.shared
@Default(.enableStatsFeature) var enableStatsFeature
@Default(.showCpuGraph) var showCpuGraph
@@ -84,6 +85,15 @@ struct ContentView: View {
@Default(.hideNonNotchUntilHover) var hideNonNotchUntilHover
@Default(.terminalStickyMode) var terminalStickyMode
+ // Battery settings reactivity
+ @Default(.showPowerStatusNotifications) var showPowerStatusNotifications
+ @Default(.showChargingBatteryHUD) var showChargingBatteryHUD
+ @Default(.showLowBatteryHUD) var showLowBatteryHUD
+ @Default(.showFullBatteryHUD) var showFullBatteryHUD
+ @Default(.showOnAllDisplays) var showOnAllDisplays
+ @Default(.lowBatteryHUDStyle) var lowBatteryHUDStyle
+ @Default(.fullBatteryHUDStyle) var fullBatteryHUDStyle
+
// Dynamic sizing based on view type and graph count with smooth transitions
var dynamicNotchSize: CGSize {
let baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
@@ -100,6 +110,39 @@ struct ContentView: View {
return CGSize(width: max(baseSize.width, inlineWidth), height: baseSize.height)
}
+ // Handle battery HUD expansion sizing
+ if vm.notchState == .closed &&
+ coordinator.expandingView.show &&
+ coordinator.expandingView.type == .battery &&
+ isBatteryHUDVisibleOnCurrentScreen {
+
+ if let kind = batteryModel.activeTemporaryHUDKind {
+ let style: BatteryNotificationStyle = {
+ switch kind {
+ case .charging: return .compact
+ case .lowBattery: return Defaults[.lowBatteryHUDStyle]
+ case .fullBattery: return Defaults[.fullBatteryHUDStyle]
+ }
+ }()
+
+ var width = vm.closedNotchSize.width
+ var height = vm.effectiveClosedNotchHeight
+
+ switch (kind, style) {
+ case (.charging, _), (.lowBattery, .compact), (.fullBattery, .compact):
+ width += 180
+ case (.lowBattery, .standard):
+ width += 100
+ height += 75
+ case (.fullBattery, .standard):
+ width += 80
+ height += 70
+ }
+
+ return CGSize(width: width, height: height)
+ }
+ }
+
if coordinator.currentView == .timer {
return CGSize(width: baseSize.width, height: 250) // Extra height for timer presets
}
@@ -382,9 +425,78 @@ struct ContentView: View {
return DynamicIslandPillShape(cornerRadius: radius)
}
+ private var isBatteryHUDVisibleOnCurrentScreen: Bool {
+ guard coordinator.expandingView.show, coordinator.expandingView.type == .battery else { return false }
+ guard showPowerStatusNotifications else { return false }
+ guard batteryModel.activeTemporaryHUDKind != nil else { return false }
+ if showOnAllDisplays { return true }
+ guard let targetScreenName = batteryModel.activeTemporaryHUDTargetScreenName else { return true }
+ return currentScreenName == targetScreenName
+ }
+
+ private var isCurrentScreenExpansionVisible: Bool {
+ guard coordinator.expandingView.show else { return false }
+ if coordinator.expandingView.type == .battery {
+ return isBatteryHUDVisibleOnCurrentScreen
+ }
+ return true
+ }
+
+ private var currentScreenExpansionType: SneakContentType? {
+ isCurrentScreenExpansionVisible ? coordinator.expandingView.type : nil
+ }
+
+ private var displayedBatteryHUDLevel: Int {
+ let resolvedLevel = batteryModel.activeTemporaryHUDLevelOverride
+ ?? Int(batteryModel.levelBattery.rounded())
+ return min(max(resolvedLevel, 0), 100)
+ }
+
+ private var displayedBatteryHUDUsesLowPowerMode: Bool {
+ batteryModel.activeTemporaryHUDLowPowerModeOverride ?? batteryModel.isInLowPowerMode
+ }
+
+
+ private var activeClosedBatterySurfaceShape: AnyShape? {
+ guard vm.notchState == .closed else { return nil }
+ guard isBatteryHUDVisibleOnCurrentScreen else { return nil }
+ guard let kind = batteryModel.activeTemporaryHUDKind else { return nil }
+
+ if isDynamicIslandMode {
+ let radius = dynamicIslandPillCornerRadiusInsets.opened
+ return AnyShape(DynamicIslandPillShape(cornerRadius: radius))
+ } else {
+ let topRadius = activeCornerRadiusInsets.closed.top
+ let bottomRadius: CGFloat = {
+ switch resolvedBatteryNotificationStyle(for: kind) {
+ case .compact:
+ return activeCornerRadiusInsets.closed.bottom
+ case .standard:
+ return kind == .fullBattery ? 36 : 40
+ }
+ }()
+ return AnyShape(NotchShape(topCornerRadius: topRadius, bottomCornerRadius: bottomRadius))
+ }
+ }
+
+ private func resolvedBatteryNotificationStyle(for kind: BatteryTemporaryHUDKind) -> BatteryNotificationStyle {
+ switch kind {
+ case .charging:
+ return .compact
+ case .lowBattery:
+ return lowBatteryHUDStyle
+ case .fullBattery:
+ return fullBatteryHUDStyle
+ }
+ }
+
+
/// Resolves the clip/content shape per-screen: pill on non-notch screens
/// when dynamic island mode is active, standard notch shape otherwise.
private var resolvedClipShape: AnyShape {
+ if let activeClosedBatterySurfaceShape {
+ return activeClosedBatterySurfaceShape
+ }
if isDynamicIslandMode {
return AnyShape(currentPillShape)
}
@@ -763,47 +875,41 @@ struct ContentView: View {
guard let musicSecondary else { return false }
switch musicSecondary {
case .timer:
- return coordinator.expandingView.type == .timer
+ return currentScreenExpansionType == .timer
case .reminder:
- return coordinator.expandingView.type == .reminder
+ return currentScreenExpansionType == .reminder
case .recording:
- return coordinator.expandingView.type == .recording
+ return currentScreenExpansionType == .recording
case .focus:
- return coordinator.expandingView.type == .doNotDisturb
+ return currentScreenExpansionType == .doNotDisturb
case .capsLock:
return false
case .extensionPayload:
return false
+ case .shelf:
+ return false
}
}()
- let canShowMusicDuringExpansion = !coordinator.expandingView.show
- || coordinator.expandingView.type == .music
+ let canShowMusicDuringExpansion = !isCurrentScreenExpansionVisible
+ || currentScreenExpansionType == .music
|| expansionMatchesSecondary
- if coordinator.expandingView.type == .battery && coordinator.expandingView.show && vm.notchState == .closed && Defaults[.showPowerStatusNotifications] {
- HStack(spacing: 0) {
- HStack {
- Text(batteryModel.statusText)
- .font(.subheadline)
- }
-
- Rectangle()
- .fill(.black)
- .frame(width: vm.closedNotchSize.width + 10)
-
- HStack {
- DynamicIslandBatteryView(
- batteryWidth: 30,
- isCharging: batteryModel.isCharging,
- isInLowPowerMode: batteryModel.isInLowPowerMode,
- isPluggedIn: batteryModel.isPluggedIn,
- levelBattery: batteryModel.levelBattery,
- isForNotification: true
- )
- }
- .frame(width: 76, alignment: .trailing)
- }
- .frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0), alignment: .center)
+ if currentScreenExpansionType == .battery
+ && isBatteryHUDVisibleOnCurrentScreen
+ && vm.notchState == .closed
+ && Defaults[.showPowerStatusNotifications]
+ && batteryModel.activeTemporaryHUDKind != nil {
+ BatteryTemporaryActivityView(
+ kind: batteryModel.activeTemporaryHUDKind ?? .charging,
+ batteryLevel: displayedBatteryHUDLevel,
+ isLowPowerMode: displayedBatteryHUDUsesLowPowerMode,
+ closedNotchWidth: vm.closedNotchSize.width + (isHovering ? 8 : 0),
+ baseHeight: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0),
+ isDynamicIslandMode: isDynamicIslandMode,
+ topCornerRadius: activeCornerRadiusInsets.closed.top,
+ styleOverride: batteryModel.activeTemporaryHUDKind.map { resolvedBatteryNotificationStyle(for: $0) }
+ )
+ .id(batteryModel.activeTemporaryHUDToken)
} else if isSneakPeekVisibleOnCurrentScreen && Defaults[.inlineHUD] && (coordinator.sneakPeek.type != .music) && (coordinator.sneakPeek.type != .battery) && (coordinator.sneakPeek.type != .timer) && (coordinator.sneakPeek.type != .reminder) && !coordinator.sneakPeek.type.isExtensionPayload && ((coordinator.sneakPeek.type != .volume && coordinator.sneakPeek.type != .brightness && coordinator.sneakPeek.type != .backlight) || vm.notchState == .closed) {
InlineHUD(type: $coordinator.sneakPeek.type, value: $coordinator.sneakPeek.value, icon: $coordinator.sneakPeek.icon, hoverAnimation: $isHovering, gestureProgress: $gestureProgress)
.transition(
@@ -818,25 +924,25 @@ struct ContentView: View {
MusicLiveActivity(secondary: musicSecondary)
.id("closed-music-live-activity")
.transition(closedLiveActivitySwapTransition)
- } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .timer) && vm.notchState == .closed && timerManager.isTimerActive && coordinator.timerLiveActivityEnabled && !vm.hideOnClosed {
+ } else if (!isCurrentScreenExpansionVisible || currentScreenExpansionType == .timer) && vm.notchState == .closed && timerManager.isTimerActive && coordinator.timerLiveActivityEnabled && !vm.hideOnClosed {
TimerLiveActivity()
- } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .reminder) && vm.notchState == .closed && reminderManager.isActive && enableReminderLiveActivity && !vm.hideOnClosed {
+ } else if (!isCurrentScreenExpansionVisible || currentScreenExpansionType == .reminder) && vm.notchState == .closed && reminderManager.isActive && enableReminderLiveActivity && !vm.hideOnClosed {
ReminderLiveActivity()
- } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .recording) && vm.notchState == .closed && (recordingManager.isRecording || !recordingManager.isRecorderIdle) && Defaults[.enableScreenRecordingDetection] && !vm.hideOnClosed && !musicPairingEligible {
+ } else if (!isCurrentScreenExpansionVisible || currentScreenExpansionType == .recording) && vm.notchState == .closed && (recordingManager.isRecording || !recordingManager.isRecorderIdle) && Defaults[.enableScreenRecordingDetection] && !vm.hideOnClosed && !musicPairingEligible {
RecordingLiveActivity()
- } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .download) && vm.notchState == .closed && downloadManager.isDownloading && Defaults[.enableDownloadListener] && !vm.hideOnClosed {
+ } else if (!isCurrentScreenExpansionVisible || currentScreenExpansionType == .download) && vm.notchState == .closed && downloadManager.isDownloading && Defaults[.enableDownloadListener] && !vm.hideOnClosed {
DownloadLiveActivity()
.transition(.blurReplace.animation(.interactiveSpring(dampingFraction: 1.2)))
- } else if !coordinator.expandingView.show && vm.notchState == .closed && localSendLiveActivityActive && !vm.hideOnClosed {
+ } else if !isCurrentScreenExpansionVisible && vm.notchState == .closed && localSendLiveActivityActive && !vm.hideOnClosed {
LocalSendLiveActivity()
.transition(.blurReplace.animation(.interactiveSpring(dampingFraction: 1.2)))
- } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .doNotDisturb) && vm.notchState == .closed && Defaults[.enableDoNotDisturbDetection] && Defaults[.showDoNotDisturbIndicator] && (doNotDisturbManager.isDoNotDisturbActive || doNotDisturbManager.isFocusToastDismissing) && !vm.hideOnClosed && !lockScreenManager.isLocked {
+ } else if (!isCurrentScreenExpansionVisible || currentScreenExpansionType == .doNotDisturb) && vm.notchState == .closed && Defaults[.enableDoNotDisturbDetection] && Defaults[.showDoNotDisturbIndicator] && (doNotDisturbManager.isDoNotDisturbActive || doNotDisturbManager.isFocusToastDismissing) && !vm.hideOnClosed && !lockScreenManager.isLocked {
DoNotDisturbLiveActivity()
- } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .lockScreen) && vm.notchState == .closed && (lockScreenManager.isLocked || !lockScreenManager.isLockIdle) && Defaults[.enableLockScreenLiveActivity] && !vm.hideOnClosed {
+ } else if (!isCurrentScreenExpansionVisible || currentScreenExpansionType == .lockScreen) && vm.notchState == .closed && (lockScreenManager.isLocked || !lockScreenManager.isLockIdle) && Defaults[.enableLockScreenLiveActivity] && !vm.hideOnClosed {
LockScreenLiveActivity()
.id("lock-screen-live-activity")
.transition(closedLiveActivitySwapTransition)
- } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .privacy) && vm.notchState == .closed && privacyManager.hasAnyIndicator && (Defaults[.enableCameraDetection] || Defaults[.enableMicrophoneDetection]) && !vm.hideOnClosed {
+ } else if (!isCurrentScreenExpansionVisible || currentScreenExpansionType == .privacy) && vm.notchState == .closed && privacyManager.hasAnyIndicator && (Defaults[.enableCameraDetection] || Defaults[.enableMicrophoneDetection]) && !vm.hideOnClosed {
PrivacyLiveActivity()
} else if let extensionPayload = extensionStandalonePayload {
let layout = extensionStandaloneLayout(
@@ -849,7 +955,11 @@ struct ContentView: View {
layout: layout,
isHovering: isHovering
)
+ } else if !coordinator.expandingView.show && vm.notchState == .closed && !shelfState.isEmpty && !vm.hideOnClosed && !lockScreenManager.isLocked && !enableMinimalisticUI {
+ ShelfInlineLiveActivity()
+ .transition(.opacity.animation(.smooth(duration: 0.25)))
} else if !coordinator.expandingView.show && vm.notchState == .closed && (!musicManager.isPlaying && musicManager.isPlayerIdle) && Defaults[.showNotHumanFace] && !vm.hideOnClosed {
+ } else if !isCurrentScreenExpansionVisible && vm.notchState == .closed && (!musicManager.isPlaying && musicManager.isPlayerIdle) && Defaults[.showNotHumanFace] && !vm.hideOnClosed {
DynamicIslandFaceAnimation().animation(.interactiveSpring, value: musicManager.isPlayerIdle)
} else if vm.notchState == .open {
DynamicIslandHeader()
@@ -1200,6 +1310,11 @@ struct ContentView: View {
return .extensionPayload(extensionPayload)
}
+ // Shelf: show file count as lowest-priority secondary
+ if !shelfState.isEmpty && !lockScreenManager.isLocked && !enableMinimalisticUI {
+ return .shelf(count: shelfState.items.count)
+ }
+
return nil
}
@@ -1220,6 +1335,8 @@ struct ContentView: View {
case .extensionPayload(let payload):
let maxWidth = baseWidth + centerBaseWidth * 0.6
return ExtensionLayoutMetrics.trailingWidth(for: payload, baseWidth: baseWidth, maxWidth: maxWidth)
+ case .shelf:
+ return baseWidth
}
}
@@ -1322,6 +1439,10 @@ struct ContentView: View {
accent: payload.descriptor.accentColor.swiftUIColor,
size: badgeSize
)
+ case .shelf:
+ Image(systemName: "tray.and.arrow.down.fill")
+ .font(.system(size: badgeSize * 0.50, weight: .semibold))
+ .foregroundStyle(.white)
}
}
.frame(width: badgeSize, height: badgeSize)
@@ -1380,6 +1501,14 @@ struct ContentView: View {
spectrumView(forceSpectrum: true, trailingInset: 6)
case .extensionPayload(let payload):
ExtensionMusicWingView(payload: payload, notchHeight: notchHeight, trailingWidth: trailingWidth)
+ case .shelf(let count):
+ // File count badge: bold white number, like a minimal pill
+ Text("\(count)")
+ .font(.system(.callout, design: .rounded, weight: .bold))
+ .foregroundStyle(.white)
+ .contentTransition(.numericText(countsDown: false))
+ .animation(.smooth(duration: 0.3), value: count)
+ .frame(alignment: .center)
case .none:
spectrumView(
forceSpectrum: false,
@@ -1614,10 +1743,10 @@ struct ContentView: View {
return nil
}
- guard !coordinator.expandingView.show else {
+ guard !isCurrentScreenExpansionVisible else {
ExtensionRoutingDiagnostics.shared.logSuppression(
.standalone,
- reason: "expanding view \(coordinator.expandingView.type) visible",
+ reason: "expanding view \(String(describing: currentScreenExpansionType ?? coordinator.expandingView.type)) visible",
pendingCount: candidates.count
)
return nil
@@ -1737,7 +1866,7 @@ struct ContentView: View {
&& !vm.hideOnClosed
&& !lockScreenManager.isLocked
&& !isMusicHUDDeferredAfterUnlock
- && !coordinator.expandingView.show
+ && !isCurrentScreenExpansionVisible
&& (!musicManager.isPlayerIdle || musicManager.bundleIdentifier != nil)
&& !coordinator.firstLaunch
}
@@ -2482,6 +2611,7 @@ private enum MusicSecondaryLiveActivity: Equatable {
case focus(FocusModeType)
case capsLock(showLabel: Bool)
case extensionPayload(ExtensionLiveActivityPayload)
+ case shelf(count: Int)
var id: String {
switch self {
@@ -2497,6 +2627,8 @@ private enum MusicSecondaryLiveActivity: Equatable {
return showLabel ? "caps-lock-label" : "caps-lock-icon"
case .extensionPayload(let payload):
return "extension-\(payload.id)"
+ case .shelf(let count):
+ return "shelf-\(count)"
}
}
}
diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift
index 8650f79..25a5e95 100644
--- a/DynamicIsland/DynamicIslandApp.swift
+++ b/DynamicIsland/DynamicIslandApp.swift
@@ -395,6 +395,43 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let inlineSneakPeekWidth: CGFloat = 460
return CGSize(width: inlineSneakPeekWidth, height: vm.effectiveClosedNotchHeight)
}
+
+ // Check for battery HUD expansion
+ if vm.notchState == .closed &&
+ coordinator.expandingView.show &&
+ coordinator.expandingView.type == .battery &&
+ Defaults[.showPowerStatusNotifications] {
+
+ let batteryModel = BatteryStatusViewModel.shared
+ if let kind = batteryModel.activeTemporaryHUDKind {
+ let closedNotchHeight = vm.effectiveClosedNotchHeight
+ let closedNotchWidth = vm.closedNotchSize.width
+
+ let style: BatteryNotificationStyle = {
+ switch kind {
+ case .charging: return .compact
+ case .lowBattery: return Defaults[.lowBatteryHUDStyle]
+ case .fullBattery: return Defaults[.fullBatteryHUDStyle]
+ }
+ }()
+
+ var width = closedNotchWidth
+ var height = closedNotchHeight
+
+ switch (kind, style) {
+ case (.charging, _), (.lowBattery, .compact), (.fullBattery, .compact):
+ width += 180
+ case (.lowBattery, .standard):
+ width += 100
+ height += 75
+ case (.fullBattery, .standard):
+ width += 80
+ height += 70
+ }
+
+ return addShadowPadding(to: CGSize(width: width, height: height), isMinimalistic: Defaults[.enableMinimalisticUI])
+ }
+ }
// Use minimalistic or normal size based on settings
var baseSize = Defaults[.enableMinimalisticUI] ? minimalisticOpenNotchSize : openNotchSize
diff --git a/DynamicIsland/DynamicIslandViewCoordinator.swift b/DynamicIsland/DynamicIslandViewCoordinator.swift
index be70f58..c6ebb3a 100644
--- a/DynamicIsland/DynamicIslandViewCoordinator.swift
+++ b/DynamicIsland/DynamicIslandViewCoordinator.swift
@@ -97,6 +97,7 @@ struct ExpandedItem {
var type: SneakContentType = .battery
var value: CGFloat = 0
var browser: BrowserType = .chromium
+ var autoHideDuration: TimeInterval? = nil
}
class DynamicIslandViewCoordinator: ObservableObject {
@@ -413,7 +414,8 @@ class DynamicIslandViewCoordinator: ObservableObject {
status: Bool,
type: SneakContentType,
value: CGFloat = 0,
- browser: BrowserType = .chromium
+ browser: BrowserType = .chromium,
+ autoHideDuration: TimeInterval? = nil
) {
Task { @MainActor in
withAnimation(.smooth) {
@@ -421,6 +423,7 @@ class DynamicIslandViewCoordinator: ObservableObject {
self.expandingView.type = type
self.expandingView.value = value
self.expandingView.browser = browser
+ self.expandingView.autoHideDuration = autoHideDuration
}
}
}
@@ -433,7 +436,7 @@ class DynamicIslandViewCoordinator: ObservableObject {
expandingViewTask?.cancel()
// Only auto-hide for battery, not for downloads (DownloadManager handles that)
if expandingView.type != .download {
- let duration: TimeInterval = 3
+ let duration = expandingView.autoHideDuration ?? 3
expandingViewTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(duration))
guard let self = self, !Task.isCancelled else { return }
diff --git a/DynamicIsland/Info.plist b/DynamicIsland/Info.plist
index 92fe265..181d8a5 100644
--- a/DynamicIsland/Info.plist
+++ b/DynamicIsland/Info.plist
@@ -7,6 +7,8 @@
NSAllowsArbitraryLoads
+ NSAppleMusicUsageDescription
+ Atoll uses Apple Music to fetch animated artwork for the lock screen wallpaper.
NSAudioCaptureUsageDescription
Atoll needs audio capture permission to display the real-time music waveform visualizer.
NSScreenCaptureUsageDescription
diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings
index 00deefe..3f26472 100644
--- a/DynamicIsland/Localizable.xcstrings
+++ b/DynamicIsland/Localizable.xcstrings
@@ -83,7 +83,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : ""
}
},
@@ -183,8 +183,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : " – %lld"
+ "state" : "translated",
+ "value" : "– %lld"
}
},
"uk" : {
@@ -223,6 +223,12 @@
"value" : " %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -254,6 +260,12 @@
"value" : "-%@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "-%@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -290,6 +302,12 @@
"value" : "—"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "—"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -320,6 +338,12 @@
"value" : ":"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : ":"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -411,8 +435,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "'Now Playing' was the only option on previous versions and works with all media apps."
+ "state" : "translated",
+ "value" : "'Şimdi Oynatılıyor' önceki sürümlerdeki tek seçenekti ve tüm medya uygulamalarıyla çalışır."
}
},
"uk" : {
@@ -511,7 +535,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "(%@)"
}
},
@@ -612,8 +636,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "%.0f seconds"
+ "state" : "translated",
+ "value" : "%.0f saniye"
}
},
"uk" : {
@@ -713,8 +737,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "%.1fs"
+ "state" : "translated",
+ "value" : "%.1f'ler"
}
},
"uk" : {
@@ -814,7 +838,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "%@"
}
},
@@ -860,6 +884,12 @@
"value" : "%1$@ · %2$@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ · %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -895,6 +925,12 @@
"value" : "%@: Модели"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ Modelleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -925,6 +961,12 @@
"value" : "%@: Смещение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ Ofset"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -955,6 +997,12 @@
"value" : "%@ пт"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ nokta"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -985,6 +1033,12 @@
"value" : "%@:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1015,6 +1069,12 @@
"value" : "%1$@. Активный фокус: %2$@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@. Etkin odaklama: %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1045,6 +1105,12 @@
"value" : "%@°"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@°"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1061,6 +1127,12 @@
"value" : "%0.1f"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%0.1f"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1151,7 +1223,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "%d%%"
}
},
@@ -1177,6 +1249,12 @@
"value" : "%lld"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1202,6 +1280,12 @@
"value" : "%1$lld %2$@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1245,6 +1329,12 @@
"value" : "%lld анимаций"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld animasyonları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1275,6 +1365,12 @@
"value" : "%lld симв."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld karakterleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1310,6 +1406,12 @@
"value" : "%lld цв."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld renkleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1340,6 +1442,12 @@
"value" : "%1$lld дней%2$@ назад"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld gün%@ önce"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1370,6 +1478,12 @@
"value" : "%1$lld часов%2$@ назад"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld saat%@ önce"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1388,6 +1502,12 @@
"value" : "%lld мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld dk."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1419,6 +1539,12 @@
"value" : "%1$lld минут%2$@ назад"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld dakika%@ önce"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1443,6 +1569,12 @@
"value" : "%lld пт"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld nk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1461,6 +1593,12 @@
"value" : "%lld пикс."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld piksel"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1480,6 +1618,12 @@
"value" : "%lld с"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld s"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1511,6 +1655,12 @@
"value" : "%1$lld вкладок%2$@ включено · мин %3$lld пкс"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld sekmesi%@ etkin · minimum %lld piksel"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1601,7 +1751,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "%lld%%"
}
},
@@ -1636,6 +1786,12 @@
"value" : "%lldд. назад"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lldd önce"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1660,6 +1816,12 @@
"value" : "%lld ч"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lldh"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1684,6 +1846,12 @@
"value" : "%lldч. назад"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lldh önce"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1708,6 +1876,12 @@
"value" : "%lldм"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lldm"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1732,6 +1906,12 @@
"value" : "%lldм. назад"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lldm önce"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1739,6 +1919,9 @@
}
}
}
+ },
+ "%llds" : {
+
},
"%lldx" : {
"localizations" : {
@@ -1754,6 +1937,12 @@
"value" : "%lldм назад"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lldx"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1779,6 +1968,12 @@
"value" : "•"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "•"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -1871,8 +2066,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "• Bug fixes"
+ "state" : "translated",
+ "value" : "• Hata düzeltmeleri"
}
},
"uk" : {
@@ -1917,6 +2112,12 @@
"value" : "• Матовое стекло: Эффект полупрозрачного размытия"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "• Buzlu Cam: Yarı saydam bulanıklık efekti"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2007,8 +2208,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "• Improved performance"
+ "state" : "translated",
+ "value" : "• Geliştirilmiş performans"
}
},
"uk" : {
@@ -2053,6 +2254,12 @@
"value" : "• Liquid Glass: Современный эффект стекла (macOS 26+)"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "• Sıvı Cam: Modern cam efekti (macOS 26+)"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2143,8 +2350,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "• New feature 1"
+ "state" : "translated",
+ "value" : "• Yeni özellik 1"
}
},
"uk" : {
@@ -2189,6 +2396,12 @@
"value" : "• Сплошной Темный/Светлый/Авто: Непрозрачный фон"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "• Düz Koyu/Açık/Otomatik: Opak arka planlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2224,6 +2437,12 @@
"value" : "• Размышление"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "• Düşünme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2253,6 +2472,12 @@
"value" : "• Обновлено %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "• %@ güncellendi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2269,6 +2494,12 @@
"value" : "••••••••"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "••••••••"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2304,6 +2535,12 @@
"value" : "••••••••"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "+%lld daha"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2329,6 +2566,12 @@
"value" : "±10 Секунд"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "±10 Saniye"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2347,6 +2590,12 @@
"value" : "0"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "0"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2366,6 +2615,12 @@
"value" : "00"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "00"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2385,6 +2640,12 @@
"value" : "00:05:00"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "00:05:00"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2408,6 +2669,12 @@
"value" : "1 час"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1 saat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2430,6 +2697,12 @@
"value" : "1 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1 dakika"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2438,6 +2711,10 @@
}
}
},
+ "1. Open Spotify in a browser and log in" : {
+ "comment" : "A step in the process of getting the Spotify cookie.",
+ "isCommentAutoGenerated" : true
+ },
"1m" : {
"localizations" : {
"ko" : {
@@ -2452,6 +2729,12 @@
"value" : "1м"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1m"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2481,6 +2764,12 @@
"value" : "1 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1x"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2503,6 +2792,12 @@
"value" : "2 часов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "2 saat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2511,6 +2806,10 @@
}
}
},
+ "2. Developer Tools -> Application/Storage -> Cookies -> https://open.spotify.com" : {
+ "comment" : "A step to copy the Spotify cookie from the developer tools.",
+ "isCommentAutoGenerated" : true
+ },
"3 items" : {
"comment" : "merge:ko:alt='항목 3개'",
"localizations" : {
@@ -2538,6 +2837,12 @@
"value" : "2 часа"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "3 ürün"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2546,6 +2851,10 @@
}
}
},
+ "3. Copy the value of `sp_dc` and paste it here" : {
+ "comment" : "A step in the process of getting the Spotify cookie.",
+ "isCommentAutoGenerated" : true
+ },
"5 colors" : {
"comment" : "merge:ko:alt='5가지 색상'",
"localizations" : {
@@ -2573,6 +2882,12 @@
"value" : "5 цв."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "5 renk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2608,6 +2923,12 @@
"value" : "5 шт."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "5 ürün"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2630,6 +2951,12 @@
"value" : "5 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "5 dakika"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2652,6 +2979,12 @@
"value" : "5м"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "5m"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2687,6 +3020,12 @@
"value" : "5 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "7 ürün"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2722,6 +3061,12 @@
"value" : "10 цв."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "10 renk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2757,6 +3102,12 @@
"value" : "10 шт."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "10 ürün"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2779,6 +3130,12 @@
"value" : "10 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "10 dakika"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2814,6 +3171,12 @@
"value" : "10 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "15 renk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2836,6 +3199,12 @@
"value" : "15 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "15 dakika"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2858,6 +3227,12 @@
"value" : "15м"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "15m"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2881,6 +3256,12 @@
"value" : "15 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "20"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2917,6 +3298,12 @@
"value" : "20 цв."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "20 renk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2940,6 +3327,12 @@
"value" : "30"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "30"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2963,6 +3356,12 @@
"value" : "30 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "30 dakika"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -2985,6 +3384,12 @@
"value" : "45 мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "45 dakika"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3003,6 +3408,12 @@
"value" : "100"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "100"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3026,6 +3437,12 @@
"value" : "100"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "100%"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3153,6 +3570,12 @@
"value" : "Акцент"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aksan"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3243,8 +3666,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Accent color"
+ "state" : "translated",
+ "value" : "Vurgu rengi"
}
},
"uk" : {
@@ -3283,6 +3706,12 @@
"value" : "Цвет акцента"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vurgu rengi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3413,6 +3842,12 @@
"value" : "Универсальный доступ"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Erişilebilirlik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3447,6 +3882,12 @@
"value" : "Действия"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eylemler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3482,6 +3923,12 @@
"value" : "Активировать"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3517,6 +3964,12 @@
"value" : "Активируйте вашу лицензию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lisansınızı etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3552,6 +4005,12 @@
"value" : "Активация"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aktivasyon"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3592,6 +4051,12 @@
"value" : "Активно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aktif"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3626,6 +4091,12 @@
"value" : "Активно — без фокусирования"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aktif - Odaklanma Yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3662,6 +4133,12 @@
"value" : "Активно — без записи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkin - Kayıt Yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3686,6 +4163,12 @@
"value" : "Активный GPU"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aktif GPU"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3711,6 +4194,12 @@
"value" : "Адаптивный градиент"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uyarlanabilir degrade"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3847,6 +4336,12 @@
"value" : "Добавить анимацию по URL"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "URL'den Animasyon Ekle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3887,6 +4382,12 @@
"value" : "Добавить файлы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dosya ekle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3923,6 +4424,12 @@
"value" : "Добавить по URL..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "URL'den ekle..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -3945,6 +4452,12 @@
"value" : "Добавить иконку"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Simge ekle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4082,6 +4595,12 @@
"value" : "Добавить новый"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yeni Ekle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4213,6 +4732,12 @@
"value" : "Добавить пресет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ön Ayar Ekle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4336,6 +4861,12 @@
"value" : "Добавляет выпадающее окно терминала в стиле Guake. Сессия сохраняется."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Guake tarzı açılır terminal sekmesi ekler. Terminal oturumu çentik açma/kapama döngüleri boyunca devam eder."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4364,6 +4895,12 @@
"value" : "Включить выпадающий терминал в стиле Guake. Сессия терминала сохраняется при сворачивании/разворачивании выреза."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Caps Lock etkinleştirildiğinde isteğe bağlı etiket ve renk tonu kontrolleriyle çentikte bir HUD gösterir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4398,6 +4935,12 @@
"value" : "Добавляет кнопку AirPlay/выбора маршрута обратно в палитру."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AirPlay/rota seçici düğmesini özelleştirilebilir kontrol paletine geri ekler."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4434,6 +4977,12 @@
"value" : "Настройте параметры выше. OSD появится внизу по центру."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Değişiklikleri gerçek zamanlı olarak görmek için yukarıdaki ayarları ayarlayın. Gerçek OSD, ekranınızın alt ortasında görünür."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4450,6 +4999,12 @@
"value" : "버튼 크기에 영향을 주지 않고 잠금 화면 타이머 위젯 너비를 조절합니다."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düğme boyutunu etkilemeden kilit ekranı zamanlayıcı widget'ının genişliğini ayarlar."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4484,6 +5039,12 @@
"value" : "ИИ-помощник"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapay Zeka Asistanı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4518,6 +5079,12 @@
"value" : "ИИ думает..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapay zeka düşünüyor..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4552,6 +5119,12 @@
"value" : "Выбор ИИ-модели"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapay Zeka Modeli Seçimi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4586,6 +5159,12 @@
"value" : "Провайдер ИИ"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapay Zeka Sağlayıcı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4626,6 +5205,12 @@
"value" : "ИИ-помощник для анализа файлов, изображений и ведения диалога. Нажмите Cmd+Shift+A для быстрого доступа."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dosyaları, görüntüleri analiz edebilen ve konuşma yardımı sağlayabilen yapay zeka destekli asistan. Asistana hızlı bir şekilde erişmek için Cmd+Shift+A tuşlarını kullanın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4661,6 +5246,12 @@
"value" : "Индекс качества воздуха %1$d, %2$@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava kalitesi endeksi %d, %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4689,6 +5280,12 @@
"value" : "Для данных о качестве воздуха требуется провайдер Open Meteo."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava kalitesi Open Meteo sağlayıcısını gerektirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4717,6 +5314,12 @@
"value" : "Шкала качества воздуха"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava kalitesi ölçeği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4807,7 +5410,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "AirDrop"
}
},
@@ -4910,7 +5513,7 @@
"tr" : {
"stringUnit" : {
"state" : "needs_review",
- "value" : "AirDrop hizmetine kullanılamıyor"
+ "value" : "AirDrop hizmeti kullanılamıyor"
}
},
"uk" : {
@@ -4943,6 +5546,12 @@
"value" : "AirPlay"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AirPlay"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4965,6 +5574,12 @@
"value" : "Выравнивание"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hizalama"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -4993,6 +5608,12 @@
"value" : "Выравнивание"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hepsi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5123,6 +5744,12 @@
"value" : "События на весь день"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tüm Gün Etkinlikleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5247,6 +5874,12 @@
"value" : "Разрешить Live Activities расширений"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantı canlı etkinliklerine izin ver"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5271,6 +5904,12 @@
"value" : "Разрешить виджеты экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantı kilit ekranı widget'larına izin ver"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5295,6 +5934,12 @@
"value" : "Разрешить функции выреза расширений"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantı çentik deneyimlerine izin ver"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5319,6 +5964,12 @@
"value" : "Разрешить интерактивный веб-контент"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkileşimli web içeriğine izin ver"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5343,6 +5994,12 @@
"value" : "Разрешить упрощенный интерфейс"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Minimalist geçersiz kılmalara izin ver"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5365,6 +6022,12 @@
"value" : "Разрешить отслеживание мыши"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fare raporlamasına izin ver"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5395,6 +6058,12 @@
"value" : "Разрешить отправку сообщений о действиях мыши"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzin Verilen Özellikler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5413,6 +6082,12 @@
"value" : "АЛЬФА"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "ALFA"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5623,6 +6298,17 @@
}
}
},
+ "Amazon Music" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Amazon Music"
+ }
+ }
+ }
+ },
"Animation" : {
"extractionState" : "stale",
"localizations" : {
@@ -5650,6 +6336,12 @@
"value" : "Анимация"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyon"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5686,6 +6378,12 @@
"value" : "Название анимации"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyon Adı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5720,6 +6418,12 @@
"value" : "Настройка API"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "API Yapılandırması"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5754,6 +6458,12 @@
"value" : "Требуется ключ API"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "API Anahtarı Gerekli"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5776,6 +6486,12 @@
"value" : "Приложение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uygulama"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -5906,6 +6622,12 @@
"value" : "Разрешения приложений"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uygulama İzinleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6031,6 +6753,12 @@
"value" : "Apple Music"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Apple Music"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6060,6 +6788,12 @@
"value" : "Apple Music"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı medya widget'ının albüm kapağına çentik stili paralaks efektini uygular."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6090,6 +6824,12 @@
"value" : "Приложения с AtollExtensionKit появятся здесь после запроса разрешения."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AtollExtensionKit kullanan uygulamalar izin istediklerinde burada görünür"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6113,6 +6853,12 @@
"value" : "AQI"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AQI"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6154,6 +6900,12 @@
"value" : "AQI"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "\"%@\"yi silmek istediğinizden emin misiniz?"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6188,6 +6940,12 @@
"value" : "Удалить этот цвет из истории?"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu rengi geçmişinizden silmek istediğinizden emin misiniz?"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6222,6 +6980,12 @@
"value" : "Спросите меня о чем-либо..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bana bir şey sor..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6244,6 +7008,12 @@
"value" : "Atoll"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6269,6 +7039,12 @@
"value" : "Atoll отслеживает яркость от выбранного провайдера, а громкость — только при внешнем контроле."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll, seçili sağlayıcının parlaklık olaylarını her zaman dinler; sağlayıcı ses düzeyi olaylarını ise yalnızca harici ses düzeyi dinleyicisi etkinken dinler."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6297,6 +7073,12 @@
"value" : "Для отображения расписания нужен доступ к календарю."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll, yaklaşan tüm etkinliklerinizi tek bir yerde gösterebilir. Programınızı görüntülemek için takviminize erişim gerekir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6325,6 +7107,12 @@
"value" : "Разрешение к камере нужно для включения функции зеркала в вырезе."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll, kameranızı kullanarak görünümünüzü doğrudan çentikten hızlıca kontrol etmenizi sağlayan bir ayna özelliği içerir. Kamera erişimi yalnızca bu canlı önizlemeyi göstermek için gereklidir. Ayna özelliğini uygulamada istediğiniz zaman açıp kapatabilirsiniz."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6349,6 +7137,12 @@
"value" : "Внешние данные громкости игнорируются, пока включен системный перехват."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll yerel ses tuşu yakalamayı korur. Bu kapalıyken harici sağlayıcı ses düzeyi verileri yok sayılır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6378,6 +7172,12 @@
"value" : "Для чтения уведомлений нужен полный доступ к диску. Добавьте Atoll в настройках вручную, если его нет."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll'un sistem bildirim veritabanını okuyabilmesi için Tam Disk Erişimi gerekir. Atoll listede görünmüyorsa 'Finder'da Uygulamayı Göster' seçeneğini kullanın ve uygulamayı ayarlar penceresine sürükleyin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6386,6 +7186,10 @@
}
}
},
+ "Atoll uses the local `sp_dc` cookie only to request Spotify's internal web-player token and fetch the matching Canvas for the current track." : {
+ "comment" : "A description of how the Spotify cookie is used.",
+ "isCommentAutoGenerated" : true
+ },
"Atoll's built-in volume key interception is disabled while external volume listening is on. Volume HUD/OSD will follow %@ payloads." : {
"comment" : "A description below the toggle that explains the effect of enabling or disabling the feature.",
"isCommentAutoGenerated" : true,
@@ -6402,6 +7206,12 @@
"value" : "Встроенный перехват отключен. HUD/OSD следует данным от %@."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Harici ses dinleme açıkken Atoll'un yerleşik ses tuşu yakalaması devre dışı bırakılır. Ses HUD/OSD gösterimi %@ verilerini izler."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6436,6 +7246,12 @@
"value" : "Вложенные файлы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekli Dosyalar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6466,6 +7282,12 @@
"value" : "Звуковой отклик"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sesli geri bildirim"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6496,6 +7318,12 @@
"value" : "Авторизовать"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yetkilendir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6526,6 +7354,12 @@
"value" : "Авторизовано"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yetkili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6548,6 +7382,12 @@
"value" : "Скрывать неактивный плеер в вырезе"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkin olmayan çentikli medya oynatıcısını otomatik gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6578,6 +7418,12 @@
"value" : "Переходить к следующему событию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sonraki etkinliğe otomatik kaydırma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6800,6 +7646,12 @@
"value" : "Автоматически переключать дисплеи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekranları otomatik olarak değiştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6822,6 +7674,12 @@
"value" : "Среднее"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ortalama"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6858,6 +7716,12 @@
"value" : "Средние"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ortalamalar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6880,6 +7744,12 @@
"value" : "Фон"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Arkaplan"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6910,6 +7780,12 @@
"value" : "Фон"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Arka ışık"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -6935,6 +7811,12 @@
"value" : "Полоса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7064,6 +7946,12 @@
"value" : "Заряд батареи: %d%%"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil yüzde %d seviyesinde"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7072,6 +7960,9 @@
}
}
},
+ "Battery at %d percent, charging on hold" : {
+ "comment" : "Plugged in but charging is paused by the system"
+ },
"Battery charging" : {
"comment" : "Battery charging without estimate",
"localizations" : {
@@ -7093,6 +7984,12 @@
"value" : "Батарея заряжается"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil şarjı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7122,6 +8019,12 @@
"value" : "Зарядка батареи, осталось %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil şarj oluyor, kalan %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7151,6 +8054,12 @@
"value" : "Батарея полностью заряжена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil tamamen şarj edildi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7158,6 +8067,9 @@
}
}
}
+ },
+ "Battery HUDs" : {
+
},
"Battery Indicator Styling" : {
"comment" : "A section header in the Settings app that lists options for customizing the appearance of the battery indicator.",
@@ -7187,6 +8099,12 @@
"value" : "Стиль индикатора батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil Göstergesi Şekillendirme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7415,6 +8333,12 @@
"value" : "Настройки батареи доступны только на MacBook"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil ayarları ve bilgileri yalnızca MacBook'lerde mevcuttur"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7543,6 +8467,12 @@
"value" : "Виджет батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil Widget'ı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7571,6 +8501,12 @@
"value" : "Поведение и стиль"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Davranış ve Stil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7589,6 +8525,12 @@
"value" : "BETA"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "BETA"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7625,6 +8567,12 @@
"value" : "Экранные индикаторы BetterDisplay будут отображаться в активном стиле панели Atoll. Убедитесь, что интеграция индикатора включена в BetterDisplay: Настройки › Приложение › Интеграция."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mavi kesikli kutu Atoll içindeki gerçek boyutu gösterir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7655,6 +8603,12 @@
"value" : "Синий контур ограничивает размер выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mavi çerçeve çentikli tuvalle eşleşiyor"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7691,6 +8645,12 @@
"value" : "Аудиоустройства Bluetooth"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bluetooth Ses Cihazları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7726,6 +8686,12 @@
"value" : "Bluetooth-устройство %1$@ на %2$d %"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bluetooth cihazı %@ yüzde %d seviyesinde"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7748,6 +8714,12 @@
"value" : "Bold text as bright цв."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parlak renkler olarak kalın metin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7889,6 +8861,12 @@
"value" : "Оба"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Her ikisi de"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7897,6 +8875,23 @@
}
}
},
+ "Both Active" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Both Active"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İkisi de etkin"
+ }
+ }
+ }
+ },
"Break" : {
"comment" : "Name of a default timer preset that lasts for 5 minutes.",
"isCommentAutoGenerated" : true,
@@ -7913,6 +8908,12 @@
"value" : "Перерыв"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mola"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7955,6 +8956,12 @@
"value" : "Перерыв"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Arıza"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7985,6 +8992,12 @@
"value" : "Пауза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parlaklık"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -7992,6 +9005,9 @@
}
}
}
+ },
+ "Brightness fine step" : {
+
},
"Brightness HUD" : {
"localizations" : {
@@ -8019,6 +9035,12 @@
"value" : "HUD яркости"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parlaklık HUD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8049,6 +9071,12 @@
"value" : "OSD яркости"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parlaklık OSD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8056,6 +9084,9 @@
}
}
}
+ },
+ "Brightness step" : {
+
},
"Built-in" : {
"comment" : "A label indicating that an animation is a built-in one.",
@@ -8085,6 +9116,12 @@
"value" : "Встроенный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yerleşik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8114,6 +9151,12 @@
"value" : "Встроенный системный мониторинг"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dahili Sistem İzleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8149,6 +9192,12 @@
"value" : "Встроенный мониторинг отслеживает громкость и яркость напрямую."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yerleşik sistem izleme, harici uygulamalar gerektirmeden ses düzeyi, parlaklık ve klavye arka aydınlatması değişikliklerini doğrudan algılar."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8183,6 +9232,12 @@
"value" : "Купить нам кофе"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bize bir kahve ısmarla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8205,6 +9260,12 @@
"value" : "В кэше"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Önbelleğe alındı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8421,6 +9482,12 @@
"state" : "translated",
"value" : "캘린더 앱"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim Uygulaması"
+ }
}
}
},
@@ -8452,6 +9519,12 @@
"value" : "Доступ к календарю или напоминаниям запрещен. Включите его в настройках."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim veya Anımsatıcı erişimi reddedildi. Lütfen Sistem Ayarları'nda etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8474,6 +9547,12 @@
"value" : "Виджет календаря"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim Widget'ı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8514,6 +9593,12 @@
"value" : "Виджет календаря"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvimler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8556,6 +9641,12 @@
"value" : "Камера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kamera"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8596,6 +9687,12 @@
"value" : "Камера активна"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kamera Aktif"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8604,6 +9701,23 @@
}
}
},
+ "Camera Only" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Camera Only"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yalnızca kamera"
+ }
+ }
+ }
+ },
"Camera Status" : {
"localizations" : {
"de" : {
@@ -8636,6 +9750,12 @@
"value" : "Состояние камеры"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kamera Durumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8752,6 +9872,12 @@
"value" : "Caps Lock"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Büyük Harf Kilidi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8781,6 +9907,12 @@
"value" : "Caps Lock"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Büyük Harf Kilidi rengi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8809,6 +9941,12 @@
"value" : "Индикатор Caps Lock"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Büyük Harf Kilidi Göstergesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8850,6 +9988,12 @@
"value" : "По центру"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Merkez"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8884,6 +10028,12 @@
"value" : "Изменить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Değiştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -8900,6 +10050,12 @@
"value" : "미디어 출력 변경"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya Çıkışını Değiştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9025,6 +10181,12 @@
"value" : "Зарядка батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pilin şarj edilmesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9033,6 +10195,15 @@
}
}
},
+ "Charging duration" : {
+
+ },
+ "Charging HUD" : {
+
+ },
+ "Charging on hold" : {
+ "comment" : "Plugged in but charging is paused by the system"
+ },
"Charging on Hold: Desktop Mode" : {
"localizations" : {
"ar" : {
@@ -9255,6 +10426,12 @@
"value" : "Цвет плашки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çip rengi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9296,6 +10473,12 @@
"value" : "Выберите звуковой файл для окончания таймера. Поддерживаются форматы: MP3, M4A, WAV, AIFF."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı tamamlandığında çalacak özel bir ses dosyası seçin. Desteklenen formatlar: MP3, M4A, WAV, AIFF."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9430,6 +10613,12 @@
"value" : "Выбрать модель ИИ"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapay zeka modelini seçin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9478,6 +10667,12 @@
"value" : "Выберите анимацию для режима ожидания. Нажмите для выбора, удерживайте для удаления."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll boştayken gösterilecek animasyonu seçin. Seçmek için dokunun, özel animasyonları silmek için basılı tutun."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9506,6 +10701,12 @@
"value" : "Выберите сторону экрана для вертикальной панели."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dikey çubuğun ekranın hangi tarafında görüneceğini doğrudan seçin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9540,6 +10741,12 @@
"value" : "Выбрать файл"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dosya Seç"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9575,6 +10782,12 @@
"value" : "Выберите режим материала для виджетов экрана блокировки."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı widget'ları için genel malzeme modunu seçin. Özel Sıvı, widget başına değişken kaydırıcıların kilidini açarken Standart, klasik buzlu/sıvı seçeneklerine sadık kalır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9605,6 +10818,12 @@
"value" : "Выберите сервис для файлов с полки. Перетащите файлы на полку или нажмите кнопку."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Raftan dosya paylaşırken hangi hizmetin kullanılacağını seçin. Dosyaları rafa sürükleyin veya dosyaları seçmek için raf düğmesini tıklayın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9641,6 +10860,12 @@
"value" : "Выберите элементы управления для показа всплывающих окон OSD."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hangi sistem denetimlerinin özel OSD pencerelerini göstermesi gerektiğini seçin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9671,6 +10896,12 @@
"value" : "Выберите элементы управления для показа уведомлений HUD."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hangi sistem kontrollerinin HUD bildirimlerini görüntülemesi gerektiğini seçin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9705,6 +10936,12 @@
"value" : "Выберите предпочитаемую модель ИИ и её параметры"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tercih ettiğiniz yapay zeka modelini ve yapılandırmasını seçin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9741,6 +10978,12 @@
"value" : "Выберите свой профиль"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Profilinizi Seçin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9831,8 +11074,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Circle"
+ "state" : "translated",
+ "value" : "daire"
}
},
"uk" : {
@@ -9865,6 +11108,12 @@
"value" : "Круглый"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dairesel"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9889,6 +11138,12 @@
"value" : "Классический"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Klasik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9919,6 +11174,12 @@
"value" : "Классический"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Klasik mod orijinal yarı saydam siyah arka planı korur."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9943,6 +11204,12 @@
"value" : "Классическая форма выреза, сливающаяся с верхним краем экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Üst ekranın kenarına uyum sağlayan klasik çentik şekli"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -9952,7 +11219,6 @@
}
},
"Clear" : {
- "extractionState" : "stale",
"localizations" : {
"fr" : {
"stringUnit" : {
@@ -9978,6 +11244,12 @@
"value" : "Классическая форма выреза, плавно переходящая в верхний край экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10012,6 +11284,12 @@
"value" : "Очистить все файлы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tüm Dosyaları Temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10052,6 +11330,12 @@
"value" : "Очистка удалит все прикрепленные файлы и аудиозаписи. Это действие необратимо."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tüm dosyaları temizle, ekli tüm dosyaları ve ses kayıtlarını kaldırır. Bu eylem kalıcıdır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10086,6 +11370,12 @@
"value" : "Очистить историю буфера обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano Geçmişini Temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10126,6 +11416,12 @@
"value" : "Очистка истории или закрепленных элементов необратима."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano geçmişini temizle, son kopyaları kaldırır. Sabitlenmiş öğeleri temizle favorilerinizi kaldırır. Her iki eylem de kalıcıdır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10156,6 +11452,12 @@
"value" : "Очистить историю буфера обмена?"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano Geçmişi Temizlensin mi?"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10190,6 +11492,12 @@
"value" : "Очистить историю цветов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Geçmişini Temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10230,6 +11538,12 @@
"value" : "Очистка удаляет все цвета. Запуск выбора включает режим захвата цвета."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk geçmişini temizle, seçilen tüm renkleri kaldırır. Renk seçmeyi başlat ekran rengi yakalama modunu başlatır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10254,6 +11568,12 @@
"value" : "Очистить диалог и вложения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Konuşmayı ve ekleri temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10288,6 +11608,12 @@
"value" : "Очистить данные"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Verileri Temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10318,6 +11644,12 @@
"value" : "Очистить историю"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geçmişi Temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10352,6 +11684,12 @@
"value" : "Удалить закрепленные элементы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sabitlenmiş Öğeleri Temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10382,6 +11720,12 @@
"value" : "Очистить поиск"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aramayı temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10412,6 +11756,12 @@
"value" : "Очистить слот"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yuvayı temizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10446,6 +11796,12 @@
"value" : "Click 'Pick Color' or use Cmd+Shift+P to start picking цв."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renkleri seçmeye başlamak için 'Renk Seç'i tıklayın veya Cmd+Shift+P'yi kullanın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10482,6 +11838,12 @@
"value" : "Нажмите для подробностей"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayrıntılar için tıklayın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10516,6 +11878,12 @@
"value" : "Буфер обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10550,6 +11918,12 @@
"value" : "Функция буфера обмена отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano özelliği devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10584,6 +11958,12 @@
"value" : "История буфера обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano Geçmişi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10618,6 +11998,12 @@
"value" : "История буфера обмена:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano Geçmişi:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10652,6 +12038,12 @@
"value" : "Менеджер буфера обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano Yöneticisi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10742,8 +12134,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Close"
+ "state" : "translated",
+ "value" : "Kapat"
}
},
"uk" : {
@@ -10786,6 +12178,12 @@
"value" : "Закрыть помощника"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Asistanı kapat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10808,6 +12206,12 @@
"value" : "Жест закрытия"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hareketi kapat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10832,6 +12236,12 @@
"value" : "Программируйте и отлаживайте с пипеткой цветов, мониторингом системы и экранным помощником."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk seçici, istatistik izleme ve ekran asistanıyla kodlayın ve hata ayıklayın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10862,6 +12272,12 @@
"value" : "Разработка и отладка: цветовая пипетка, мониторинг статистики и экранный помощник."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı veya katman sorunlarını bildirirken geliştiriciyle paylaşmak üzere en son kilitlenme raporunu toplayın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10896,6 +12312,12 @@
"value" : "Детали цвета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Detayları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10930,6 +12352,12 @@
"value" : "Форматы цвета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Formatları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -10964,6 +12392,12 @@
"value" : "История цветов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Geçmişi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11000,6 +12434,12 @@
"value" : "Настройки цвета влияют на иконку и индикатор. Автоматически адаптируется под тему."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk seçenekleri simgenin ve ilerleme çubuğunun görünümünü kontrol eder. Otomatik olarak sistem temasına uyum sağlar."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11034,6 +12474,12 @@
"value" : "Цвет выбран!"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçildi!"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11068,6 +12514,12 @@
"value" : "Пипетка цветов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçici"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11102,6 +12554,12 @@
"value" : "Функция пипетки отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçici özelliği devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11136,6 +12594,12 @@
"value" : "Панель пипетки:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçici Paneli:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11158,6 +12622,12 @@
"value" : "Цветное отображение батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk kodlu pil ekranı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11188,6 +12658,12 @@
"value" : "Цветная заливка и градиенты недоступны в режиме Segmented."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk kodlu dolgular ve pürüzsüz gradyanlar Bölümlenmiş modda kullanılamaz. Bu seçenekleri ayarlamak için Hiyerarşik veya Gradyan'a geçin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11218,6 +12694,12 @@
"value" : "Цветная заливка недоступна в режиме Segmented."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk kodlu dolgular Bölümlere ayrılmış modda kullanılamaz. Gelişmiş seçenekleri ayarlamak için Hiyerarşik veya Gradyan İç Kontrollere geçin › Dynamic Island."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11240,6 +12722,12 @@
"value" : "Цветная громкость"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Kodlu Hacim"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11262,6 +12750,12 @@
"value" : "Цветное отображение громкости"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk kodlu ses seviyesi ekranı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11296,6 +12790,12 @@
"value" : "Пипетка отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçici Devre Dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11318,6 +12818,12 @@
"value" : "Цвета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renkler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11426,6 +12932,12 @@
}
}
}
+ },
+ "Compact keeps the alert inline. Standard uses the taller full-charge HUD with the charging animation." : {
+
+ },
+ "Compact matches the charging HUD. Standard uses the expanded DynamicNotch-style card." : {
+
},
"Compressed" : {
"localizations" : {
@@ -11441,6 +12953,12 @@
"value" : "Сжато"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sıkıştırılmış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11463,6 +12981,12 @@
"value" : "Вычисления"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hesapla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11497,6 +13021,12 @@
"value" : "Конфигурация"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapılandırma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11527,6 +13057,12 @@
"value" : "Настройте стиль обратного отсчета и виджеты на вкладке Calendar."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim sekmesinde geri sayım stilini ve kilit ekranı widget'larını yapılandırın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11555,6 +13091,12 @@
"value" : "Настройте внешний вид таймера в закрытом вырезе."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcının kapalı çentik içinde nasıl görüneceğini yapılandırın. İlerleme, simgenin etrafında bir halka veya yatay çubuklar şeklinde oluşturulabilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11590,6 +13132,12 @@
"value" : "Настройте материалы экрана блокировки на вкладке Appearance."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Görünüm sekmesinden kilit ekranı malzemelerini yapılandırın. Custom Liquid, Liquid Glass seçildiğinde her iki widget için de değişken kaydırıcıların kilidini açar."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11620,6 +13168,12 @@
"value" : "Настроить пресеты в Настройках"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayarlar'da ön ayarları yapılandırın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11650,6 +13204,12 @@
"value" : "Настройте пресеты в Настройках, чтобы увидеть их здесь."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ön ayarları burada görmek için Ayarlar'da yapılandırın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11658,6 +13218,10 @@
}
}
},
+ "Connect Spotify" : {
+ "comment" : "A label displayed above the Spotify login sheet.",
+ "isCommentAutoGenerated" : true
+ },
"Connected" : {
"extractionState" : "stale",
"localizations" : {
@@ -11679,6 +13243,12 @@
"value" : "Подключено"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bağlı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11701,6 +13271,12 @@
"value" : "Напрямую подключается к приложению Apple Music."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Doğrudan Apple Music uygulamasına bağlanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11723,6 +13299,12 @@
"value" : "Напрямую подключается к приложению Spotify."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Doğrudan Spotify uygulamasına bağlanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11813,8 +13395,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Continue"
+ "state" : "translated",
+ "value" : "Devam et"
}
},
"uk" : {
@@ -11853,6 +13435,12 @@
"value" : "Палитра элементов управления"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kontrol Paleti"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11881,6 +13469,12 @@
"value" : "Управляет доступностью таймера, Live Activities и тем, дублирует ли приложение таймеры из системного приложения Часы."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı kullanılabilirliğini, canlı etkinlik davranışını ve uygulamanın macOS Clock uygulamasından başlatılan zamanlayıcıları yansıtıp yansıtmadığını kontrol edin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11915,6 +13509,12 @@
"value" : "Управление"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kontroller"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11943,6 +13543,12 @@
"value" : "Управляет частотой обновления системных метрик при активном мониторинге."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzleme etkinken sistem ölçümlerinin ne sıklıkta yenilendiğini kontrol eder."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11965,6 +13571,12 @@
"value" : "Управляет плашкой напоминаний на экране блокировки и ее позицией."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı hatırlatma çipini ve konumunu kontrol eder."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -11994,6 +13606,12 @@
"value" : "Управляет областью напоминаний на экране блокировки и её расположением."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genel malzeme ayarından bağımsız olarak klasik, buzlu veya sıvı cam yüzeyi de dahil olmak üzere medya panelinin üzerinde yüzen isteğe bağlı zamanlayıcı widget'ını kontrol eder."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12022,6 +13640,12 @@
"value" : "Управляет тем, дублирует ли Dynamic Island системные события блокировки/разблокировки своими Live Activity и звуками."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dynamic Island'ın kendi canlı etkinliği ve sesli çanları ile kilitleme/kilit açma olaylarını yansıtıp yansıtmadığını kontrol eder."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12052,6 +13676,12 @@
"value" : "Копировать ссылку на событие"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinlik bağlantısını kopyala"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12082,6 +13712,12 @@
"value" : "Копировать элементы при перетаскивании"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Öğeleri sürüklerken kopyala"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12116,6 +13752,12 @@
"value" : "Скопировать последний отчет об ошибке"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "En Son Kilitlenme Raporunu Kopyala"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12150,6 +13792,12 @@
"value" : "Скопируйте что-нибудь для начала"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Başlamak için bir şeyi kopyalayın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12184,6 +13832,12 @@
"value" : "Скопируйте что-нибудь для начала"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Başlamak için bir şeyi kopyalayın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12214,6 +13868,12 @@
"value" : "Скопируйте текст, чтобы он здесь появился"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Burada görmek için metni kopyalayın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12238,6 +13898,12 @@
"value" : "Ядро %lld"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Core %lld"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12261,6 +13927,12 @@
"value" : "Частота ядра"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çekirdek Saat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12283,6 +13955,12 @@
"value" : "Ядра"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çekirdekler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12305,6 +13983,12 @@
"value" : "Масштабирование радиуса углов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Köşe yarıçapı ölçeklendirme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12335,6 +14019,12 @@
"value" : "Стиль обратного отсчета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geri sayım stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12359,6 +14049,12 @@
"value" : "CPU"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "CPU"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12381,6 +14077,12 @@
"value" : "Обзор CPU"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "CPU Genel Bakış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12415,6 +14117,12 @@
"value" : "ЦП"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "CPU Kullanımı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12439,6 +14147,12 @@
"value" : "Творите и дизайните с пипеткой, зеркалом и визуальными эффектами."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk seçici, ayna ve görsel efektlerle yaratın ve tasarlayın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12467,6 +14181,12 @@
"value" : "Создание и дизайн: пипетка, зеркало, визуальные эффекты"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Panodan Oluştur"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12489,6 +14209,12 @@
"value" : "Критическое"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kritik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12524,6 +14250,12 @@
"value" : "Текущий таймер:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mevcut özel zamanlayıcı:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12554,6 +14286,12 @@
"value" : "Выбран по умолчанию:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mevcut varsayılan:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12588,6 +14326,12 @@
"value" : "Текущая история"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Güncel Tarih"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12622,6 +14366,12 @@
"value" : "Текущие элементы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mevcut Öğeler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12652,6 +14402,12 @@
"value" : "Текущее преобразование"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Akım Dönüşümü"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12688,6 +14444,12 @@
"value" : "В данный момент активен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şu anda aktif"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12718,6 +14480,12 @@
"value" : "Выбрано: %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şu anda seçili: %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12736,6 +14504,12 @@
"value" : "현재: %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şu anda: %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12758,6 +14532,12 @@
"value" : "Курсор"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İmleç"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12780,6 +14560,12 @@
"value" : "Стиль курсора"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İmleç stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12815,6 +14601,12 @@
"value" : "Стиль указателя"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12856,6 +14648,12 @@
"value" : "Пользовательский цвет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel renk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12878,6 +14676,12 @@
"value" : "사용자 지정 집중 모드 메타데이터"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel Odak meta verileri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -12969,8 +14773,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Custom height"
+ "state" : "translated",
+ "value" : "Özel yükseklik"
}
},
"uk" : {
@@ -13008,6 +14812,12 @@
"value" : "Пользовательский Liquid"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel Sıvı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13044,6 +14854,12 @@
"value" : "Настраиваемая жидкость"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel sıvı cam zaten Apple'ın sıvı malzemesiyle işleniyor, dolayısıyla bu seçenek otomatik olarak yönetiliyor."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13073,6 +14889,12 @@
"value" : "Пользовательский Liquid доступен на macOS 26 и новее."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Custom Liquid, macOS 26 veya üzeri sürümlerde mevcuttur."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13108,6 +14930,12 @@
"value" : "Настраиваемая жидкость доступна на macOS 26+"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel Liquid ayarları Liquid Glass malzemesini gerektirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13137,6 +14965,12 @@
"value" : "Вариант пользовательского Liquid"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel sıvı çeşidi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13228,8 +15062,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Custom music live activity animation"
+ "state" : "translated",
+ "value" : "Özel müzik canlı aktivite animasyonu"
}
},
"uk" : {
@@ -13328,8 +15162,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Custom notch size - %.0f"
+ "state" : "translated",
+ "value" : "Özel çentik boyutu - %.0f"
}
},
"uk" : {
@@ -13368,6 +15202,12 @@
"value" : "Собственный OSD"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel OSD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13398,6 +15238,12 @@
"value" : "Функция Собственный OSD требует macOS 15 и новее."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel OSD özelliği, macOS 15 veya üzerini gerektirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13432,6 +15278,12 @@
"value" : "Свой таймер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel Zamanlayıcı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13462,6 +15314,12 @@
"value" : "Длительность пользовательского таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel Zamanlayıcı Süresi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13552,8 +15410,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Custom vizualizers (Lottie)"
+ "state" : "translated",
+ "value" : "Özel görselleştiriciler (Lottie)"
}
},
"uk" : {
@@ -13596,6 +15454,12 @@
"value" : "Свой: %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel: %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13630,6 +15494,12 @@
"value" : "Настроить анимацию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyonu Özelleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13660,6 +15530,12 @@
"value" : "Настройте организацию и создание заметок. Включение фильтра по цвету и поиска поможет управлять большими списками."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notları düzenleme ve oluşturma şeklinizi özelleştirin. Renk filtrelemeyi ve aramayı etkinleştirmek büyük listeleri yönetmenize yardımcı olur."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13750,8 +15626,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Customize in Settings"
+ "state" : "translated",
+ "value" : "Ayarlar'da özelleştirin"
}
},
"uk" : {
@@ -13778,6 +15654,12 @@
"value" : "파일을 드롭할 때 나타나는 LocalSend 기기 선택 팝업의 모양을 맞춤 설정합니다."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dosyaları bıraktığınızda görünen LocalSend aygıt seçimi açılır penceresinin görünümünü özelleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13808,6 +15690,12 @@
"value" : "Пунктирный контур соответствует точным границам выреза."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çizgi taslağı tam çentik sınırlarına eşittir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13838,6 +15726,12 @@
"value" : "Глубокая работа"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Derin Çalışma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13928,8 +15822,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Default"
+ "state" : "translated",
+ "value" : "Varsayılan"
}
},
"uk" : {
@@ -13974,6 +15868,12 @@
"value" : "Стандартный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varsayılan Özel Zamanlayıcı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -13989,6 +15889,12 @@
"state" : "translated",
"value" : "기본 보기"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varsayılan Görünüm"
+ }
}
}
},
@@ -14018,6 +15924,12 @@
"value" : "По умолчанию: dynamic.m4a"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varsayılan: dinamik.m4a"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14052,6 +15964,12 @@
"value" : "Удалить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14080,6 +15998,12 @@
"value" : "Удалить все заметки?"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tüm Notlar Silinsin mi?"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14116,6 +16040,12 @@
"value" : "Удалить анимацию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyonu Sil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14150,6 +16080,12 @@
"value" : "Удалить цвет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rengi Sil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14166,6 +16102,12 @@
"value" : "거부됨"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reddedildi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14196,6 +16138,12 @@
"value" : "Запрещено/Отозвано"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reddedildi/İptal Edildi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14226,6 +16174,12 @@
"value" : "Запретить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reddet"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14317,8 +16271,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Description"
+ "state" : "translated",
+ "value" : "Açıklama"
}
},
"uk" : {
@@ -14349,6 +16303,12 @@
"value" : "Отменить выбор"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tümünün Seçimini Kaldır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14373,6 +16333,12 @@
"value" : "Дизайнер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tasarımcı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14409,6 +16375,12 @@
"value" : "Дизайнер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Algılama Durumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14433,6 +16405,12 @@
"value" : "Разработчик"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geliştirici"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14451,6 +16429,12 @@
"value" : "기기 선택 스타일"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cihaz Seçici Stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14481,6 +16465,12 @@
"value" : "Разработчик"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cihazlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14511,6 +16501,12 @@
"value" : "Диагностика"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Teşhis"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14536,6 +16532,12 @@
"value" : "Цифровой"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dijital"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14564,6 +16566,12 @@
"value" : "Цифровой"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Boyutlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14655,8 +16663,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Disable"
+ "state" : "translated",
+ "value" : "Devre dışı bırak"
}
},
"uk" : {
@@ -14687,6 +16695,12 @@
"value" : "Отключите минималистичный интерфейс, чтобы настроить стандартный медиаплеер выреза."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Standart çentikli medya kontrollerini yapılandırmak için Minimalist Kullanıcı Arayüzünü devre dışı bırakın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14777,8 +16791,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Disabled"
+ "state" : "translated",
+ "value" : "Devre dışı"
}
},
"uk" : {
@@ -14794,6 +16808,9 @@
}
}
}
+ },
+ "Disabled while external display brightness integration is active." : {
+
},
"Disabled while external display integration is active — brightness keys are handled by the external app." : {
"localizations" : {
@@ -14809,6 +16826,12 @@
"value" : "Отключено при активной интеграции внешнего дисплея — клавиши яркости обрабатываются внешним приложением."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Harici ekran entegrasyonu etkinken devre dışı bırakılır; parlaklık tuşları harici uygulama tarafından yönetilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14816,6 +16839,9 @@
}
}
}
+ },
+ "Disabled while external display volume integration is active." : {
+
},
"Discrete transitions snap between Green (0–60%), Yellow (60–85%), and Red (85–100%)." : {
"comment" : "A description of how the color-coded progress bars work when not using smooth transitions.",
@@ -14839,6 +16865,12 @@
"value" : "Отключено при активной интеграции BetterDisplay — приложение использует Cmd+Клавиши яркости для своего управления."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayrık geçişler Yeşil (%0–60), Sarı (%60–85) ve Kırmızı (%85–100) arasında geçiş yapar."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14863,6 +16895,12 @@
"value" : "Диск"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "disk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14885,6 +16923,12 @@
"value" : "Ввод/вывод диска"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Disk G/Ç"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14907,6 +16951,12 @@
"value" : "Обзор диска"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Diske Genel Bakış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14941,6 +16991,12 @@
"value" : "Диск"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Disk Okuma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -14975,6 +17031,12 @@
"value" : "Запись на диск"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Diske Yazma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15009,6 +17071,12 @@
"value" : "Режим отображения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15038,6 +17106,12 @@
"value" : "Параметры отображения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Seçenekleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15060,6 +17134,12 @@
"value" : "Стиль отображения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15096,6 +17176,12 @@
"value" : "Стиль дисплея"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bluetooth ses aygıtları (kulaklık, AirPods, hoparlörler) bağlandığında aygıt adını ve pil seviyesini gösteren bir HUD bildirimi görüntüler."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15124,6 +17210,12 @@
"value" : "Отображает кнопки \"Воспроизведение/Пауза\" и \"Вперед\" рядом с вырезом при активной музыке. Отключено по умолчанию."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Müzik etkinken çentiğin yanında oynat/duraklat ve atla düğmelerini görüntüler. Varsayılan olarak devre dışı."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15152,6 +17244,12 @@
"value" : "Показывает текущее Фокусирование над виджетом погоды, если включено отслеживание Фокусирования."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odak algılama etkinleştirildiğinde hava durumu kapsülünün üzerinde mevcut Odak durumunu görüntüler."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15174,6 +17272,12 @@
"value" : "Показывает следующее событие календаря над или под виджетом погоды. Выбор календаря не зависит от фильтров Dynamic Island."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bir sonraki yaklaşan takvim etkinliğinizi hava durumu kapsülünün üstünde veya altında görüntüler. Buradaki takvim seçimi Dynamic Island takvim filtresinden bağımsızdır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15202,6 +17306,12 @@
"value" : "Отображает ближайшее предстоящее событие календаря над или под виджетом погоды. Выбор календаря здесь не зависит от фильтра календаря в динамическом острове."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rahatsız Etmeyin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15232,6 +17342,12 @@
"value" : "Поддержать проект"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bağış yap"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15262,6 +17378,12 @@
"value" : "Поддержать"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bitti"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15290,6 +17412,12 @@
"value" : "Вниз"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aşağı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15427,6 +17555,12 @@
"value" : "Отслеживание загрузок"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Algılamayı İndir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15468,6 +17602,12 @@
"value" : "Стиль значка загрузки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Simge stilini indir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15508,6 +17648,12 @@
"value" : "Стиль индикатора загрузки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gösterge stilini indirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15537,6 +17683,12 @@
"value" : "Индикаторы загрузки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Göstergeleri indir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15565,6 +17717,12 @@
"value" : "Загружено"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İndirildi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15696,6 +17854,12 @@
"value" : "Перетащите элемент в слот или нажмите, чтобы добавить его в первый свободный слот."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bir kontrolü bir yuvaya sürükleyin veya ilk boş yuvaya yerleştirmek için dokunun."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15726,6 +17890,12 @@
"value" : "Перетаскивайте элементы между слотами или сбрасывайте их с палитры для изменения управления."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kontrolleri yeniden eşlemek için öğeleri yuvalar arasında sürükleyin veya paletten bırakın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15756,6 +17926,12 @@
"value" : "Перетаскивайте превью по вертикали и используйте ползунки ширины (не превышая исходный размер). Изменения применяются мгновенно."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dikey yerleşimi ayarlamak için önizlemeleri sürükleyin. Pozitif değerler paneli kaldırır; Negatif değerler onu düşürür. Medya ve zamanlayıcı widget'larını varsayılan boyutlarını aşmadan daraltmak için aşağıdaki genişlik kaydırıcılarını kullanın. Widget'lar görünür durumdayken değişiklikler anında uygulanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15778,6 +17954,12 @@
"value" : "Бросьте файл PNG, JPEG, TIFF или ICNS, чтобы добавить его в библиотеку иконок."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Simge kitaplığınıza eklemek için bir PNG, JPEG, TIFF veya ICNS dosyasını bırakın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15902,6 +18084,12 @@
"value" : "Dynamic Island"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dynamic Island"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15932,6 +18120,12 @@
"value" : "Динамический остров"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dynamic Island İlerleme Çubukları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15954,6 +18148,12 @@
"value" : "Видимость Dynamic Island"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dynamic Island Görünürlük"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -15976,6 +18176,12 @@
"value" : "Видимость динамического острова"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "dynamic.island"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16011,6 +18217,12 @@
"value" : "Каждый график можно включить или выключить отдельно. Сетевая активность показывает скорость интернета, а ввода-вывода — скорость диска."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Her grafik ayrı ayrı etkinleştirilebilir veya devre dışı bırakılabilir. Ağ etkinliği indirme/yükleme hızlarını gösterir ve disk G/Ç okuma/yazma hızlarını gösterir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16035,6 +18247,12 @@
"value" : "ЕИВ (EAQI)"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "EAQI"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16125,8 +18343,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!"
+ "state" : "translated",
+ "value" : "En çok kullanılan içeriğinizi kolayca kopyalayın, saklayın ve yönetin. Çok öğeli depolama ve hızlı erişim gibi gelişmiş özellikler için şimdi yükseltin!"
}
},
"uk" : {
@@ -16143,6 +18361,23 @@
}
}
},
+ "Edit" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Edit"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düzenle"
+ }
+ }
+ }
+ },
"Edit Animation" : {
"localizations" : {
"fr" : {
@@ -16169,6 +18404,12 @@
"value" : "Изменить анимацию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyonu Düzenle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16298,6 +18539,12 @@
"value" : "Электронная почта"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "E-posta adresi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16314,6 +18561,12 @@
"value" : "빈 슬롯"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Boş Yuva"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16324,6 +18577,12 @@
},
"Enable \"Idle Animation\" to customize animations" : {
"localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Enable \"Idle Animation\" to customize animations"
+ }
+ },
"ko" : {
"stringUnit" : {
"state" : "translated",
@@ -16336,6 +18595,12 @@
"value" : "Включите \"Анимация бездействия\" для персональных анимаций."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyonları özelleştirmek için \"Boşta animasyonu\"nu etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16367,6 +18632,12 @@
"value" : "Включите \"Отображать лицо при бездействии\", чтобы настроить анимации."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyonları özelleştirmek için \"Hareketsizlik sırasında havalı yüz animasyonunu göster\"i etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16389,6 +18660,12 @@
"value" : "Включить параллакс обложки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Albüm kapağı paralaksını etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16412,6 +18689,12 @@
"value" : "Включить параллакс-эффект"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Albüm kapağı paralaks efektini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16442,6 +18725,12 @@
"value" : "Включение и настройка плера над системными часами при заблокированном экране."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran kilitliyken sistem saatinin üzerinde görünen medya denetimlerini etkinleştirin ve şekillendirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16464,6 +18753,12 @@
"value" : "Размытие фона обложки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Albüm resminin arkasında bulanıklaştırma efektini etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16493,6 +18788,12 @@
"value" : "Включить системный HUD"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yerleşik Sistemi Etkinleştir HUD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16522,6 +18823,12 @@
"value" : "Заменяет стандартные HUD-уведомления macOS (яркость/громкость) красивыми элементами Atoll."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "macOS HUD bildirimlerini Atoll ekranlarıyla değiştirmek için yerleşik sistem izlemeyi etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16550,6 +18857,12 @@
"value" : "Разрешить доступ к календарю"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim Erişimini Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16578,6 +18891,12 @@
"value" : "Разрешить использование камеры"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kamera Erişimini Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16600,6 +18919,12 @@
"value" : "Включить обнаружение камеры"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kamera Algılamayı Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16622,6 +18947,12 @@
"value" : "Включить Менеджер буфера обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano Yöneticisini Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16644,6 +18975,12 @@
"value" : "Включить фильтрацию по цвету"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Filtrelemeyi Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16666,6 +19003,12 @@
"value" : "Включить пипетку"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçiciyi Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16682,6 +19025,12 @@
"value" : "색상 스펙트로그램 활성화"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renkli spektogramları etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16773,8 +19122,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Enable colored spectrograms"
+ "state" : "translated",
+ "value" : "Renkli spektrogramları etkinleştir"
}
},
"uk" : {
@@ -16817,6 +19166,12 @@
"value" : "Включите Пипетку: Настройки → Пипетка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayarlar → ColorPicker'da ColorPicker'ı etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16839,6 +19194,12 @@
"value" : "Создавать из буфера обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Panodan Oluşturmayı Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16869,6 +19230,12 @@
"value" : "Кастомизация управления"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özelleştirilebilir kontrolleri etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16891,6 +19258,12 @@
"value" : "Отслеживать загрузки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İndirme algılamayı etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16913,6 +19286,12 @@
"value" : "Включите управление Dynamic Island для контроля панели экрана блокировки."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı panelini yönetmek için Dynamic Island medya kontrollerini etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16935,6 +19314,12 @@
"value" : "Разрешить Динамическое Зеркало"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dinamik yansıtmayı etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16959,6 +19344,12 @@
"value" : "Логирование диагностики расширений"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantı tanılama günlüğünü etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -16989,6 +19380,12 @@
"value" : "Включить журнал диагностики для расширений"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Üçüncü taraf uygulamaların Atoll içinde canlı etkinlikler ve kilit ekranı widget'ları göstermesine izin vermek için uzantıları etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17013,6 +19410,12 @@
"value" : "Внешний датчик контроля громкости"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Harici ses kontrolü dinleyicisini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17035,6 +19438,12 @@
"value" : "Включить отслеживание Фокусирования"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odak Algılamayı Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17057,6 +19466,12 @@
"value" : "Включить жесты"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hareketleri etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17079,6 +19494,12 @@
"value" : "Включить глобальные сочетания клавиш"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genel klavye kısayollarını etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17107,6 +19528,12 @@
"value" : "Включите глобальные сочетания клавиш выше, чтобы настроить их."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kısayollarınızı özelleştirmek için yukarıdaki genel klavye kısayollarını etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17129,6 +19556,12 @@
"value" : "Включить эффект свечения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parlama efektini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17159,6 +19592,12 @@
"value" : "Включите отображение графиков в Settings → Stats."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Performans verilerini görüntülemek için Ayarlar → İstatistikler bölümünde grafik görünürlüğünü etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17249,8 +19688,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Enable haptics"
+ "state" : "translated",
+ "value" : "Dokunsal özelliği etkinleştir"
}
},
"uk" : {
@@ -17350,8 +19789,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Enable HUD replacement"
+ "state" : "translated",
+ "value" : "HUD değiştirmeyi etkinleştir"
}
},
"uk" : {
@@ -17382,6 +19821,12 @@
"value" : "Включите в Settings → Terminal"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayarlar → Terminal'de etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17404,6 +19849,12 @@
"value" : "Включить Live Activity на экране блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı canlı etkinliğini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17426,6 +19877,12 @@
"value" : "Включить тексты песен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şarkı sözlerini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17456,6 +19913,12 @@
"value" : "Включите в Настройках → Терминал"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya paneli bulanıklığını etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17478,6 +19941,12 @@
"value" : "Отслеживать использование микрофона"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofon Algılamayı Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17500,6 +19969,12 @@
"value" : "Минималистичный интерфейс"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Minimalist Kullanıcı Arayüzünü Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17590,8 +20065,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Enable music live activity"
+ "state" : "translated",
+ "value" : "Müzik canlı etkinliğini etkinleştir"
}
},
"uk" : {
@@ -17622,6 +20097,12 @@
"value" : "Разрешить закрепление заметок"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Not Sabitlemeyi Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17644,6 +20125,12 @@
"value" : "Поиск по заметкам"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Not Aramayı Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17666,6 +20153,12 @@
"value" : "Включить Заметки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notları Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17682,6 +20175,12 @@
"value" : "실시간 파형 활성화"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gerçek zamanlı dalga biçimini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17704,6 +20203,12 @@
"value" : "Включить Live Activity напоминаний"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hatırlatıcı canlı etkinliğini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17726,6 +20231,12 @@
"value" : "Использовать Помощника экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Screen Assistant'ı etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17754,6 +20265,12 @@
"value" : "Включите пипетку цветов. Cmd+Shift+P для быстрого доступа."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran rengi toplama işlevini etkinleştirin. Renk seçiciye hızlı bir şekilde erişmek için Cmd+Shift+P tuşlarını kullanın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17776,6 +20293,12 @@
"value" : "Отслеживать запись экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Kaydı Algılamayı Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17798,6 +20321,12 @@
"value" : "Включить полку"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rafı etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17832,6 +20361,12 @@
"value" : "Включить быстрый просмотр"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gizli bakışı etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17860,6 +20395,12 @@
"value" : "Включите мониторинг системы в настройках."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem performans verilerini görüntülemek için Ayarlar'da istatistik izlemeyi etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17882,6 +20423,12 @@
"value" : "Включить мониторинг системы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem istatistikleri izlemeyi etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17904,6 +20451,12 @@
"value" : "Включить терминал"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Terminali etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17932,6 +20485,12 @@
"value" : "Включить капсулу батареи и настроить её."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil kapsülünü etkinleştirin ve düzenini yapılandırın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17962,6 +20521,12 @@
"value" : "Включите функцию таймера в настройках, чтобы получить доступ."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu sekmeye erişmek için Ayarlar'da zamanlayıcı özelliğini etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -17992,6 +20557,12 @@
"value" : "Включить капсулу погоды и настроить её."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava kapsülünü etkinleştirin ve düzenini, sağlayıcısını, birimlerini ve isteğe bağlı pil/AQI göstergelerini yapılandırın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18020,6 +20591,12 @@
"value" : "Режим обдумывания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düşünme Modunu Etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18035,6 +20612,12 @@
"state" : "translated",
"value" : "타사 캘린더 앱 실행 활성화"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Üçüncü taraf takvim uygulamasının başlatılmasını etkinleştirin"
+ }
}
}
},
@@ -18054,6 +20637,12 @@
"value" : "Включить интеграцию со сторонними DDC приложениями"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Üçüncü taraf DDC uygulama entegrasyonunu etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18078,6 +20667,12 @@
"value" : "Включить сторонние расширения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Üçüncü taraf uzantıları etkinleştirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18100,6 +20695,12 @@
"value" : "Включить функцию таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı özelliğini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18128,6 +20729,12 @@
"value" : "Включить сторонние расширения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı canlı etkinliğini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18152,6 +20759,12 @@
"value" : "Маршрутизировать изменения дисплея через Atoll."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "BetterDisplay veya Lunar ekran ayarlarını Atoll'un etkin HUD stili üzerinden yönlendirmek için etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18180,6 +20793,12 @@
"value" : "Включите, чтобы отображать изменения яркости и громкости BetterDisplay через панель Atoll вместо системных экранных индикаторов."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll'un sorunsuz açılması için 30x20 pikselin üzerine ölçeklendirirken etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18202,6 +20821,12 @@
"value" : "Включить тень окна"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pencere gölgesini etkinleştir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18238,6 +20863,12 @@
"value" : "Использование движка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Motor Kullanımı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18328,8 +20959,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Enhance your experience with HUDs"
+ "state" : "translated",
+ "value" : "HUD'lerle deneyiminizi geliştirin"
}
},
"uk" : {
@@ -18472,6 +21103,12 @@
"value" : "Введите Gemini API Key"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gemini API Anahtarınızı girin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18597,6 +21234,12 @@
"value" : "Цвет события"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinlik rengi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18627,6 +21270,12 @@
"value" : "Цвет события"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinlik Listesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18662,6 +21311,12 @@
"value" : "Исключить приложения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uygulamaları hariç tut"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18791,6 +21446,12 @@
"value" : "Расширять вырез под ширину анимации"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyon genişliğini takip etmek için çentiği genişletin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18821,6 +21482,12 @@
"value" : "Расширенная зона перетаскивания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genişletilmiş sürükleme algılama alanı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18851,6 +21518,12 @@
"value" : "Увеличенная ширина выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genişletilmiş çentik genişliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18873,6 +21546,12 @@
"value" : "Расширить зону наведения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fareyle üzerine gelme alanını genişlet"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -18903,6 +21582,12 @@
"value" : "Вкладка расширения недоступна"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantı sekmesi kullanılamıyor"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19040,6 +21725,12 @@
"value" : "Внешний"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Harici"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19064,6 +21755,12 @@
"value" : "Стиль внешнего дисплея"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Harici ekran stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19086,6 +21783,12 @@
"value" : "Скорость кулера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fan Hızı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19110,6 +21813,12 @@
"value" : "Избранное"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Favoriler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19134,6 +21843,12 @@
"value" : "Файл"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dosya"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19152,6 +21867,12 @@
"value" : "여기에 드롭한 파일은 이 서비스를 통해 공유됩니다."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buraya bırakılan dosyalar bu hizmet aracılığıyla paylaşılacak"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19182,6 +21903,12 @@
"value" : "Файл"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rafa düşen dosyalar bu hizmet aracılığıyla paylaşılacak"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19200,6 +21927,12 @@
"value" : "서랍에서 공유된 파일은 이 서비스를 사용합니다."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Raftan paylaşılan dosyalar bu hizmeti kullanacak"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19228,6 +21961,12 @@
"value" : "Заполнить вырез"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentiği Doldur"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19318,8 +22057,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Finish"
+ "state" : "translated",
+ "value" : "Bitiş"
}
},
"uk" : {
@@ -19356,6 +22095,12 @@
"value" : "Вписать в вырез"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentiklere Sığdır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19386,6 +22131,12 @@
"value" : "Поведение пропуска плавающего окна"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayan pencere paneli atlama davranışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19410,6 +22161,12 @@
"value" : "Фокус"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odaklanma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19440,6 +22197,12 @@
"value" : "Фокус"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkin odaklama: %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19464,6 +22227,12 @@
"value" : "Режим обнаружения фокуса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odak algılama modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19492,6 +22261,12 @@
"value" : "Режим обнаружения фокуса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odaklanma Durumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19520,6 +22295,12 @@
"value" : "Виджет Фокуса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odak Widget'ı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19543,6 +22324,12 @@
"value" : "Шрифт"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yazı tipi ailesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19567,6 +22354,12 @@
"value" : "Размер шрифта"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yazı tipi boyutu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19589,6 +22382,12 @@
"value" : "Передний план"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ön plan"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19605,6 +22404,12 @@
"value" : "10초 앞으로 가기"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "10'lu ileri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19627,6 +22432,12 @@
"value" : "Свободно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ücretsiz"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19663,6 +22474,12 @@
"value" : "Текст"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ücretsiz · %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19685,6 +22502,12 @@
"value" : "Частота"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Frekans"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19709,6 +22532,12 @@
"value" : "Матовое стекло"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Buzlu Cam"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19725,6 +22554,12 @@
"value" : "전체 접근 가능"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tam Erişim"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19732,6 +22567,15 @@
}
}
}
+ },
+ "Full Battery" : {
+
+ },
+ "Full battery duration" : {
+
+ },
+ "Full battery style" : {
+
},
"Full charge" : {
"comment" : "Text displayed in the status text when the battery is fully charged.",
@@ -19749,6 +22593,12 @@
"value" : "Полный заряд"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tam şarj"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19756,6 +22606,9 @@
}
}
}
+ },
+ "Full charge threshold" : {
+
},
"Full Disk Access required" : {
"extractionState" : "stale",
@@ -19778,6 +22631,12 @@
"value" : "Заряжено"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tam Disk Erişimi gerekli"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19794,6 +22653,12 @@
"value" : "전체 디스크 접근 권한을 허용하면 사용자 지정 집중 모드 아이콘, 색상 및 레이블을 사용할 수 있습니다. 권한이 없어도 표준 집중 모드 감지는 정상적으로 작동하므로, 개인화된 표시기가 필요한 경우에만 권한을 허용하십시오."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tam Disk Erişimi, özel Odak simgelerinin, renklerinin ve etiketlerinin kilidini açar. Standart Odak tespiti bu özellik olmadan da çalışır; yalnızca kişiselleştirilmiş göstergelere ihtiyacınız varsa erişim izni verin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19801,6 +22666,9 @@
}
}
}
+ },
+ "Fullscreen artwork on right-click" : {
+
},
"Fully charged" : {
"comment" : "Charging fallback label when already charged",
@@ -19823,6 +22691,12 @@
"value" : "Полностью заряжена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tamamen şarj edildi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19830,6 +22704,9 @@
}
}
}
+ },
+ "Fully charged HUD" : {
+
},
"Gemini API Key" : {
"localizations" : {
@@ -19857,6 +22734,12 @@
"value" : "Ключ Gemini API"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gemini API Anahtarı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -19947,8 +22830,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "General"
+ "state" : "translated",
+ "value" : "Genel"
}
},
"uk" : {
@@ -20047,8 +22930,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Gesture control"
+ "state" : "translated",
+ "value" : "Hareket kontrolü"
}
},
"uk" : {
@@ -20147,8 +23030,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Gesture sensitivity"
+ "state" : "translated",
+ "value" : "Hareket hassasiyeti"
}
},
"uk" : {
@@ -20187,6 +23070,12 @@
"value" : "Поведение при пропуске жестом"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hareketle atlama davranışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20277,8 +23166,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Get started"
+ "state" : "translated",
+ "value" : "Başlayın"
}
},
"uk" : {
@@ -20295,6 +23184,10 @@
}
}
},
+ "Get the cookie manually" : {
+ "comment" : "A section that provides instructions on how to get the Spotify cookie manually.",
+ "isCommentAutoGenerated" : true
+ },
"Get your free API key from Google AI Studio" : {
"localizations" : {
"fr" : {
@@ -20321,6 +23214,12 @@
"value" : "Получите бесплатный API ключ в Google AI Studio"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Google AI Studio'den ücretsiz API anahtarınızı alın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20411,7 +23310,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "GitHub"
}
},
@@ -20446,6 +23345,12 @@
"value" : "Стекло"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cam"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20475,6 +23380,12 @@
"value" : "Стекло"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cam modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20505,6 +23416,12 @@
"value" : "Глобальные настройки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genel Ayarlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20595,8 +23512,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Got it!"
+ "state" : "translated",
+ "value" : "Anladım!"
}
},
"uk" : {
@@ -20629,6 +23546,12 @@
"value" : "GPU"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GPU"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20651,6 +23574,12 @@
"value" : "Обзор GPU"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GPU Genel Bakış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20685,6 +23614,12 @@
"value" : "ГП"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "GPU Kullanımı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20775,8 +23710,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Gradient"
+ "state" : "translated",
+ "value" : "Gradyan"
}
},
"uk" : {
@@ -20819,6 +23754,12 @@
"value" : "Отображение графиков"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Grafik Görünürlüğü"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20844,6 +23785,12 @@
"value" : "Зеленый"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yeşil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20879,6 +23826,12 @@
"value" : "Зелёный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yükseklik:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20907,6 +23860,12 @@
"value" : "Высота: %lld px"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yükseklik: %lldpx"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20915,6 +23874,23 @@
}
}
},
+ "Hello, glass!" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hello, glass!"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Merhaba, cam!"
+ }
+ }
+ }
+ },
"HH" : {
"localizations" : {
"ko" : {
@@ -20929,6 +23905,12 @@
"value" : "HH"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SS"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20963,6 +23945,12 @@
"value" : "Скрыть"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -20979,6 +23967,12 @@
"value" : "활성 이벤트를 숨기고 다음 예정된 이벤트 표시"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkin etkinliği gizle ve bir sonraki yaklaşan etkinliği göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21009,6 +24003,12 @@
"value" : "Скрыть события на весь день"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tüm gün süren etkinlikleri gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21039,6 +24039,12 @@
"value" : "Скрыть завершенные напоминания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tamamlanan hatırlatıcıları gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21061,6 +24067,12 @@
"value" : "Скрывать Dynamic Island на снимках и записях экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran görüntüleri ve kayıtlar sırasında Dynamic Island'yi gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21095,6 +24107,12 @@
"value" : "Скрыть настройки Dynamic Island"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "DynamicIsland Seçeneklerini Gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21117,6 +24135,12 @@
"value" : "Скрыть предпросмотр экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı önizlemesini gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21207,8 +24231,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Hide only when NowPlaying app is in fullscreen"
+ "state" : "translated",
+ "value" : "Yalnızca NowPlaying uygulaması tam ekrandayken gizle"
}
},
"uk" : {
@@ -21239,6 +24263,12 @@
"value" : "Скрывать до наведения на дисплеях без выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentiksiz ekranlarda fareyle üzerine gelinene kadar gizle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21329,8 +24359,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Hierarchical"
+ "state" : "translated",
+ "value" : "Hiyerarşik"
}
},
"uk" : {
@@ -21430,8 +24460,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "High"
+ "state" : "translated",
+ "value" : "Yüksek"
}
},
"uk" : {
@@ -21468,6 +24498,12 @@
"value" : "Частые обновления без таймаута могут увеличить расход батареи."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zaman aşımı olmadan yüksek frekanslı güncellemeler pil kullanımını artırabilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21492,6 +24528,12 @@
"value" : "История"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tarih"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21526,6 +24568,12 @@
"value" : "История"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geçmiş Boyutu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21549,6 +24597,12 @@
"value" : "По горизонтали:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yatay:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21566,6 +24620,12 @@
"value" : "시간"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Saatler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21590,6 +24650,12 @@
"value" : "https://example.com/animation.json"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "https://example.com/animation.json"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21681,7 +24747,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "https://github.com/th-ch/youtube-music"
}
},
@@ -21699,6 +24765,9 @@
}
},
"shouldTranslate" : false
+ },
+ "HUD Duration" : {
+
},
"HUD icon style" : {
"comment" : "A label describing the style of the 3D or symbol-based Bluetooth HUD icon.",
@@ -21716,6 +24785,12 @@
"value" : "Стиль иконки HUD"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "HUD simge stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21744,6 +24819,12 @@
"value" : "Стиль значка в панели"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "HUD Konumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -21834,8 +24915,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "HUD style"
+ "state" : "translated",
+ "value" : "HUD tarzı"
}
},
"uk" : {
@@ -21851,6 +24932,9 @@
}
}
}
+ },
+ "HUD Tests" : {
+
},
"HUDs" : {
"extractionState" : "stale",
@@ -21935,8 +25019,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "HUDs"
+ "state" : "translated",
+ "value" : "HUD'ler"
}
},
"uk" : {
@@ -21976,6 +25060,12 @@
"value" : "Цвет иконки и индикатора"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Simge ve İlerleme Rengi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22012,6 +25102,12 @@
"value" : "Ожидание"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Boşta"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22034,6 +25130,12 @@
"value" : "Анимация ожидания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Boşta Animasyon"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22070,6 +25172,12 @@
"value" : "Стиль анимации ожидания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Boşta Animasyon Stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22094,6 +25202,12 @@
"value" : "Изображение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Resim"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22128,6 +25242,12 @@
"value" : "Изображение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İthalat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22164,6 +25284,12 @@
"value" : "Ошибка импорта"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İçe Aktarma Hatası"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22200,6 +25326,12 @@
"value" : "Импортировать файл..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dosyayı İçe Aktar..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22290,8 +25422,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "In progress"
+ "state" : "translated",
+ "value" : "Devam ediyor"
}
},
"uk" : {
@@ -22334,6 +25466,12 @@
"value" : "Неактивно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkin değil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22424,8 +25562,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Inline"
+ "state" : "translated",
+ "value" : "Satır içi"
}
},
"uk" : {
@@ -22462,6 +25600,12 @@
"value" : "Встроенный предпросмотр снимка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Satır içi anlık görüntü önizlemesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22484,6 +25628,12 @@
"value" : "Ввод"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Giriş"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22520,6 +25670,12 @@
"value" : "Автоматически устанавливать обновления"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Güncellemeleri otomatik olarak yükle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22612,8 +25768,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Installed extensions"
+ "state" : "translated",
+ "value" : "Yüklü uzantılar"
}
},
"uk" : {
@@ -22646,6 +25802,12 @@
"value" : "Интеграции"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Entegrasyonlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22674,6 +25836,12 @@
"value" : "Интерактивный (Перетащите для изменения)"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkileşimli (Değiştirmek için Sürükleyin)"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22696,6 +25864,12 @@
"value" : "Интерфейсы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Arayüzler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22720,6 +25894,12 @@
"value" : "IPv4 · %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "IPv4 · %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22745,6 +25925,12 @@
"value" : "IPv6 · %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "IPv6 · %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22774,6 +25960,12 @@
"value" : "Присоединиться к встрече"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Toplantıya katılın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22798,6 +25990,12 @@
"value" : "только что"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "şimdi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22822,6 +26020,12 @@
"value" : "Только что"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Az önce"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22829,6 +26033,9 @@
}
}
}
+ },
+ "Keep album art visible during fullscreen artwork" : {
+
},
"Keep terminal open until clicked outside" : {
"localizations" : {
@@ -22844,6 +26051,12 @@
"value" : "Держать терминал открытым до клика за его пределами"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dışarıdan tıklanana kadar terminali açık tutun"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22878,6 +26091,12 @@
"value" : "Только что"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Klavye Arka Işığı HUD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22908,6 +26127,12 @@
"value" : "OSD подсветки клавиатуры"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Klavye Arka Işığı OSD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22942,6 +26167,12 @@
"value" : "Сочетания клавиш отключены"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Klavye kısayolları devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -22972,6 +26203,12 @@
"value" : "Текст уплотнён до вкл/выкл, пока работает режим кратких уведомлений."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kısa kızartma modu etkinleştirildiğinde, etiketler açık/kapalı metni sıkıştırmaya zorlanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23002,6 +26239,12 @@
"value" : "Последняя причина отказа:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Son Reddedilme Sebebi:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23018,6 +26261,12 @@
"value" : "마지막 업데이트"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Son Güncelleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23052,6 +26301,12 @@
"value" : "Последнее обновление"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Son Güncelleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23142,8 +26397,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Later"
+ "state" : "translated",
+ "value" : "Daha sonra"
}
},
"uk" : {
@@ -23174,6 +26429,12 @@
"value" : "Запускать при входе"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Giriş sırasında başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23192,6 +26453,12 @@
"value" : "Макет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düzen"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23221,6 +26488,12 @@
"value" : "Макет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düzen Önizlemesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23249,6 +26522,12 @@
"value" : "Слева"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sol"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23278,6 +26557,12 @@
"value" : "Лицензионный ключ"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lisans anahtarı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23302,6 +26587,12 @@
"value" : "Легкая нагрузка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hafif Kullanım"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23330,6 +26621,12 @@
"value" : "Лёгкий режим"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Satır Genişliği: %lldpx"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23353,6 +26650,12 @@
"value" : "LinkedIn"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "LinkedIn"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23378,6 +26681,12 @@
"value" : "Liquid Glass"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sıvı Cam"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23413,6 +26722,12 @@
"value" : "Жидкое стекло"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Liquid Glass, macOS 26 veya üzerini gerektirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23431,6 +26746,12 @@
"value" : "Liquid Glass 변형"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sıvı Cam Varyantı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23459,6 +26780,12 @@
"value" : "Отслеживает изменения сеансов Фокуса через распределенные уведомления"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dağıtılmış bildirimler aracılığıyla Odaklanma oturumu değişikliklerini dinler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23489,6 +26816,12 @@
"value" : "LIVE"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "CANLI"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23519,6 +26852,12 @@
"value" : "Live Activities"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Canlı Etkinlikler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23549,6 +26888,12 @@
"value" : "Live Activity и Отклик"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Canlı Etkinlik ve Geri Bildirim"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23578,6 +26923,12 @@
"value" : "Мониторинг в реальном времени"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Canlı İzleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23612,6 +26963,12 @@
"value" : "Данные производительности"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Canlı Performans Verileri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23642,6 +26999,12 @@
"value" : "Предпросмотр в реальном времени"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Canlı Önizleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23672,6 +27035,12 @@
"value" : "Индикатор прямого эфира"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Canlı yayın göstergesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23694,6 +27063,12 @@
"value" : "Load 1м"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1m yük"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23716,6 +27091,12 @@
"value" : "Load 5м"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yük 5m"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23738,6 +27119,12 @@
"value" : "Load 15м"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yük 15 m"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23774,6 +27161,12 @@
"value" : "Средняя нагрузка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yük Ortalaması"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23798,6 +27191,12 @@
"value" : "Загрузка..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yükleniyor..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23816,6 +27215,12 @@
"value" : "LocalSend 기기 선택기"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "LocalSend Cihaz Seçici"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23850,6 +27255,12 @@
"value" : "Загрузка..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23879,6 +27290,12 @@
"value" : "Стекло экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Camı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23908,6 +27325,12 @@
"value" : "Режим стекла экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı cam modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23936,6 +27359,12 @@
"value" : "Интеграция с экраном блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Entegrasyonu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23966,6 +27395,12 @@
"value" : "Liquid Glass экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Sıvı Cam"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -23994,6 +27429,12 @@
"value" : "Медиа на экране блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Medyası"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24024,6 +27465,12 @@
"value" : "Предпросмотр панели экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı paneli önizlemesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24054,6 +27501,12 @@
"value" : "Позиционирование на экране блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Konumlandırma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24084,6 +27537,12 @@
"value" : "Виджет напоминаний экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Hatırlatma Widget'ı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24114,6 +27573,12 @@
"value" : "Виджеты экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Widget'ları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24136,6 +27601,12 @@
"value" : "Логические ядра"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mantıksal çekirdekler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24164,6 +27635,12 @@
"value" : "Режим зацикливания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Döngü Modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24254,7 +27731,7 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "Lottie JSON URL"
}
},
@@ -24355,8 +27832,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Low"
+ "state" : "translated",
+ "value" : "Düşük"
}
},
"uk" : {
@@ -24389,6 +27866,12 @@
"value" : "Низкий заряд батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düşük pil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24396,6 +27879,21 @@
}
}
}
+ },
+ "Low Battery" : {
+
+ },
+ "Low battery duration" : {
+
+ },
+ "Low battery HUD" : {
+
+ },
+ "Low battery style" : {
+
+ },
+ "Low battery threshold" : {
+
},
"Low Power Mode" : {
"localizations" : {
@@ -24513,6 +28011,12 @@
"value" : "Энергосбережение: %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düşük Güç: %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24529,6 +28033,12 @@
"value" : "가사"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şarkı sözleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24558,6 +28068,12 @@
"value" : "Режим энергосбережения: %d"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mac pili yüzde %d seviyesinde"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24588,6 +28104,12 @@
"value" : "Требуется macOS 15 или новее"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "macOS 15 veya üzeri gereklidir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24618,6 +28140,12 @@
"value" : "Сделано с ❤️ Ebullioscopic"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ebullioskopik tarafından ❤️ ile yapılmıştır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24640,6 +28168,12 @@
"value" : "Стиль главного экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ana ekran stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24658,6 +28192,12 @@
"value" : "다른 기기에서 LocalSend가 실행 중인지 확인하십시오."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "LocalSend'nin diğer cihazlarda çalıştığından emin olun"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24688,6 +28228,12 @@
"value" : "Стиль главного экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gösterge tercihlerini Ayarlar → Gizlilik bölümünden yönetin. Manuel mikrofon değiştirmeye gerek yoktur."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24718,6 +28264,12 @@
"value" : "Отметить как выполненное"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tamamlandı olarak işaretle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24748,6 +28300,12 @@
"value" : "Отметить как невыполненное"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Eksik olarak işaretle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24966,6 +28524,12 @@
"value" : "Материал"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Malzeme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -24997,6 +28561,12 @@
"value" : "Материал"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Malzeme Seçenekleri:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -25087,8 +28657,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Max Capacity: %lld%%"
+ "state" : "translated",
+ "value" : "Maksimum Kapasite: %lld%%"
}
},
"uk" : {
@@ -25121,6 +28691,12 @@
"value" : "Максимальная высота"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Maksimum yükseklik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -25245,6 +28821,12 @@
"value" : "Медиа и Дисплей"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya ve Görüntülü Reklam Ağı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -25337,8 +28919,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Media change with horizontal gestures"
+ "state" : "translated",
+ "value" : "Yatay hareketlerle medya değişimi"
}
},
"uk" : {
@@ -25438,8 +29020,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Media controls"
+ "state" : "translated",
+ "value" : "Medya kontrolleri"
}
},
"uk" : {
@@ -25539,8 +29121,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Media inactivity timeout"
+ "state" : "translated",
+ "value" : "Medya hareketsizliği zaman aşımı"
}
},
"uk" : {
@@ -25579,6 +29161,12 @@
"value" : "Медиа Live Activity"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya Canlı Etkinliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -25609,6 +29197,12 @@
"value" : "Вывод аудио"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya çıkışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -25639,6 +29233,12 @@
"value" : "Медиа-панель"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya Paneli"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -25655,6 +29255,12 @@
"value" : "미디어 패널 넓이"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya Paneli Genişliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -25746,8 +29352,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Media playback live activity"
+ "state" : "translated",
+ "value" : "Medya oynatma canlı etkinliği"
}
},
"uk" : {
@@ -25947,8 +29553,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Medium"
+ "state" : "translated",
+ "value" : "Orta"
}
},
"uk" : {
@@ -25985,6 +29591,12 @@
"value" : "Встреча начинается в %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Toplantı %@'de başlıyor"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26009,6 +29621,12 @@
"value" : "Память"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bellek"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26031,6 +29649,12 @@
"value" : "Частота памяти"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bellek Saati"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26053,6 +29677,12 @@
"value" : "Обзор памяти"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Belleğe Genel Bakış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26075,6 +29705,12 @@
"value" : "Нагрузка на память критическая; система может начать агрессивную очистку."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bellek baskısı kritik öneme sahiptir; sistem agresif bir şekilde temizlenebilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26097,6 +29733,12 @@
"value" : "Нагрузка на память повышена. Закройте приложения."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bellek basıncı arttı. Uygulamaları kapatmak yardımcı olabilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26131,6 +29773,12 @@
"value" : "Память"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bellek Kullanımı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26221,8 +29869,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Menubar icon"
+ "state" : "translated",
+ "value" : "Menü çubuğu simgesi"
}
},
"uk" : {
@@ -26262,6 +29910,12 @@
"value" : "Сообщения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mesajlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26270,6 +29924,10 @@
}
}
},
+ "Method source" : {
+ "comment" : "A link to the source code of Spotify's method for retrieving a user's Spotify Canvas.",
+ "isCommentAutoGenerated" : true
+ },
"Mic" : {
"comment" : "Name of a sneak action type that controls the microphone.",
"isCommentAutoGenerated" : true,
@@ -26286,6 +29944,12 @@
"value" : "Микрофон"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofon"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26377,8 +30041,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Mic %@"
+ "state" : "translated",
+ "value" : "Mikrofon %@"
}
},
"uk" : {
@@ -26417,6 +30081,12 @@
"value" : "Микрофон выключен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofonun sesi kapatıldı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26447,6 +30117,12 @@
"value" : "Микрофон включен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofonun sesi açıldı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26483,6 +30159,12 @@
"value" : "Микрофон"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofon"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26517,6 +30199,12 @@
"value" : "Микрофон активен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofon Aktif"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26525,6 +30213,23 @@
}
}
},
+ "Microphone Only" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Microphone Only"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yalnızca mikrofon"
+ }
+ }
+ }
+ },
"Microphone privacy indicator runs automatically whenever apps access audio." : {
"comment" : "A description of the microphone privacy indicator feature.",
"isCommentAutoGenerated" : true,
@@ -26547,6 +30252,12 @@
"value" : "Индикатор конфиденциальности микрофона включается автоматически, когда приложения используют аудио."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofon gizlilik göstergesi, uygulamalar sese her eriştiğinde otomatik olarak çalışır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26581,6 +30292,12 @@
"value" : "Статус микрофона"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Mikrofon Durumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26610,6 +30327,12 @@
"value" : "мин"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "dk."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26638,6 +30361,12 @@
"value" : "Минималистичный режим фокусируется на медиа и системных HUD, скрывая все лишние функции. Автоматически включает простые анимации."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Minimalist mod, temiz, odaklanmış bir deneyim için tüm ekstra özellikleri gizleyerek medya kontrollerine ve sistem HUD'lerine odaklanır. Daha basit animasyonları otomatik olarak etkinleştirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26767,6 +30496,12 @@
"value" : "Минуты"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "dakika"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -26876,7 +30611,14 @@
}
},
"Mirror Camera" : {
-
+ "localizations" : {
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayna Kamerası"
+ }
+ }
+ }
},
"Mirror macOS Clock timers" : {
"comment" : "A toggle that enables or disables the app's ability to mirror macOS Clock timers.",
@@ -26900,6 +30642,12 @@
"value" : "Повторять таймеры macOS"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayna macOS Saat zamanlayıcıları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27028,6 +30776,12 @@
"value" : "Дублирует переключатель настроек экрана блокировки для управления виджетом из этой вкладки."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı ayarları altında bulunan geçişi yansıtır, böylece zamanlayıcıya özel iş akışları sekmeleri değiştirmeden widget'ı etkinleştirebilir veya devre dışı bırakabilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27050,6 +30804,12 @@
"value" : "MM"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "DD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27078,6 +30838,12 @@
"value" : "Отслеживайте изменения буфера обмена и ведите историю копирования. Нажмите Cmd+Shift+V для быстрого доступа."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano değişikliklerini izleyin ve son kopyaların geçmişini saklayın. Pano geçmişine hızlı bir şekilde erişmek için Cmd+Shift+V tuşlarını kullanın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27108,6 +30874,12 @@
"value" : "Отслеживает папку загрузок на наличие файлов Chromium (.crdownload) и показывает процесс в Dynamic Island."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Chromium tarzı indirmeler (.crdownload dosyaları) için İndirilenler klasörünüzü izleyin ve indirmeler devam ederken Dynamic Island'de canlı bir etkinlik gösterin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27136,6 +30908,12 @@
"value" : "Режим мониторинга"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Davranışı İzleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27170,6 +30948,12 @@
"value" : "Статус мониторинга"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzleme Durumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27199,6 +30983,12 @@
"value" : "Мониторинг остановлен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzleme Durduruldu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27289,8 +31079,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "More"
+ "state" : "translated",
+ "value" : "Daha Fazla"
}
},
"uk" : {
@@ -27329,6 +31119,12 @@
"value" : "Переместить вниз"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aşağı Taşı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27359,6 +31155,12 @@
"value" : "Переместить вверх"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yukarı Taşı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27375,6 +31177,12 @@
"value" : "음악"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Müzik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27403,6 +31211,12 @@
"value" : "Вариант музыкальной панели"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Müzik paneli çeşidi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27519,6 +31333,12 @@
"value" : "음악 비주얼라이저"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Müzik Görselleştirici"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27655,6 +31475,12 @@
"value" : "Моя анимация"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Animasyonum"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27789,6 +31615,12 @@
"value" : "Навигация"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Navigasyon"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27879,8 +31711,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Need Restart"
+ "state" : "translated",
+ "value" : "Yeniden Başlatma Gerekiyor"
}
},
"uk" : {
@@ -27913,6 +31745,12 @@
"value" : "Сеть"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ağ"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27935,6 +31773,12 @@
"value" : "Сетевая активность"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ağ Etkinliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27969,6 +31813,12 @@
"value" : "Сеть"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ağ İndirme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -27991,6 +31841,12 @@
"value" : "Обзор сети"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ağa Genel Bakış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28025,6 +31881,12 @@
"value" : "Загрузка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ağ Yükleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28141,6 +32003,12 @@
"value" : "다음 트랙"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sonraki Parça"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28177,6 +32045,12 @@
"value" : "Активных интерфейсов пока не обнаружено."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Henüz etkin arayüz algılanmadı."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28201,6 +32075,12 @@
"value" : "Устройства AirPlay не найдены"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AirPlay cihazı bulunamadı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28225,6 +32105,12 @@
"value" : "Нет выходов AirPlay"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AirPlay çıkışı yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28255,6 +32141,12 @@
"value" : "Нет аудиовыходов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ses çıkışı mevcut değil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28289,6 +32181,12 @@
"value" : "Нет истории буфера обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano geçmişi yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28323,6 +32221,12 @@
"value" : "Цвета не найдены"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Bulunamadı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28357,6 +32261,12 @@
"value" : "Нет цветов, соответствующих «%@»"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "'%@' ile eşleşen renk yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28385,6 +32295,12 @@
"value" : "Цветов еще нет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Henüz Renk Yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28415,6 +32331,12 @@
"value" : "Копий еще нет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Henüz kopya yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28505,8 +32427,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "No custom animation available"
+ "state" : "translated",
+ "value" : "Özel animasyon yok"
}
},
"uk" : {
@@ -28606,8 +32528,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "No custom visualizer"
+ "state" : "translated",
+ "value" : "Özel görselleştirici yok"
}
},
"uk" : {
@@ -28634,6 +32556,12 @@
"value" : "기기를 찾을 수 없음"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hiçbir cihaz bulunamadı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28664,6 +32592,12 @@
"value" : "Нет событий"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinlik yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28754,8 +32688,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "No events today"
+ "state" : "translated",
+ "value" : "Bugün etkinlik yok"
}
},
"uk" : {
@@ -28793,6 +32727,12 @@
"value" : "Исключений нет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hariç tutulan yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28885,8 +32825,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "No extension installed"
+ "state" : "translated",
+ "value" : "Uzantı yüklü değil"
}
},
"uk" : {
@@ -28925,6 +32865,12 @@
"value" : "Расширений еще нет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Henüz uzantı yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28959,6 +32905,12 @@
"value" : "Нет избранного"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Favori yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -28989,6 +32941,12 @@
"value" : "Нет включенных графиков"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Grafik Etkin Değil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29017,6 +32975,12 @@
"value" : "Заметок еще нет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Henüz not yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29047,6 +33011,12 @@
"value" : "Нет настроенных пресетов. Добавьте пресет для его появления в окне таймера."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapılandırılmış ön ayar yok. Zamanlayıcı açılır penceresinde görünmesi için bir ön ayar ekleyin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29077,6 +33047,12 @@
"value" : "Данных о процессах пока нет. Оставьте меню открытым ненадолго."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Henüz işlem verisi yok. İstatistikleri bir süre açık tutun."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29111,6 +33087,12 @@
"value" : "Нет результатов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sonuç yok"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29145,6 +33127,12 @@
"value" : "Результаты не найдены"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sonuç bulunamadı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29175,6 +33163,12 @@
"value" : "Устройства хранения пока не обнаружены."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Henüz hiçbir depolama cihazı algılanmadı."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29265,8 +33259,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Non-notch display height"
+ "state" : "translated",
+ "value" : "Çentiksiz ekran yüksekliği"
}
},
"uk" : {
@@ -29297,6 +33291,12 @@
"value" : "Обычный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "normal"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29333,6 +33333,12 @@
"value" : "Неактивно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aktif değil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29357,6 +33363,12 @@
"value" : "Не заряжается"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şarj olmuyor"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29373,6 +33385,12 @@
"value" : "결정 안 됨"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Belirlenmedi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29481,6 +33499,23 @@
}
}
},
+ "Not Recording" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Not Recording"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayıt yok"
+ }
+ }
+ }
+ },
"Not Set" : {
"localizations" : {
"ko" : {
@@ -29501,6 +33536,12 @@
"value" : "Не задано"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayarlanmadı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29591,8 +33632,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Notch behavior"
+ "state" : "translated",
+ "value" : "Çentik davranışı"
}
},
"uk" : {
@@ -29691,8 +33732,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Notch display height"
+ "state" : "translated",
+ "value" : "Çentik ekran yüksekliği"
}
},
"uk" : {
@@ -29719,6 +33760,12 @@
"value" : "Функции выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentik Deneyimleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29850,6 +33897,12 @@
"value" : "Ширина выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentik Genişliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29880,6 +33933,12 @@
"value" : "Заметки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29904,6 +33963,12 @@
"value" : "Ничего не воспроизводится"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hiçbir şey oynatılmıyor"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29934,6 +33999,12 @@
"value" : "Ничего не воспроизводится"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Daha önce bildir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29959,6 +34030,12 @@
"value" : "Сейчас воспроизводится"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şimdi Oynanıyor"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -29980,6 +34057,12 @@
"state" : "translated",
"value" : "Количество строк в буфере прокрутки. Высокие значения используют больше памяти."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geri kaydırma arabelleğinde tutulan satır sayısı. Daha yüksek değerler daha fazla bellek kullanır."
+ }
}
}
},
@@ -29999,6 +34082,12 @@
"value" : "Выкл"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kapalı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30089,8 +34178,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "OK"
+ "state" : "translated",
+ "value" : "tamam"
}
},
"uk" : {
@@ -30124,6 +34213,12 @@
"value" : "Вкл"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Açık"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30132,6 +34227,9 @@
}
}
},
+ "On hold" : {
+ "comment" : "Plugged in but charging is paused by the system"
+ },
"One or more files failed to load" : {
"localizations" : {
"ar" : {
@@ -30214,8 +34312,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "One or more files failed to load"
+ "state" : "translated",
+ "value" : "Bir veya daha fazla dosya yüklenemedi"
}
},
"uk" : {
@@ -30252,6 +34350,12 @@
"state" : "translated",
"value" : "Только значок приложения"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yalnızca uygulama simgesi"
+ }
}
}
},
@@ -30276,6 +34380,12 @@
"state" : "translated",
"value" : "Применяется только если выбран материал «Матовое стекло»."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yalnızca Malzeme Buzlu Cam olarak ayarlandığında geçerlidir."
+ }
}
}
},
@@ -30300,6 +34410,12 @@
"state" : "translated",
"value" : "Доступно только если выбран материал «Матовое стекло»."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yalnızca Malzeme Buzlu Cam olarak ayarlandığında kullanılabilir."
+ }
}
}
},
@@ -30323,6 +34439,12 @@
"state" : "translated",
"value" : "Только значок загрузки"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yalnızca indir simgesi"
+ }
}
}
},
@@ -30345,9 +34467,19 @@
"state" : "translated",
"value" : "Открыть Google AI Studio"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Google AI Studio'yi açın"
+ }
}
}
},
+ "Open in Browser" : {
+ "comment" : "A button that opens Spotify in the user's browser.",
+ "isCommentAutoGenerated" : true
+ },
"Open in Calendar" : {
"comment" : "A button label that opens a calendar with the event details.",
"isCommentAutoGenerated" : true,
@@ -30369,6 +34501,12 @@
"state" : "translated",
"value" : "Открыть в приложении Календарь"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim'de aç"
+ }
}
}
},
@@ -30391,6 +34529,12 @@
"state" : "translated",
"value" : "Открыть настройки модели"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Model Ayarlarını Aç"
+ }
}
}
},
@@ -30407,6 +34551,12 @@
"state" : "translated",
"value" : "Открывать вырез при наведении"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fareyle üzerine gelindiğinde çentiği açın"
+ }
}
}
},
@@ -30417,6 +34567,12 @@
"state" : "translated",
"value" : "개인정보 보호 및 보안 열기"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gizlilik ve Güvenlik'i açın"
+ }
}
}
},
@@ -30433,9 +34589,19 @@
"state" : "translated",
"value" : "Открывать вкладку полки по умолчанию при добавлении файлов"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Öğeler eklenirse raf sekmesini varsayılan olarak aç"
+ }
}
}
},
+ "Open Spotify Web Player" : {
+ "comment" : "A link to open Spotify.",
+ "isCommentAutoGenerated" : true
+ },
"Open System Settings" : {
"localizations" : {
"ko" : {
@@ -30456,6 +34622,12 @@
"value" : "Открыть Системные Настройки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem Ayarlarını Aç"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30477,6 +34649,12 @@
"state" : "translated",
"value" : "Открывает прозрачное окно предпросмотра, которое дублирует конфигурацию виджетов."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geçerli kilit ekranı widget yapılandırmasını yansıtan sahte verileri içeren şeffaf bir önizleme penceresi açar."
+ }
}
}
},
@@ -30499,6 +34677,12 @@
"state" : "translated",
"value" : "Открывает прозрачное окно предварительного просмотра с имитаций данных, отражающих текущую конфигурацию виджета экрана блокировки."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dosya analizi ve konuşma için AI asistan panelini açar. Varsayılan Cmd+Shift+A'dır. Yalnızca ekran yardımcısı özelliği etkinleştirildiğinde çalışır."
+ }
}
}
},
@@ -30521,6 +34705,12 @@
"state" : "translated",
"value" : "Открывает панель буфера обмена. По умолчанию Cmd+Shift+V."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano geçmişi panelini açar. Varsayılan Cmd+Shift+V'dir (PC'deki Windows+V'ye benzer). Yalnızca pano özelliği etkinleştirildiğinde çalışır."
+ }
}
}
},
@@ -30543,6 +34733,12 @@
"state" : "translated",
"value" : "Открывает панель пипетки цветов. По умолчанию Cmd+Shift+P."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran rengi yakalama için renk seçici panelini açar. Varsayılan Cmd+Shift+P'dir. Yalnızca renk seçici özelliği etkinleştirildiğinde çalışır."
+ }
}
}
},
@@ -30559,6 +34755,12 @@
"state" : "translated",
"value" : "Открывает вкладку терминала в вырезе. По умолчанию Ctrl+`."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentikteki terminal sekmesini açar. Varsayılan Ctrl+`'dir. Yalnızca terminal özelliği etkinleştirildiğinde çalışır."
+ }
}
}
},
@@ -30575,6 +34777,12 @@
"state" : "translated",
"value" : "Клавиша Option как Meta"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Meta anahtarı olarak seçenek"
+ }
}
}
},
@@ -30591,6 +34799,12 @@
"state" : "translated",
"value" : "Option как Meta отправляет Esc+клавишу вместо спецсимволов."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Meta seçeneği, macOS özel karakterleri yerine Esc+tuşunu gönderir. Fare raporlaması, fare olaylarını vim veya tmux gibi terminal uygulamalarına iletir."
+ }
}
}
},
@@ -30608,6 +34822,12 @@
"value" : "Другое"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Diğer"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30637,6 +34857,12 @@
"state" : "translated",
"value" : "Использование Option в качестве Meta клавиши отправляет Esc+клавишу вместо специальных символов macOS. Отправка сообщений о действиях мыши передаёт события мыши приложениям терминала, например vim или tmux."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çıkış cihazları"
+ }
}
}
},
@@ -30661,6 +34887,12 @@
"state" : "translated",
"value" : "Громкость звука"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çıkış hacmi"
+ }
}
}
},
@@ -30685,6 +34917,12 @@
"state" : "translated",
"value" : "Сверхурочное время"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fazla mesai"
+ }
}
}
},
@@ -30705,6 +34943,12 @@
"value" : "Панель"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paneli"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30734,6 +34978,12 @@
"state" : "translated",
"value" : "Панель"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Panel modu, panoyu çentiğin yakınında kayan bir pencerede gösterir."
+ }
}
}
},
@@ -30756,6 +35006,12 @@
"state" : "translated",
"value" : "Режим \"Панель\" показывает пипетку в отдельном окне. Всплывающее окно – возле кнопки."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Panel modu, yüzen bir pencerede renk seçiciyi gösterir. Açılır pencere modu, renk seçici düğmesine bağlı bir açılır menü olarak renk seçiciyi gösterir."
+ }
}
}
},
@@ -30778,6 +35034,12 @@
"state" : "translated",
"value" : "Инструмент ИИ отображается в отдельном окне или контекстном меню кнопки."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Panel modu, asistanı çentiğin yakınındaki yüzen bir pencerede gösterir. Açılır pencere modu, asistanı AI düğmesine bağlı bir açılır menü olarak gösterir."
+ }
}
}
},
@@ -30788,9 +35050,19 @@
"state" : "translated",
"value" : "시차 효과 강도"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Paralaks Etkisi Yoğunluğu"
+ }
}
}
},
+ "Paste from Clipboard" : {
+ "comment" : "A button that copies the Spotify cookie from the clipboard.",
+ "isCommentAutoGenerated" : true
+ },
"Pause" : {
"localizations" : {
"ru" : {
@@ -30799,6 +35071,12 @@
"value" : "Пауза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Duraklat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30822,6 +35100,12 @@
"value" : "Пик"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zirve"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30852,6 +35136,12 @@
"value" : "В ожидании"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Beklemede"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30873,8 +35163,17 @@
"state" : "translated",
"value" : "Загрузка на ядро"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çekirdek Başına Kullanım"
+ }
}
}
+ },
+ "Percent change per key press. Fine step applies when holding Shift+Option." : {
+
},
"Percentage" : {
"localizations" : {
@@ -30896,6 +35195,12 @@
"value" : "Процент"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yüzde"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30925,6 +35230,12 @@
"state" : "translated",
"value" : "Статусы разрешений:"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzin Durumları:"
+ }
}
}
},
@@ -30948,6 +35259,12 @@
"value" : "Разрешения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzinler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -30976,6 +35293,12 @@
"value" : "Выбрать цвет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seç"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31004,6 +35327,12 @@
"state" : "translated",
"value" : "Настройте варианты liquid-glass для каждого виджета. Изменения дублируют вкладку экрана блокировки."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aşağıda widget başına sıvı cam çeşitlerini seçin. Değişiklikler Kilit Ekranı sekmesini yansıtır."
+ }
}
}
},
@@ -31026,6 +35355,12 @@
"state" : "translated",
"value" : "Выбран %@"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ seçildi"
+ }
}
}
},
@@ -31048,6 +35383,12 @@
"state" : "translated",
"value" : "Выбранный цвет"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Seçilen Renk"
+ }
}
}
},
@@ -31070,6 +35411,12 @@
"state" : "translated",
"value" : "Статус захвата"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Toplama Durumu"
+ }
}
}
},
@@ -31088,6 +35435,12 @@
"state" : "translated",
"value" : "Вырез в форме таблетки с закругленными углами, как в iPhone."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "iPhone'un Dynamic Island modeline benzer, köşeleri yuvarlatılmış hap şeklindeki ada"
+ }
}
}
},
@@ -31110,6 +35463,12 @@
"state" : "translated",
"value" : "Остров с закруглёнными углами, похожий на динамический остров iPhone"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geçmişteki öğeleri buraya kaydetmek için sabitleyin"
+ }
}
}
},
@@ -31132,6 +35491,12 @@
"state" : "translated",
"value" : "Закрепите элементы для добавления в избранное"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Favorilere eklemek için öğeleri sabitleyin"
+ }
}
}
},
@@ -31154,6 +35519,12 @@
"state" : "translated",
"value" : "Закрепите элементы для добавления в избранное"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Favorilere eklemek için öğeleri sabitleyin"
+ }
}
}
},
@@ -31176,6 +35547,12 @@
"state" : "translated",
"value" : "Закрепленные элементы"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sabitlenmiş Öğeler"
+ }
}
}
},
@@ -31186,6 +35563,12 @@
"state" : "translated",
"value" : "재생 / 일시정지"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oynat / Duraklat"
+ }
}
}
},
@@ -31202,6 +35585,12 @@
"state" : "translated",
"value" : "Проигрывать отклик при изменении громкости"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ses düzeyi değiştirildiğinde geri bildirim çal"
+ }
}
}
},
@@ -31218,6 +35607,12 @@
"state" : "translated",
"value" : "Проигрывать звуки при открытии/закрытии"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilitleme/kilit açma seslerini çal"
+ }
}
}
},
@@ -31234,6 +35629,12 @@
"state" : "translated",
"value" : "Воспроизводить звук при низком заряде батареи"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düşük pil uyarı sesini çal"
+ }
}
}
},
@@ -31258,6 +35659,12 @@
"state" : "translated",
"value" : "Воспроизводит звуковой отклик при нажатии аппаратных клавиш громкости."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Donanım ses seviyesi tuşlarına her bastığınızda, birlikte verilen geri bildirim klibini oynatır."
+ }
}
}
},
@@ -31280,6 +35687,12 @@
"state" : "translated",
"value" : "Настройте ваш API ключ для выбранного провайдера ИИ в настройках."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Lütfen model ayarlarında seçilen AI sağlayıcısı için API anahtarınızı yapılandırın."
+ }
}
}
},
@@ -31365,8 +35778,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Plugged In"
+ "state" : "translated",
+ "value" : "Takılı"
}
},
"uk" : {
@@ -31399,6 +35812,12 @@
"value" : "Всплывающее окно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Popover"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31428,6 +35847,12 @@
"state" : "translated",
"value" : "Всплывающее окно"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Açılır pencere modu, panoyu pano düğmesine eklenmiş bir açılır menü olarak gösterir."
+ }
}
}
},
@@ -31450,6 +35875,12 @@
"state" : "translated",
"value" : "Поведение отображения пипетки."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Açılır pencere modu, renk seçici düğmesine bağlı bir açılır menü olarak renk seçiciyi gösterir. Panel modu, yüzen bir pencerede renk seçiciyi gösterir."
+ }
}
}
},
@@ -31472,6 +35903,12 @@
"state" : "translated",
"value" : "Поведение отображения ИИ помощника."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Açılır pencere modu, asistanı AI düğmesine bağlı bir açılır menü olarak gösterir. Panel modu, asistanı çentiğin yakınındaki yüzen bir pencerede gösterir."
+ }
}
}
},
@@ -31501,6 +35938,12 @@
"value" : "Позиция"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pozisyon"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31531,6 +35974,12 @@
"value" : "Имя пресета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ön ayar adı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31561,6 +36010,12 @@
"value" : "Пресеты"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ön ayarlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31590,6 +36045,12 @@
"state" : "translated",
"value" : "Пресеты отображаются в окне таймера. Меняйте их порядок для изменения отображения."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ön ayarlar, yapılandırılmış ad, süre ve vurgu rengiyle birlikte zamanlayıcı açılır penceresinin içinde görünür. Görüntüleme sırasını değiştirmek için bunları yeniden sıralayın."
+ }
}
}
},
@@ -31620,6 +36081,12 @@
"state" : "translated",
"value" : "Давление"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Basınç"
+ }
}
}
},
@@ -31637,6 +36104,12 @@
"value" : "Нагрузка и Файлы подкачки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Basınç ve Değişim"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31658,6 +36131,12 @@
"state" : "translated",
"value" : "Предотвращает закрытие терминала при случайном отводе курсора от зоны выреза."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İmleç yanlışlıkla çentik alanından çıktığında terminalin kapanmasını önler."
+ }
}
}
},
@@ -31687,6 +36166,12 @@
"value" : "Предпросмотр"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Önizleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31708,6 +36193,12 @@
"state" : "translated",
"value" : "Предпросмотр виджетов экрана блокировки"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı widget'larını önizleyin"
+ }
}
}
},
@@ -31732,6 +36223,12 @@
"state" : "translated",
"value" : "Предпросмотр виджетов экрана блокировки"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yakınlaştırmayı Önizle"
+ }
}
}
},
@@ -31743,6 +36240,12 @@
"value" : "이전 트랙"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Önceki Parça"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31773,6 +36276,12 @@
"value" : "Индикаторы конфиденциальности"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gizlilik Göstergeleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31803,6 +36312,12 @@
"value" : "Политика конфиденциальности"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gizlilik Politikası"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31811,6 +36326,23 @@
}
}
},
+ "PRO" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "PRO"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "PRO"
+ }
+ }
+ }
+ },
"Productivity" : {
"comment" : "Title of a settings tab group related to productivity features.",
"isCommentAutoGenerated" : true,
@@ -31827,6 +36359,12 @@
"value" : "Продуктивность"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Verimlilik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31851,6 +36389,12 @@
"value" : "Прогресс"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İlerleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31880,6 +36424,12 @@
"value" : "Индикатор прогресса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İlerleme çubuğu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31908,6 +36458,12 @@
"value" : "Стиль индикатора"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İlerleme stili"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -31998,8 +36554,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Progressbar style"
+ "state" : "translated",
+ "value" : "İlerleme çubuğu stili"
}
},
"uk" : {
@@ -32032,6 +36588,12 @@
"value" : "Провайдер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "sağlayıcı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32066,6 +36628,12 @@
"value" : "Быстрые действия"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hızlı Eylemler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32084,6 +36652,12 @@
"value" : "Быстрая отправка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hızlı Paylaşım"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32115,6 +36689,12 @@
"value" : "Быстрая отправка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hızlı Paylaşım Hizmeti"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32133,6 +36713,12 @@
"value" : "Quick Share 설정"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hızlı Paylaşım ayarları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32368,6 +36954,12 @@
"value" : "Закрыть boring.notch"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "boring.notch'tan çık"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32391,6 +36983,12 @@
"state" : "translated",
"value" : "R "
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "O "
+ }
}
}
},
@@ -32416,6 +37014,12 @@
"value" : "Ч "
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yeniden yetkilendir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32452,6 +37056,12 @@
"value" : "Чтение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oku"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32486,6 +37096,12 @@
"value" : "Готово"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hazır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32520,6 +37136,12 @@
"value" : "Режим размышления"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Muhakeme Modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32550,6 +37172,12 @@
"value" : "Недавняя активность (последние 5 мин)"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Son Etkinlik (son 5 dakika)"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32584,6 +37212,12 @@
"value" : "Недавние цвета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Son Renkler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32600,6 +37234,12 @@
"value" : "활성화된 탭 수에 따라 권장 최소 너비가 자동으로 조절됩니다."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Önerilen minimum genişlik, etkin sekme sayısına göre otomatik olarak ayarlanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32634,6 +37274,12 @@
"value" : "Запись"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayıt"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32642,6 +37288,23 @@
}
}
},
+ "Recording Active" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Recording Active"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayıt etkin"
+ }
+ }
+ }
+ },
"Recording Detected" : {
"comment" : "A text description of a red dot displayed in the Atoll header when screen recording is active.",
"isCommentAutoGenerated" : true,
@@ -32670,6 +37333,12 @@
"value" : "Обнаружена запись"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayıt Algılandı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32704,6 +37373,12 @@
"value" : "Статус записи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayıt Durumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32728,6 +37403,12 @@
"value" : "Обновление обнаружения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yenileme tespiti"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32744,6 +37425,12 @@
"value" : "기기 새로 고침"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cihazları yenile"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32762,6 +37449,12 @@
"value" : "거절됨"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Reddedildi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -32852,8 +37545,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Release name"
+ "state" : "translated",
+ "value" : "Sürüm adı"
}
},
"uk" : {
@@ -32952,8 +37645,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Remember last tab"
+ "state" : "translated",
+ "value" : "Son sekmeyi hatırla"
}
},
"uk" : {
@@ -32992,6 +37685,12 @@
"value" : "Live Activity напоминаний"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hatırlatma Canlı Etkinliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33014,6 +37713,12 @@
"value" : "Виджет напоминаний"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hatırlatma Widget'ı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33048,6 +37753,12 @@
"value" : "Виджет напоминаний"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hatırlatıcılar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33078,6 +37789,12 @@
"value" : "Удалить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kaldır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33108,6 +37825,12 @@
"value" : "Удалить %@ из списка авторизованных расширений?"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@ yetkili dahili numaralar listesinden kaldırılsın mı? Bu, bu uygulamadaki tüm aktif canlı etkinlikleri, kilit ekranı widget'larını ve çentik deneyimlerini devre dışı bırakacaktır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33136,6 +37859,12 @@
"value" : "Удалить расширение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantıyı Kaldır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33164,6 +37893,12 @@
"value" : "Удалять с полки после перетаскивания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sürükledikten sonra raftan çıkarın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33186,6 +37921,12 @@
"value" : "Удалить выбранное"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Seçileni kaldır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33208,6 +37949,12 @@
"value" : "Рендер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İşleme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33230,6 +37977,12 @@
"value" : "Рендерер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oluşturucu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33246,6 +37999,12 @@
"value" : "반복"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tekrarla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33253,6 +38012,9 @@
}
}
}
+ },
+ "Replaces the artwork tile with the live canvas when the current app provides one, and reuses that moving canvas for the surrounding lighting effect." : {
+
},
"Request Access" : {
"localizations" : {
@@ -33280,6 +38042,12 @@
"value" : "Удалить выбранное"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Erişim Talebi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33296,6 +38064,12 @@
"value" : "전체 디스크 접근 권한 요청"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tam Disk Erişimi İste"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33320,6 +38094,12 @@
"value" : "Требуется сторонний клиент с включенным API плагином."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "API eklentisinin etkin olduğu bir üçüncü taraf istemci gerektirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33348,6 +38128,12 @@
"value" : "Требуется сторонний клиент с включенным плагином API."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dynamic Island'nin donanım ses düzeyi anahtarlarını ele geçirebilmesi için Erişilebilirlik izni gerektirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33382,6 +38168,12 @@
"value" : "Сбросить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33416,6 +38208,12 @@
"value" : "Сбросить все"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tümünü Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33440,6 +38238,12 @@
"value" : "Сбросить контекст"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İçeriği Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33470,6 +38274,12 @@
"value" : "Сбросить Live Activities"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Canlı Etkinlikleri Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33500,6 +38310,12 @@
"value" : "Сбросить виджеты экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit Ekranı Widget'larını Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33516,6 +38332,12 @@
"value" : "미디어 너비 초기화"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya Genişliğini Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33532,6 +38354,12 @@
"value" : "음악 초기화"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Müziği Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33562,6 +38390,12 @@
"value" : "Сбросить UI выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentik Deneyimlerini Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33592,6 +38426,12 @@
"value" : "Сбросить лимиты API"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hız Sınırlarını Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33600,6 +38440,10 @@
}
}
},
+ "Reset Session" : {
+ "comment" : "A button that clears the Spotify session.",
+ "isCommentAutoGenerated" : true
+ },
"Reset Timer" : {
"localizations" : {
"ko" : {
@@ -33608,6 +38452,12 @@
"value" : "타이머 초기화"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcıyı Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33624,6 +38474,12 @@
"value" : "타이머 너비 초기화"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı Genişliğini Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33658,6 +38514,12 @@
"value" : "По умолчанию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varsayılana Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33688,6 +38550,12 @@
"value" : "По умолчанию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varsayılanlara sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33704,6 +38572,12 @@
"value" : "날씨 초기화"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava Durumunu Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33734,6 +38608,12 @@
"value" : "Сбросить ширину"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genişliği Sıfırla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33869,6 +38749,12 @@
"value" : "Перезапустить Atoll"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Atoll'u yeniden başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33893,6 +38779,12 @@
"value" : "Перезапустить shell"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kabuğu yeniden başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33917,6 +38809,12 @@
"value" : "Перезапустить Shell"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kabuğu yeniden başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33941,6 +38839,12 @@
"value" : "Перезапускает процесс shell. Несохраненные данные в терминале будут потеряны."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kabuk işlemini yeniden başlatır. Terminaldeki kaydedilmemiş çalışmalar kaybolacaktır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -33971,6 +38875,12 @@
"value" : "Перезапускает процессы оболочки. Любые несохраненные данные в терминале будут потеряны."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geri yükle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34001,6 +38911,12 @@
"value" : "Восстановить пресеты таймера по умолчанию?"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varsayılan zamanlayıcı ön ayarları geri yüklensin mi?"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34031,6 +38947,12 @@
"value" : "Восстановить настройки по умолчанию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varsayılanları Geri Yükle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34047,6 +38969,12 @@
"value" : "제한됨"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kısıtlı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34081,6 +39009,12 @@
"value" : "Возобновить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Devam et"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34103,6 +39037,12 @@
"value" : "Инвертировать жесты открытия/закрытия"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Açma/kapama kaydırma hareketlerini ters çevirme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34125,6 +39065,12 @@
"value" : "Инвертировать жесты смахивания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ters kaydırma hareketleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34155,6 +39101,12 @@
"value" : "Отозвать доступ"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Erişimi İptal Et"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34171,6 +39123,12 @@
"value" : "10초 되감기"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "10 saniyeyi geri sar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34195,6 +39153,12 @@
"value" : "Форматированный текст"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zengin Metin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34223,6 +39187,12 @@
"value" : "Форматированный текст"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sağ"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34230,6 +39200,9 @@
}
}
}
+ },
+ "Right-click the album art on the lock screen to set it as the wallpaper. Right-click again or click the background to restore the original wallpaper. If a canvas is available, Atoll can also keep the same album art + player layout on top of the live canvas." : {
+
},
"Ring" : {
"comment" : "The name of the timer progress style that shows a ring shape.",
@@ -34248,6 +39221,12 @@
"value" : "Кольцо"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yüzük"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34277,6 +39256,12 @@
"value" : "Кольцо"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Rotasyon:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34368,8 +39353,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Running"
+ "state" : "translated",
+ "value" : "Koşma"
}
},
"uk" : {
@@ -34385,6 +39370,9 @@
}
}
}
+ },
+ "Runs the real notch animation on the current target display. If an external screen is using Dynamic Island mode, the battery HUD is sent there first." : {
+
},
"Sampling can continue while the notch is closed when the timeout is disabled." : {
"localizations" : {
@@ -34406,6 +39394,12 @@
"value" : "Сэмплинг может продолжаться при закрытом вырезе, если отключен таймаут."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zaman aşımı devre dışı bırakıldığında çentik kapalıyken örneklemeye devam edebilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34440,6 +39434,12 @@
"value" : "Сохранить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kaydet"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34474,6 +39474,12 @@
"value" : "Сохранить изменения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Değişiklikleri Kaydet"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34508,6 +39514,12 @@
"value" : "Сохранить конфигурацию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yapılandırmayı Kaydet"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34542,6 +39554,12 @@
"value" : "Уменьшить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ölçeği Azalt"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34576,6 +39594,12 @@
"value" : "Увеличить 2x"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "2 Kat Artırın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34605,6 +39629,12 @@
"value" : "Масштаб:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ölçek:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34623,6 +39653,12 @@
"value" : "다시 검색"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tekrar Tara"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34641,6 +39677,12 @@
"value" : "기기 검색 중..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cihazlar taranıyor..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34669,6 +39711,12 @@
"value" : "Экранный помощник"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Asistanı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34697,6 +39745,12 @@
"value" : "Функция экранного помощника отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Yardımcısı özelliği devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34731,6 +39785,12 @@
"value" : "Экранный помощник:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Yardımcısı:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34759,6 +39819,12 @@
"value" : "Отступы экрана: %lld px"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Dolgusu: %lldpx"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34787,6 +39853,12 @@
"value" : "Запись экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Kaydı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34821,6 +39893,12 @@
"value" : "Параметры снимка экрана"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran görüntüsü seçenekleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34855,6 +39933,12 @@
"value" : "Тип снимка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran Görüntüsü Türü"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34885,6 +39969,12 @@
"value" : "Прокрутка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kaydırma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34907,6 +39997,12 @@
"value" : "Прокручивать имя устройства в HUD"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "HUD'de cihaz adını kaydırın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34929,6 +40025,12 @@
"value" : "История прокрутки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geri kaydırma"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34951,6 +40053,12 @@
"value" : "Строк истории прокрутки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geri kaydırma çizgileri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -34985,6 +40093,12 @@
"value" : "Строк обратной прокрутки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Panoda ara..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35019,6 +40133,12 @@
"value" : "Искать цвета..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renkleri ara..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35049,6 +40169,12 @@
"value" : "Искать расширения..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantıları ara..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35079,6 +40205,12 @@
"value" : "Искать заметки..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Notlarda ara..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35109,6 +40241,12 @@
"value" : "Поиск настроек"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Arama Ayarları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35143,6 +40281,12 @@
"value" : "Поиск..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ara..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35178,6 +40322,12 @@
"value" : "секунды"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Saniyeler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35206,6 +40356,12 @@
"value" : "Секунды"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Saniyeler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35240,6 +40396,12 @@
"value" : "Сегментированный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bölümlere ayrılmış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35268,6 +40430,12 @@
"value" : "Выберите цвет для просмотра деталей"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayrıntıları görüntülemek için bir renk seçin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35298,6 +40466,12 @@
"value" : "Выберите звук для окончания таймера. Поддерживаются MP3, M4A, WAV и AIFF."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı sona erdiğinde çalmak için özel bir ses seçin. Desteklenen formatlar arasında MP3, M4A, WAV ve AIFF bulunur."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35332,6 +40506,12 @@
"value" : "Выбрать календари"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvimleri Seçin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35350,6 +40530,12 @@
"value" : "기기 선택"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cihaz Seç"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35380,6 +40566,12 @@
"value" : "Выберите один или несколько профилей для настройки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Deneyiminizi özelleştirmek için bir veya daha fazla profil seçin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35470,8 +40662,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Select the music source you want to use. You can change this later in the app settings."
+ "state" : "translated",
+ "value" : "Kullanmak istediğiniz müzik kaynağını seçin. Bunu daha sonra uygulama ayarlarından değiştirebilirsiniz."
}
},
"uk" : {
@@ -35570,8 +40762,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "selected"
+ "state" : "translated",
+ "value" : "seçilmiş"
}
},
"uk" : {
@@ -35616,6 +40808,12 @@
"value" : "Выбрано"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Seçildi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35706,8 +40904,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Selected animation"
+ "state" : "translated",
+ "value" : "Seçilen animasyon"
}
},
"uk" : {
@@ -35745,6 +40943,12 @@
"value" : "Чувствительность"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hassasiyet"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35770,6 +40974,12 @@
"value" : "Отдельная вкладка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayrı Sekme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35800,6 +41010,12 @@
"value" : "Отдельная вкладка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayrı Sekme modu, Kopyalanan Öğeleri ve Notları tek bir görünümde birleştirir. Her ikisi de etkinse, sağda Notlar ve solda Pano görünür."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35842,6 +41058,12 @@
"value" : "Сессия · ↓ %1$@ · ↑ %2$@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oturum · ↓ %@ · ↑ %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35864,6 +41086,12 @@
"value" : "В среднем за сеанс"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oturum Ortalaması"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35886,6 +41114,12 @@
"value" : "Итого за сеанс"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oturum Toplamları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -35920,6 +41154,12 @@
"value" : "Установить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayarla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36042,6 +41282,12 @@
"value" : "Значок настроек в вырезе"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentikteki ayarlar simgesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36072,6 +41318,12 @@
"value" : "Настройки…"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ayarlar..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36162,8 +41414,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Shelf"
+ "state" : "translated",
+ "value" : "Raf"
}
},
"uk" : {
@@ -36194,6 +41446,12 @@
"value" : "Shell"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kabuk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36218,6 +41476,12 @@
"value" : "Путь к Shell"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kabuk yolu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36329,12 +41593,24 @@
},
"Show \"Change Media Output\" control" : {
"localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Show \"Change Media Output\" control"
+ }
+ },
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "“미디어 출력 변경” 제어기 표시"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "\"Medya Çıkışını Değiştir\" kontrolünü göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36357,6 +41633,12 @@
"value" : "Показывать все форматы цвета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tüm Renk Formatlarını Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36379,6 +41661,12 @@
"value" : "Показывать виджет AQI"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AQI widget'ını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36469,8 +41757,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Show battery indicator"
+ "state" : "translated",
+ "value" : "Pil göstergesini göster"
}
},
"uk" : {
@@ -36501,6 +41789,12 @@
"value" : "Показывать процент батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil yüzdesini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36523,6 +41817,12 @@
"value" : "Показывать процент в HUD"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pil yüzdesi metnini HUD'de göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36545,6 +41845,12 @@
"value" : "Показывать заряд Bluetooth"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bluetooth pilini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36567,6 +41873,12 @@
"value" : "Показывать подключения Bluetooth устройств"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bluetooth cihaz bağlantılarını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36589,6 +41901,12 @@
"value" : "Показывать календарь"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvimi göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36611,6 +41929,12 @@
"value" : "Показывать индикатор Caps Lock"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Büyük Harf Kilidi Göstergesini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36633,6 +41957,12 @@
"value" : "Показывать метку Caps Lock"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Caps Lock etiketini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36655,6 +41985,12 @@
"value" : "Показывать количество символов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Karakter Sayısını Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36690,6 +42026,12 @@
"value" : "Показать индикатор зарядки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şarj göstergesini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36712,6 +42054,12 @@
"value" : "Показывать процент зарядки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şarj yüzdesini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36734,6 +42082,12 @@
"value" : "Показывать статус зарядки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Şarj durumunu göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36756,6 +42110,12 @@
"value" : "Показывать значок буфера обмена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pano Simgesini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36790,6 +42150,12 @@
"value" : "Показывать историю цветов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Geçmişini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36812,6 +42178,12 @@
"value" : "Показывать значок пипетки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçici Simgesini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36840,6 +42212,12 @@
"value" : "Показывать панель пипетки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçici Panelini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -36931,8 +42309,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Show cool face animation while inactivity"
+ "state" : "translated",
+ "value" : "Hareketsizken havalı yüz animasyonu göster"
}
},
"uk" : {
@@ -36969,6 +42347,12 @@
"value" : "Показывать обратный отсчет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geri sayımı göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37010,6 +42394,12 @@
"value" : "Показать прогресс загрузки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İndirme ilerlemesini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37032,6 +42422,12 @@
"value" : "Показывать событие после начала"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinliği başladıktan sonra göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37054,6 +42450,12 @@
"value" : "Показывать событие на протяжении всего времени"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinliği tüm süre boyunca göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37076,6 +42478,12 @@
"value" : "Показывать события всех календарей"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tüm takvimlerdeki etkinlikleri göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37098,6 +42506,12 @@
"value" : "Показывать события в ближайшие"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sonraki etkinlikteki etkinlikleri göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37122,6 +42536,12 @@
"value" : "Показывать вкладки расширения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uzantı sekmelerini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37138,6 +42558,12 @@
"value" : "플로팅 미디어 제어기 표시"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayan medya kontrollerini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37166,6 +42592,12 @@
"value" : "Показать вкладки расширений"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Değişken duraklatma/durdurma kontrollerini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37188,6 +42620,12 @@
"value" : "Показывать Фокус как краткое уведомление"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odağı kısa bir tost olarak göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37210,6 +42648,12 @@
"value" : "Показывать индикатор Фокуса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odak Göstergesini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37232,6 +42676,12 @@
"value" : "Показывать метку Фокуса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odak Etiketini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37254,6 +42704,12 @@
"value" : "Показывать виджет Фокуса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Odak widget'ını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37284,6 +42740,12 @@
"value" : "Показывать полные названия событий"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinlik başlıklarının tamamını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37291,6 +42753,9 @@
}
}
}
+ },
+ "Show live canvas in Dynamic Island" : {
+
},
"Show location label" : {
"localizations" : {
@@ -37306,6 +42771,12 @@
"value" : "Показывать локацию"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Konum etiketini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37328,6 +42799,12 @@
"value" : "Показывать медиа-панель на экране блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı medya panelini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37350,6 +42827,12 @@
"value" : "Показывать напоминания на экране блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı hatırlatıcısını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37372,6 +42855,12 @@
"value" : "Показывать таймер экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı zamanlayıcısını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37394,6 +42883,12 @@
"value" : "Показывать виджет таймера экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı zamanlayıcı widget'ını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37416,6 +42911,12 @@
"value" : "Показывать погоду экрана блокировки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kilit ekranı hava durumunu göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37438,6 +42939,12 @@
"value" : "Показывать иконку медиаплеера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya uygulaması simgesini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37460,6 +42967,12 @@
"value" : "Показывать элементы управления медиа в Dynamic Island"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya kontrollerini Dynamic Island'de göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37476,6 +42989,12 @@
"value" : "AirPlay 및 출력 기기 통합 표시"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Birleştirilmiş AirPlay ve çıkış cihazlarını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37498,6 +43017,12 @@
"value" : "Показывать следующее событие календаря"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sonraki takvim etkinliğini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37588,8 +43113,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Show on a specific display"
+ "state" : "translated",
+ "value" : "Belirli bir ekranda göster"
}
},
"uk" : {
@@ -37688,8 +43213,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Show on all displays"
+ "state" : "translated",
+ "value" : "Tüm ekranlarda göster"
}
},
"uk" : {
@@ -37720,6 +43245,12 @@
"value" : "Показывать рамки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Panel kenarlığını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37748,6 +43279,12 @@
"value" : "Показывать проценты"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yüzdeyi Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37770,6 +43307,12 @@
"value" : "Показывать проценты рядом с индикаторами"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İlerleme çubuklarının yanında yüzdeleri göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37792,6 +43335,12 @@
"value" : "Показывать иконки статуса питания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Güç durumu simgelerini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37814,6 +43363,12 @@
"value" : "Показывать уведомления статуса питания"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Güç durumu bildirimlerini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37844,6 +43399,12 @@
"value" : "Показывать список пресетов на вкладке таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı sekmesinde ön ayar listesini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37872,6 +43433,12 @@
"value" : "Показывать прогресс"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İlerlemeyi göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37894,6 +43461,12 @@
"value" : "Показывать индикатор записи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kayıt Göstergesini Göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -37986,8 +43559,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Show shuffle and repeat buttons"
+ "state" : "translated",
+ "value" : "Karıştır ve tekrarla düğmelerini göster"
}
},
"uk" : {
@@ -38026,6 +43599,12 @@
"value" : "Быстрый просмотр при изменении воспроизведения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Oynatma değişikliklerine kısa bakış göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38033,9 +43612,6 @@
}
}
}
- },
- "Show song title and artist on non-notch displays" : {
-
},
"Show start time after event begins" : {
"localizations" : {
@@ -38051,6 +43627,12 @@
"value" : "Продолжать показывать время начала"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinlik başladıktan sonraki başlangıç saatini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38073,6 +43655,12 @@
"value" : "Показывать время восхода солнца"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Güneşin doğuş saatini göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38103,6 +43691,12 @@
"value" : "Отображать имя Фокуса в индикаторе."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Göstergenin içinde etkin Odak adını gösterin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38125,6 +43719,12 @@
"value" : "Показывать оставшееся время"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kalan süreyi göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38153,6 +43753,12 @@
"value" : "Показывать название таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı adını göster"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38181,6 +43787,12 @@
"value" : "Показывает зеленую иконку камеры и желтую иконку микрофона во время их использования."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanımdayken yeşil kamera simgesi ve sarı mikrofon simgesi gösterir. Olay odaklı CoreAudio ve CoreMediaIO API'lerini kullanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38206,6 +43818,12 @@
"value" : "Экранный помощник открывается как всплывающее окно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Ekran yardımcısını AI düğmesine eklenen bir açılır menü olarak gösterir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38231,6 +43849,12 @@
"value" : "Экранный помощник открывается как плавающая панель возле выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentiğin yakınındaki kayan bir panelde ekran yardımcısını gösterir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38265,6 +43889,12 @@
"value" : "Показывает экранный помощник во всплывающей панели рядом с вырезом"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Nihai cevaptan önce modelin akıl yürütme sürecini gösterir"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38293,6 +43923,12 @@
"value" : "Отображает системный таймер в вырезе. Требуется разрешение к функциям Универсального доступа."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem Saati zamanlayıcısını mevcut olduğunda çentikte gösterir. Durum öğesini okumak için Erişilebilirlik izni gerektirir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38309,6 +43945,12 @@
"value" : "확장 뷰는 전체 너비로 유지하면서 잠금 화면 미디어 패널을 축소합니다."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genişletilmiş görünümü tam genişlikte korurken kilit ekranı medya panelini küçültür."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38325,6 +43967,12 @@
"value" : "임의 재생"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Karıştır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38333,6 +43981,14 @@
}
}
},
+ "Sign in to Spotify to capture the `sp_dc` cookie automatically, or paste it in below." : {
+ "comment" : "A description of how to sign in to Spotify to get the `sp_dc` cookie.",
+ "isCommentAutoGenerated" : true
+ },
+ "Sign in with Spotify" : {
+ "comment" : "A button that opens a sheet for signing in with Spotify.",
+ "isCommentAutoGenerated" : true
+ },
"Simple and minimal interface with just the essentials for everyday tasks." : {
"localizations" : {
"ko" : {
@@ -38347,6 +44003,12 @@
"value" : "Простой минималистичный интерфейс только с самыми необходимыми функциями."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Günlük görevler için yalnızca temel öğeleri içeren basit ve minimal arayüz."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38376,6 +44038,12 @@
"value" : "Простой и минималистичный интерфейс с самым необходимым для повседневных задач."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Boyut ve Ölçek"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38404,6 +44072,12 @@
"value" : "Размер: %lld px"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Boyut: %lldpx"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38434,6 +44108,12 @@
"value" : "Скип-кнопки"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düğmeleri atla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38459,6 +44139,12 @@
"value" : "Промотать вперед/назад на 10 секунд"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "On saniye ileri veya geri atla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38549,8 +44235,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Slider color"
+ "state" : "translated",
+ "value" : "Kaydırıcı rengi"
}
},
"uk" : {
@@ -38581,6 +44267,12 @@
"value" : "Плавные переходы цвета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pürüzsüz renk geçişleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38611,6 +44303,12 @@
"value" : "Плавный переход от зеленого (0-60%) к желтому (60-85%) и красному (85-100%)."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pürüzsüz geçişler Yeşil (%0–60), Sarı (%60–85) ve Kırmızıyı (%85–100) tüm dolgu boyunca harmanlar."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38639,6 +44337,12 @@
"value" : "Плавный переход от зеленого (0-60%) к желтому (60-85%) и красному (85-100%)."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yumuşak geçişler, tüm dolgu boyunca Yeşil (%0-60), Sarı (%60-85) ve Kırmızıyı (%85-100) harmanlar. Degrade davranışını Kontroller › Dynamic Island'den ayarlayın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38669,6 +44373,12 @@
"value" : "Длительность быстрого просмотра"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kısa bakış süresi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -38759,8 +44469,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Sneak Peek shows the media title and artist under the notch for a few seconds."
+ "state" : "translated",
+ "value" : "Sneak Peek, medya başlığını ve sanatçıyı birkaç saniye boyunca çentiğin altında gösterir."
}
},
"uk" : {
@@ -38859,8 +44569,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Sneak Peek Style"
+ "state" : "translated",
+ "value" : "Sneak Peek Tarzı"
}
},
"uk" : {
@@ -38900,6 +44610,12 @@
"value" : "Стиль быстрого просмотра зафиксирован в минималистичном режиме"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sneak peek stili minimalist modda Standart'a kilitlendi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39029,6 +44745,12 @@
"value" : "Сплошной цвет"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Düz renk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39037,6 +44759,10 @@
}
}
},
+ "sp_dc cookie" : {
+ "comment" : "A label for the Spotify `sp_dc` cookie.",
+ "isCommentAutoGenerated" : true
+ },
"Speed" : {
"localizations" : {
"ar" : {
@@ -39119,8 +44845,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Speed"
+ "state" : "translated",
+ "value" : "Hız"
}
},
"uk" : {
@@ -39158,6 +44884,12 @@
"value" : "Скорость:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hız:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39183,6 +44915,12 @@
"value" : "Spotify"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Spotify"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39191,6 +44929,14 @@
}
}
},
+ "Spotify Canvas Session" : {
+ "comment" : "A section that allows the user to configure Spotify's session.",
+ "isCommentAutoGenerated" : true
+ },
+ "Spotify often blocks in-app logins. Sign in via your normal browser, then paste sp_dc in Settings." : {
+ "comment" : "A help message for the Spotify login sheet.",
+ "isCommentAutoGenerated" : true
+ },
"Square" : {
"localizations" : {
"ar" : {
@@ -39273,8 +45019,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Square"
+ "state" : "translated",
+ "value" : "Kare"
}
},
"uk" : {
@@ -39305,6 +45051,12 @@
"value" : "SS"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "SN"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39327,6 +45079,12 @@
"value" : "Стандартный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Standart"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39351,6 +45109,12 @@
"value" : "Стандартный вырез"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Standart Çentik"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39373,6 +45137,12 @@
"value" : "Элементы управления медиа в вырезе скрыты, пока этот переключатель выключен."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu geçiş kapalıyken standart çentikli medya kontrolleri gizlenir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39395,6 +45165,12 @@
"value" : "Элементы управления медиа в вырезе скрыты. Включите их выше для восстановления."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Standart çentikli medya kontrolleri gizlidir. Bunları geri yüklemek için yukarıdaki geçişi yeniden etkinleştirin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39420,6 +45196,12 @@
"value" : "Стандартные кнопки предыдущий/следующий трек"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Standart önceki/sonraki parça kontrolleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39454,6 +45236,12 @@
"value" : "Стандартные элементы управления предыдущим/следующим треком"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39488,6 +45276,12 @@
"value" : "Начните беседу"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sohbet geçmişinizi görmek için burada bir sohbet başlatın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39522,6 +45316,12 @@
"value" : "Начать выбор цвета"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renk Seçimini Başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39552,6 +45352,12 @@
"value" : "Запустить настраиваемый таймер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Özel Zamanlayıcıyı Başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39586,6 +45392,12 @@
"value" : "Запустить демо-таймер:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Demo Zamanlayıcısını Başlat:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39620,6 +45432,12 @@
"value" : "Начать мониторинг"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzlemeye Başla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39648,6 +45466,12 @@
"value" : "Начните выбирать цвета, чтобы создать историю"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Tarihinizi oluşturmak için renkleri seçmeye başlayın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39682,6 +45506,12 @@
"value" : "Начать запись"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kaydı başlat"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39712,6 +45542,12 @@
"value" : "Начните печатать..."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yazmaya başlayın..."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39740,6 +45576,12 @@
"value" : "Запускает демо-таймер на 5 минут для тестирования."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı canlı etkinlik özelliğini test etmek için 5 dakikalık bir demo zamanlayıcısı başlatır. Yalnızca zamanlayıcı özelliği etkinleştirildiğinde çalışır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39774,6 +45616,12 @@
"value" : "Статистика"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İstatistikler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39808,6 +45656,12 @@
"value" : "Статистика отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İstatistikler Devre Dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39837,6 +45691,12 @@
"value" : "Функция статистики отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İstatistik Özelliği Devre Dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39866,6 +45726,12 @@
"value" : "Функция статистики отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İstatistik özelliği devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39901,6 +45767,12 @@
"value" : "Панель статистики отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İstatistik paneli devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39936,6 +45808,12 @@
"value" : "Панель статистики:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İstatistik Paneli:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39964,6 +45842,12 @@
"value" : "Интервал обновления статистики"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İstatistik güncelleme aralığı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -39993,6 +45877,12 @@
"value" : "Активно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Aktif"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40017,6 +45907,12 @@
"value" : "Статус"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Durum"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40051,6 +45947,12 @@
"value" : "Статус и действия"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Durum ve Eylemler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40069,6 +45971,12 @@
"value" : "Статус:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Durum:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40092,6 +46000,12 @@
"value" : "Организуйте работу с календарем, таймером и мониторингом батареи."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim, zamanlayıcı ve pil izleme özellikleriyle düzenli kalın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40099,6 +46013,9 @@
}
}
}
+ },
+ "Step size" : {
+
},
"Stop" : {
"localizations" : {
@@ -40126,6 +46043,12 @@
"value" : "Держите дела под контролем с календарём, таймером и мониторингом батареи."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Durdur"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40160,6 +46083,12 @@
"value" : "Остановить мониторинг"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "İzlemeyi Durdur"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40182,6 +46111,12 @@
"value" : "Останавливать мониторинг при закрытии выреза"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentiği kapattıktan sonra izlemeyi durdurun"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40216,6 +46151,12 @@
"value" : "Остановить запись"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kaydı durdur"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40306,8 +46247,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Stopped"
+ "state" : "translated",
+ "value" : "Durduruldu"
}
},
"uk" : {
@@ -40338,6 +46279,12 @@
"value" : "Устройства хранения"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Depolama Cihazları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40362,6 +46309,12 @@
"value" : "Студент"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Öğrenci"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40390,6 +46343,12 @@
"value" : "Студент"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Stil"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40420,6 +46379,12 @@
"value" : "Восход в %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%@'de Gündoğumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40555,6 +46520,12 @@
"value" : "Поддерживает режим размышления"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Akıl yürütme modunu destekler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40579,6 +46550,12 @@
"value" : "Файл подкачки (Swap)"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takas"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40616,6 +46593,12 @@
"value" : "Swap отключен в системе."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu sistemde takas devre dışıdır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40652,6 +46635,12 @@
"value" : "Система"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40774,6 +46763,12 @@
"value" : "Нагрузка на системную память в норме."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem belleği basıncı nominaldir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40796,6 +46791,12 @@
"value" : "Системный моноширинный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem Tek Aralıklı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40825,6 +46826,12 @@
"value" : "Монитор производительности системы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sistem Performansı Monitörü"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40849,6 +46856,12 @@
"value" : "Вкладка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sekme"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40883,6 +46896,12 @@
"value" : "Вкладка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Alan ekran görüntüsünü al"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40905,6 +46924,12 @@
"value" : "Температура"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sıcaklık"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40933,6 +46958,12 @@
"value" : "Единицы температуры"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Sıcaklık birimi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40951,6 +46982,12 @@
"value" : "Терминал"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "terminali"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40974,6 +47011,12 @@
"value" : "Функция терминала отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Terminal özelliği devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -40998,6 +47041,12 @@
"value" : "Терминал отключен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Terminal devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41022,6 +47071,12 @@
"value" : "Непрозрачность терминала"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Terminal opaklığı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41031,6 +47086,22 @@
}
},
"Terminal opacity only affects the terminal backdrop; text stays fully opaque. Blur uses the system material behind the window." : {
+ "localizations" : {
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Terminal opaklığı yalnızca terminal arka planını etkiler; metin tamamen opak kalır. Blur, pencerenin arkasındaki sistem malzemesini kullanır."
+ }
+ }
+ }
+ },
+ "Test charging HUD" : {
+
+ },
+ "Test full battery HUD" : {
+
+ },
+ "Test low battery HUD" : {
},
"Text" : {
@@ -41049,6 +47120,12 @@
"value" : "Текст"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Metin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41065,6 +47142,12 @@
"value" : "TheDynamicIsland"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "TheDynamicIsland"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41088,6 +47171,12 @@
"value" : "Эти элементы управления дублируют настройки экрана блокировки для удобства."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu kontroller Kilit Ekranı sekmesini yansıtır, böylece oynatma ayarlarına odaklanırken medya katmanını ayarlayabilirsiniz."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41116,6 +47205,12 @@
"value" : "Эти элементы управления дублируют вкладку «Экран блокировки», позволяя настроить панель медиа с акцентом на параметры воспроизведения."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu kontroller zamanlayıcı çalışırken çentiğin yanında yer alır. Boşluk için zamanlayıcı adının gizli kalması gerekir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41144,6 +47239,12 @@
"value" : "Эти индикаторы появляются в вырезе, когда приложения используют камеру или микрофон."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu göstergeler, uygulamalar kameranızı veya mikrofonunuzu kullanırken çentikte görünür ve macOS'un yerel gizlilik göstergeleriyle eşleşir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41151,6 +47252,9 @@
}
}
}
+ },
+ "These temporary HUDs recreate the charging, low-battery, and full-battery notch alerts." : {
+
},
"Third-party apps using AtollExtensionKit can display live activities, lock screen widgets, and dedicated notch experiences. Toggle features above or manage individual app permissions below." : {
"comment" : "A footer text that explains the purpose of the \"Global Settings\" section in the Extensions settings view. | A footer text that explains the purpose of the \"Global Settings\" section in the Extensions settings view. | merge:ko:alt='AtollExtensionKit을 사용하는 타사 앱은 실시간 현황, 잠금 화면 위젯 및 노치 전용 기능을 표시할 수 있습니다. 위에서 기능을 켜고 끄거나 아래에서 개별 앱 권한을 관리할 수 있습니다.'",
@@ -41174,6 +47278,12 @@
"value" : "Сторонние приложения с AtollExtensionKit могут отображать Live Activities и виджеты. Управляйте ими здесь."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "AtollExtensionKit kullanan üçüncü taraf uygulamalar canlı etkinlikler, kilit ekranı widget'ları ve özel çentik deneyimleri gösterebilir. Yukarıdaki özellikleri açıp kapatın veya aşağıdan uygulama izinlerini yönetin."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41189,6 +47299,12 @@
"state" : "translated",
"value" : "타사 캘린더 통합"
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Üçüncü Taraf Takvim Entegrasyonu"
+ }
}
}
},
@@ -41196,6 +47312,12 @@
"comment" : "A footer description for the custom timer settings, explaining their purpose.",
"isCommentAutoGenerated" : true,
"localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "This duration powers the \"Custom\" option inside the timer popover for quick access."
+ }
+ },
"ko" : {
"stringUnit" : {
"state" : "translated",
@@ -41214,6 +47336,12 @@
"value" : "Используется для быстрого доступа к кнопке «Особый»."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu süre, hızlı erişim için zamanlayıcı açılır penceresindeki \"Özel\" seçeneğini belirler."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41236,6 +47364,12 @@
"value" : "Это необязательно. Вы сможете изменить это позже из строки меню."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu isteğe bağlıdır. İstediğiniz zaman menü çubuğundan değiştirebilirsiniz."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41264,6 +47398,12 @@
"value" : "Это необязательный параметр. Вы можете изменить его в любое время в строке меню."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu, kaydedilen her notu ve eklerini kalıcı olarak kaldırır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41294,6 +47434,12 @@
"value" : "Это удалит все сохраненные элементы буфера обмена из выреза."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bu, kaydedilen her pano öğesini çentik sekmesinden kaldırır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41316,6 +47462,12 @@
"value" : "Плиточный"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kiremitçi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41345,6 +47497,12 @@
"value" : "Временной диапазон"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zaman Aralığı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41435,8 +47593,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Time to Full Charge: %lld min"
+ "state" : "translated",
+ "value" : "Tam Şarja Kadar Süre: %lld dk."
}
},
"uk" : {
@@ -41479,6 +47637,12 @@
"value" : "Таймер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41509,6 +47673,12 @@
"value" : "Элементы управления таймером выглядят как"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı kontrolleri şu şekilde görünür:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41543,6 +47713,12 @@
"value" : "Таймер отключен"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı Devre Dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41577,6 +47753,12 @@
"value" : "Функция таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı Özelliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41611,6 +47793,12 @@
"value" : "Функция таймера отключена"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı özelliği devre dışı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41641,6 +47829,12 @@
"value" : "Материал стекла таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı cam malzemesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41671,6 +47865,12 @@
"value" : "Режим Liquid таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı sıvı modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41701,6 +47901,12 @@
"value" : "Пресеты таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı Ön Ayarları"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41735,6 +47941,12 @@
"value" : "Звук таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı Sesi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41765,6 +47977,12 @@
"value" : "Поверхность таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı yüzeyi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41793,6 +48011,12 @@
"value" : "Оттенок таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı tonu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41823,6 +48047,12 @@
"value" : "Виджет таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı Widget'ı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41851,6 +48081,12 @@
"value" : "Вариант виджета таймера"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı widget'ı çeşidi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41867,6 +48103,12 @@
"value" : "타이머 위젯 너비"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcı Widget Genişliği"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41897,6 +48139,12 @@
"value" : "Название"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Başlık"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -41987,8 +48235,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Toggle Notch Open:"
+ "state" : "translated",
+ "value" : "Çentik Açmayı Aç/Kapat:"
}
},
"uk" : {
@@ -42087,8 +48335,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Toggle Sneak Peek:"
+ "state" : "translated",
+ "value" : "Kısa Bakış'ı aç/kapat:"
}
},
"uk" : {
@@ -42121,6 +48369,12 @@
"value" : "Открыть/закрыть вкладку терминала:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Terminal Sekmesini Değiştir:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42151,6 +48405,12 @@
"value" : "Открывайте и закрывайте Dynamic Island из любого места."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dynamic Island'yi istediğiniz yerden açın veya kapatın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42173,6 +48433,12 @@
"value" : "Топ процессов"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "En İyi Süreçler"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42195,6 +48461,12 @@
"value" : "Всего"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Toplam"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42225,6 +48497,12 @@
"value" : "Всего · %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Toplam · %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42251,6 +48529,12 @@
"value" : "Пропуск трека"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parça Atla"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42280,6 +48564,12 @@
"value" : "Пропустить"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dönüştür"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42309,6 +48599,12 @@
"value" : "Сделайте вырез по-настоящему вашим"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentiklerinizi gerçekten kendinize ait hale getirin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42343,6 +48639,12 @@
"value" : "Попробуйте изменить поисковый запрос"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Arama terimlerinizi ayarlamayı deneyin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42377,6 +48679,12 @@
"value" : "Попробуйте другие условия поиска"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Farklı arama terimlerini deneyin"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42399,6 +48707,12 @@
"value" : "Выключите показ на всё время, чтобы использовать опцию после начала."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Başlangıç sonrası süre seçeneğini kullanmak için 'Etkinliği tüm süre boyunca göster' seçeneğini kapatın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42429,6 +48743,12 @@
"value" : "Отключите 'Показывать событие всю его длительность', чтобы использовать опцию отображения времени с начала события."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Medya düğmelerini yeniden düzenlemek için özelleştirilebilir kontrolleri açın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42459,6 +48779,12 @@
"value" : "Выключите, чтобы включить события на весь день."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çentik takvimine tüm gün girişlerini dahil etmek ve canlı etkinliği hatırlatmak için bunu kapatın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42551,8 +48877,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled"
+ "state" : "translated",
+ "value" : "**Fareyle üzerine gelince çentiği aç** seçeneği devre dışıyken kapatmak için çentik üzerinde iki parmakla yukarı, açmak için iki parmakla aşağı kaydırın"
}
},
"uk" : {
@@ -42595,6 +48921,12 @@
"value" : "Режим интерфейса"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanıcı Arayüzü Modu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42687,8 +49019,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Uninstall"
+ "state" : "translated",
+ "value" : "Kaldır"
}
},
"uk" : {
@@ -42721,6 +49053,12 @@
"value" : "Неизвестно"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Bilinmiyor"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42812,8 +49150,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Unlock advanced features and improve your experience. Upgrade now for more customizations!"
+ "state" : "translated",
+ "value" : "Gelişmiş özelliklerin kilidini açın ve deneyiminizi geliştirin. Daha fazla özelleştirme için şimdi yükseltin!"
}
},
"uk" : {
@@ -42912,8 +49250,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "unmuted"
+ "state" : "translated",
+ "value" : "sesi açık"
}
},
"uk" : {
@@ -42946,6 +49284,12 @@
"value" : "Отключено от сети"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Fişi çekilmiş"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42968,6 +49312,12 @@
"value" : "Вверх"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yukarı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -42996,6 +49346,12 @@
"value" : "Отключено"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Güncelleme aralığı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43031,6 +49387,12 @@
"value" : "Обновить таймер"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Zamanlayıcıyı Güncelle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43161,6 +49523,12 @@
"value" : "Отправка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yükle"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43184,6 +49552,12 @@
"value" : "Отправлено"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yüklendi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43206,6 +49580,12 @@
"value" : "Время работы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Çalışma süresi"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43230,6 +49610,12 @@
"value" : "URL"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "URL"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43266,6 +49652,12 @@
"value" : "Ссылка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanım"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43302,6 +49694,12 @@
"value" : "Детализация использования"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanım Dağılımı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43324,6 +49722,12 @@
"value" : "Использовать цвет акцента"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vurgu rengini kullan"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43352,6 +49756,12 @@
"value" : "Использовать цвет акцента"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Vurgu Rengini Kullan"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43359,6 +49769,9 @@
}
}
}
+ },
+ "Use album art layout over fullscreen canvas" : {
+
},
"Use circular battery indicator" : {
"localizations" : {
@@ -43374,6 +49787,12 @@
"value" : "Использовать круглый индикатор батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dairesel pil göstergesini kullan"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43396,6 +49815,12 @@
"value" : "Использовать цветные шкалы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Renkli göstergeler kullanın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43420,6 +49845,12 @@
"value" : "Использовать DevTools"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "DevTools'u kullanın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43429,7 +49860,14 @@
}
},
"Use enhanced liquid border" : {
-
+ "localizations" : {
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Geliştirilmiş sıvı kenarlığı kullan"
+ }
+ }
+ }
},
"Use MacBook icon when on battery" : {
"localizations" : {
@@ -43445,6 +49883,12 @@
"value" : "Использовать иконку MacBook при работе от батареи"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Pille çalışırken MacBook simgesini kullan"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43536,8 +49980,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "Use music visualizer spectrogram"
+ "state" : "translated",
+ "value" : "Müzik görselleştirici spektrogramını kullanma"
}
},
"uk" : {
@@ -43568,6 +50012,12 @@
"value" : "Использовать простую анимацию закрытия"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Daha basit yakın animasyon kullanın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43598,6 +50048,12 @@
"value" : "Используйте вкладку Media для настройки быстрого просмотра, текстов песен и др."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gizli bakış, şarkı sözleri ve kayan medya kontrollerini yapılandırmak için Medya sekmesini kullanın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43627,6 +50083,12 @@
"value" : "Используйте ползунки ниже для выбора вариантов liquid-glass для виджетов."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Her widget için benzersiz Apple sıvı cam çeşitlerini seçmek üzere aşağıdaki kaydırıcıları kullanın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43651,6 +50113,12 @@
"value" : "Использовать без DevTools"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "DevTools olmadan kullanın"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43687,6 +50155,12 @@
"value" : "Использовать без инструментов разработчика (DevTools)"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanılmış"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43723,6 +50197,12 @@
"value" : "Исп. · %@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanılmış · %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43745,6 +50225,12 @@
"value" : "Пользователь"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanıcı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43773,6 +50259,12 @@
"value" : "Отслеживает запись экрана в реальном времени"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Gerçek zamanlı ekran kaydı algılama için olay odaklı özel API kullanır"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43783,7 +50275,15 @@
},
"Uses macOS Now Playing when the Amazon Music app is the active media source. Playback controls follow the system Now Playing target. Scrubbing the timeline may not work if the Amazon Music app does not support remote seek." : {
"comment" : "Description of the Amazon Music media controller.",
- "isCommentAutoGenerated" : true
+ "isCommentAutoGenerated" : true,
+ "localizations" : {
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Amazon Music uygulaması etkin medya kaynağı olduğunda macOS Şimdi Yürütülüyor özelliğini kullanır. Oynatma kontrolleri sistemin Şimdi Yürütülüyor hedefini takip eder. Amazon Music uygulaması uzaktan aramayı desteklemiyorsa zaman çizelgesini temizleme çalışmayabilir."
+ }
+ }
+ }
},
"Uses the frosted blur treatment while glass mode is enabled." : {
"comment" : "A description of the frosted blur treatment when glass mode is enabled. | A description of the frosted blur treatment when glass mode is enabled. | merge:ko:alt='Glass 모드가 활성화되어 있는 동안에는 Frosted 블러 효과가 적용됩니다.'",
@@ -43807,6 +50307,12 @@
"value" : "Использует матовое размытие при включенном режиме стекла."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cam modu etkinken buzlu bulanıklaştırma işlemini kullanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43831,6 +50337,12 @@
"value" : "Утилиты"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yardımcı programlar"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43867,6 +50379,12 @@
"value" : "Загрузка"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kullanım"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43889,6 +50407,12 @@
"value" : "v%lld"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "v%lld"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -43898,6 +50422,14 @@
},
"shouldTranslate" : false
},
+ "Validate Cookie" : {
+ "comment" : "A button that validates the Spotify cookie.",
+ "isCommentAutoGenerated" : true
+ },
+ "Validating..." : {
+ "comment" : "The text that appears when validating a Spotify cookie.",
+ "isCommentAutoGenerated" : true
+ },
"Variant %lld" : {
"comment" : "A label for a specific variant of the Liquid Glass.",
"isCommentAutoGenerated" : true,
@@ -43908,6 +50440,12 @@
"value" : "변형 %lld"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Varyant %lld"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44124,6 +50662,12 @@
"value" : "세로 막대"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dikey Çubuk"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44146,6 +50690,12 @@
"value" : "Вертикальное смещение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dikey ofset"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44175,6 +50725,12 @@
"value" : "Вертикальный отступ"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Dikey:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44198,6 +50754,12 @@
"value" : "Видео"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "video"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44288,8 +50850,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "View on GitHub: th-ch/youtube-music"
+ "state" : "translated",
+ "value" : "GitHub'da görüntüleyin: th-ch/youtube-music"
}
},
"uk" : {
@@ -44328,6 +50890,12 @@
"value" : "Громкость"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hacim"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44335,6 +50903,9 @@
}
}
}
+ },
+ "Volume fine step" : {
+
},
"Volume HUD" : {
"localizations" : {
@@ -44362,6 +50933,12 @@
"value" : "HUD громкости"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hacim HUD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44392,6 +50969,12 @@
"value" : "OSD громкости"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Cilt OSD"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44399,6 +50982,9 @@
}
}
}
+ },
+ "Volume step" : {
+
},
"W " : {
"comment" : "Label for the write data point in the disk usage graph.",
@@ -44415,6 +51001,12 @@
"state" : "translated",
"value" : "W "
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Y "
+ }
}
}
},
@@ -44432,6 +51024,12 @@
"value" : "Предупреждение"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Uyarı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44462,6 +51060,12 @@
"value" : "З "
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava Durumu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44490,6 +51094,12 @@
"value" : "Провайдер данных погоды"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava durumu veri sağlayıcısı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44520,6 +51130,12 @@
"value" : "Виджет погоды"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava Durumu Widget'ı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44555,6 +51171,12 @@
"value" : "Погода: %1$@ %2$@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava Durumu: %@ %@"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44590,6 +51212,12 @@
"value" : "Погода: %1$@ %2$@ в %3$@"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Hava Durumu: %@ %@, %@ içinde"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44698,6 +51326,23 @@
}
}
},
+ "Welcome window" : {
+ "extractionState" : "stale",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Welcome window"
+ }
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Karşılama penceresi"
+ }
+ }
+ }
+ },
"What's New" : {
"localizations" : {
"ar" : {
@@ -44812,6 +51457,12 @@
"value" : "Утолщенный шрифт 대신 ярких цветов ANSI."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Parlak kadar kalın seçeneği kapalıyken, kalın metinde parlak ANSI renkleri yerine daha ağır bir yazı tipi ağırlığı kullanılır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44840,6 +51491,12 @@
"value" : "Когда параметр «Жирный текст как яркие цвета» отключён, он отображается более толстым начертанием шрифта вместо использования ярких ANSI-цветов."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Devre dışı bırakıldığında, tüm klavye kısayolları devre dışı bırakılacaktır. Kullanıcı arayüzü kontrollerini kullanmaya devam edebilirsiniz."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44862,6 +51519,12 @@
"value" : "При отключении музыкальный плеер выреза остается видимым с пустыми данными даже без воспроизведения."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Devre dışı bırakıldığında çentikli müzik çalar, oynatma etkin olmadığında bile yer tutucu meta verilerle birlikte görünür kalır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44877,6 +51540,12 @@
"state" : "translated",
"value" : "활성화하면 캘린더 이벤트를 클릭할 때 Apple 캘린더 대신 선택한 타사 캘린더 앱이 열립니다."
}
+ },
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinleştirildiğinde, takvim etkinliklerine tıklandığında Apple Takvim yerine seçilen üçüncü taraf takvim uygulaması açılır."
+ }
}
}
},
@@ -44902,6 +51571,12 @@
"value" : "При включении, Atoll будет получать экранные уведомления от BetterDisplay и отображать их в выбранном выше стиле панели. Это работает вместе с существующим перехватом мультимедийных клавиш — BetterDisplay управляет внешними дисплеями, а Atoll обеспечивает визуальную индикацию."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinleştirildiğinde Odak kısa bir süre görünür (açık/kapalı) ve ardından görünür kalmak yerine daraltılır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44930,6 +51605,12 @@
"value" : "При включении мониторинг статистики останавливается через несколько секунд после закрытия выреза."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinleştirildiğinde, istatistik izleme çentik kapandıktan birkaç saniye sonra durur."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44946,6 +51627,12 @@
"value" : "활성화 시 음악 비주얼라이저가 음악에 동기화된 실시간 오디오 스펙트럼 데이터를 표시합니다. macOS 14.2 이상이 필요하며, Accelerate 프레임워크를 통해 최소한의 CPU/GPU 리소스만 사용합니다."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinleştirildiğinde, müzik görselleştirici müziğinizle senkronize edilmiş gerçek zamanlı ses spektrumu verilerini görüntüler. macOS 14.2+ gerektirir ve Accelerate çerçevesi aracılığıyla minimum CPU/GPU kaynaklarını kullanır."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44970,6 +51657,12 @@
"value" : "При включении вырез скрывается на внешних дисплеях, пока вы не наведете курсор."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinleştirildiğinde çentik yukarı kayar ve siz üzerine gelene kadar harici (çentiksiz) ekranlarda gizlenir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -44998,6 +51691,12 @@
"value" : "При включении, вырез на экране сдвигается вверх и скрывается на внешних (не имеющих выреза) дисплеях, пока вы не наведете на него курсор."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Etkinleştirildiğinde, İstatistikler sekmesi gerçek zamanlı sistem performans grafiklerini gösterecektir. Bu özellik sistem izinleri gerektirir ve ek pil kullanabilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45020,6 +51719,12 @@
"value" : "Белый"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Beyaz"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45050,6 +51755,12 @@
"value" : "Белый"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Widget Güncellemeleri"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45066,6 +51777,12 @@
"value" : "너비 조절은 표준 노치 레이아웃에만 적용됩니다. 이 값을 수정하려면 미니멀리스트 UI를 비활성화하십시오."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genişlik ayarlamaları yalnızca standart çentik düzenine uygulanır. Bu değeri düzenlemek için Minimalist Kullanıcı Arayüzünü devre dışı bırakın."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45101,6 +51818,12 @@
"value" : "Ширина:"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genişlik:"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45129,6 +51852,12 @@
"value" : "Ширина: %lld px"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Genişlik: %lldpx"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45151,6 +51880,12 @@
"value" : "По проводу"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "kablolu"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45179,6 +51914,12 @@
"value" : "При %lld включенных графиках Dynamic Island расширится по горизонтали."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "%lld grafikleri etkinleştirildiğinde, Dynamic Island tüm grafikleri tek bir satırda barındıracak şekilde yatay olarak genişleyecektir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45203,6 +51944,12 @@
"value" : "Работает с большинством медиаплееров."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Neyin oynatıldığını algılamak için tarayıcılar dahil çoğu medya uygulamasıyla çalışır. Not: Bu, gelecekteki bir macOS sürümünde kaldırılabilir."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45239,6 +51986,12 @@
"value" : "Работает со всеми медиа приложениями, включая браузеры, определяя, что воспроизводится. Заметка: функция может быть удалена в будущих версиях macOS."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yazmak"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45255,6 +52008,12 @@
"value" : "쓰기 전용"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yalnızca Yaz"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45277,6 +52036,12 @@
"value" : "Записано"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Yazılı"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45311,6 +52076,12 @@
"value" : "Вы"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "sen"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45401,8 +52172,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "You can now enjoy the app. If you want to tweak things further, you can always visit the settings."
+ "state" : "translated",
+ "value" : "Artık uygulamayı kullanabilirsiniz. Daha fazla ayar yapmak isterseniz ayarlara her zaman dönebilirsiniz."
}
},
"uk" : {
@@ -45539,6 +52310,12 @@
"value" : "Данные календаря используются только для показа событий и никуда не передаются."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Takvim verileriniz yalnızca etkinliklerinizi göstermek için kullanılır ve asla paylaşılmaz."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45567,6 +52344,12 @@
"value" : "Ваша камера не используется без согласия, ничего не записывается и не сохраняется."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Kameranız asla sizin izniniz olmadan kullanılmaz, hiçbir şey kaydedilmez veya saklanmaz."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45591,6 +52374,12 @@
"value" : "Ваша поддержка финансирует обучение студентов программированию."
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Desteğiniz, 9-12. sınıftaki öğrencilerin yazılım geliştirme öğrenimini finanse ediyor."
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45616,6 +52405,12 @@
"value" : "Youtube Music"
}
},
+ "tr" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "YouTube Music"
+ }
+ },
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
@@ -45707,8 +52502,8 @@
},
"tr" : {
"stringUnit" : {
- "state" : "needs_review",
- "value" : "YouTube Music requires this third-party app to be installed: "
+ "state" : "translated",
+ "value" : "YouTube Music, bu üçüncü taraf uygulamanın yüklenmesini gerektirir:"
}
},
"uk" : {
diff --git a/DynamicIsland/MediaControllers/SpotifyController.swift b/DynamicIsland/MediaControllers/SpotifyController.swift
index 0380d96..5950ada 100644
--- a/DynamicIsland/MediaControllers/SpotifyController.swift
+++ b/DynamicIsland/MediaControllers/SpotifyController.swift
@@ -20,8 +20,8 @@
* along with this program. If not, see .
*/
-import Foundation
import Combine
+import Foundation
import SwiftUI
class SpotifyController: MediaControllerProtocol {
@@ -31,91 +31,117 @@ class SpotifyController: MediaControllerProtocol {
@Published private var playbackState: PlaybackState = PlaybackState(
bundleIdentifier: SpotifyController.bundleIdentifier
)
-
+
var playbackStatePublisher: AnyPublisher {
$playbackState.eraseToAnyPublisher()
}
-
+
var isWorking: Bool {
- return true // Spotify controller always works
+ true
}
-
+
private var notificationTask: Task?
-
+ private var sessionChangeCancellable: AnyCancellable?
+
// Constant for time between command and update
private let commandUpdateDelay: Duration = .milliseconds(25)
private var lastArtworkURL: String?
private var artworkFetchTask: Task?
-
+ private var canvasFetchTask: Task?
+ private var currentCanvasTrackURI: String?
+ private var lastCanvasRequestTrackURI: String?
+ private var lastCanvasRequestDate: Date = .distantPast
+ private let canvasResolver = SpotifyCanvasResolver()
+
init() {
setupPlaybackStateChangeObserver()
+ setupSessionChangeObserver()
Task {
if isActive() {
await updatePlaybackInfo()
}
}
}
-
+
private func setupPlaybackStateChangeObserver() {
notificationTask = Task { @Sendable [weak self] in
let notifications = DistributedNotificationCenter.default().notifications(
named: NSNotification.Name("com.spotify.client.PlaybackStateChanged")
)
-
+
for await _ in notifications {
await self?.updatePlaybackInfo()
}
}
}
-
+
+ private func setupSessionChangeObserver() {
+ sessionChangeCancellable = NotificationCenter.default.publisher(for: .spotifyCanvasSessionDidChange)
+ .sink { [weak self] _ in
+ self?.lastCanvasRequestTrackURI = nil
+ self?.lastCanvasRequestDate = .distantPast
+ if let self {
+ var updatedState = self.playbackState
+ updatedState.liveArtworkURL = nil
+ self.playbackState = updatedState
+ }
+ Task {
+ await self?.updatePlaybackInfo()
+ }
+ }
+ }
+
deinit {
notificationTask?.cancel()
artworkFetchTask?.cancel()
+ canvasFetchTask?.cancel()
+ sessionChangeCancellable?.cancel()
}
-
+
// MARK: - Protocol Implementation
func play() async { await executeCommand("play") }
func pause() async { await executeCommand("pause") }
func togglePlay() async { await executeCommand("playpause") }
func nextTrack() async { await executeCommand("next track") }
-
+
func previousTrack() async {
await executeAndRefresh("previous track")
}
-
+
func seek(to time: Double) async {
await executeAndRefresh("set player position to \(time)")
}
-
+
func toggleShuffle() async {
await executeAndRefresh("set shuffling to not shuffling")
}
-
+
func toggleRepeat() async {
await executeAndRefresh("set repeating to not repeating")
}
-
+
func isActive() -> Bool {
NSWorkspace.shared.runningApplications.contains { $0.bundleIdentifier == playbackState.bundleIdentifier }
}
-
+
func updatePlaybackInfo() async {
guard let descriptor = try? await fetchPlaybackInfoAsync() else { return }
guard descriptor.numberOfItems >= 11 else { return }
-
+
let isPlaying = descriptor.atIndex(1)?.booleanValue ?? false
let currentTrack = descriptor.atIndex(2)?.stringValue ?? "Unknown"
let currentTrackArtist = descriptor.atIndex(3)?.stringValue ?? "Unknown"
let currentTrackAlbum = descriptor.atIndex(4)?.stringValue ?? "Unknown"
let currentTime = descriptor.atIndex(5)?.doubleValue ?? 0
- let duration = (descriptor.atIndex(6)?.doubleValue ?? 0)/1000
+ let duration = (descriptor.atIndex(6)?.doubleValue ?? 0) / 1000
let isShuffled = descriptor.atIndex(7)?.booleanValue ?? false
let isRepeating = descriptor.atIndex(8)?.booleanValue ?? false
let artworkURL = descriptor.atIndex(9)?.stringValue ?? ""
- let trackIdentifier = descriptor.atIndex(10)?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- let trackSpotifyURL = descriptor.atIndex(11)?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
-
+ let trackIdentifier = descriptor.atIndex(10)?.stringValue ?? ""
+ let spotifyURLString = descriptor.atIndex(11)?.stringValue ?? ""
+ let trackURI = Self.canonicalTrackURI(from: trackIdentifier, spotifyURLString: spotifyURLString)
+
var state = PlaybackState(
bundleIdentifier: Self.bundleIdentifier,
isPlaying: isPlaying,
@@ -129,20 +155,40 @@ class SpotifyController: MediaControllerProtocol {
repeatMode: isRepeating ? .all : .off,
lastUpdated: Date()
)
- state.contentIdentifier = trackIdentifier.isEmpty ? nil : trackIdentifier
- state.contentURL = trackSpotifyURL.isEmpty ? nil : trackSpotifyURL
+ state.contentIdentifier = trackIdentifier.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+ ? nil
+ : trackIdentifier.trimmingCharacters(in: .whitespacesAndNewlines)
+ state.contentURL = spotifyURLString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+ ? nil
+ : spotifyURLString.trimmingCharacters(in: .whitespacesAndNewlines)
if artworkURL == lastArtworkURL, let existingArtwork = self.playbackState.artwork {
state.artwork = existingArtwork
}
- playbackState = state
+ let isSameCanvasTrack = trackURI == currentCanvasTrackURI
+
+ if isSameCanvasTrack {
+ state.liveArtworkURL = playbackState.liveArtworkURL
+ } else {
+ state.liveArtworkURL = nil
+ currentCanvasTrackURI = trackURI.isEmpty ? nil : trackURI
+ canvasFetchTask?.cancel()
+ canvasFetchTask = nil
+ }
+
+ playbackState = state
+
+ if !trackURI.isEmpty {
+ scheduleCanvasFetchIfNeeded(for: trackURI)
+ }
if !artworkURL.isEmpty, let url = URL(string: artworkURL) {
guard artworkURL != lastArtworkURL || state.artwork == nil else { return }
artworkFetchTask?.cancel()
let currentState = state
+ let expectedTrackURI = trackURI
artworkFetchTask = Task {
do {
@@ -150,8 +196,13 @@ class SpotifyController: MediaControllerProtocol {
await MainActor.run { [weak self] in
guard let self = self else { return }
+ guard expectedTrackURI.isEmpty || self.currentCanvasTrackURI == expectedTrackURI else {
+ self.artworkFetchTask = nil
+ return
+ }
var updatedState = currentState
updatedState.artwork = data
+ updatedState.liveArtworkURL = self.playbackState.liveArtworkURL
self.playbackState = updatedState
self.lastArtworkURL = artworkURL
self.artworkFetchTask = nil
@@ -164,9 +215,40 @@ class SpotifyController: MediaControllerProtocol {
}
}
}
-
-// MARK: - Private Methods
-
+
+ private func scheduleCanvasFetchIfNeeded(for trackURI: String) {
+ guard !trackURI.isEmpty else { return }
+ guard playbackState.liveArtworkURL == nil else { return }
+ guard canvasFetchTask == nil else { return }
+
+ if lastCanvasRequestTrackURI == trackURI,
+ Date().timeIntervalSince(lastCanvasRequestDate) < 5
+ {
+ return
+ }
+
+ lastCanvasRequestTrackURI = trackURI
+ lastCanvasRequestDate = Date()
+
+ canvasFetchTask = Task { [weak self] in
+ guard let self else { return }
+
+ let canvasURL = await self.canvasResolver.canvasURL(for: trackURI)
+ guard !Task.isCancelled else { return }
+
+ await MainActor.run {
+ guard self.currentCanvasTrackURI == trackURI else {
+ self.canvasFetchTask = nil
+ return
+ }
+
+ var updatedState = self.playbackState
+ updatedState.liveArtworkURL = canvasURL
+ self.playbackState = updatedState
+ self.canvasFetchTask = nil
+ }
+ }
+ }
private func executeCommand(_ command: String) async {
let script = "tell application \"Spotify\" to \(command)"
try? await AppleScriptHelper.executeVoid(script)
@@ -177,7 +259,7 @@ class SpotifyController: MediaControllerProtocol {
try? await Task.sleep(for: commandUpdateDelay)
await updatePlaybackInfo()
}
-
+
private func fetchPlaybackInfoAsync() async throws -> NSAppleEventDescriptor? {
let script = """
tell application "Spotify"
@@ -192,21 +274,294 @@ class SpotifyController: MediaControllerProtocol {
set shuffleState to shuffling
set repeatState to repeating
set artworkURL to artwork url of current track
- set trackIdentifier to ""
- try
- set trackIdentifier to id of current track
- end try
- set trackSpotifyURL to ""
- try
- set trackSpotifyURL to spotify url of current track
- end try
- return {playerState, currentTrackName, currentTrackArtist, currentTrackAlbum, trackPosition, trackDuration, shuffleState, repeatState, artworkURL, trackIdentifier, trackSpotifyURL}
+ set trackID to id of current track
+ set spotifyURL to spotify url of current track
+ return {playerState, currentTrackName, currentTrackArtist, currentTrackAlbum, trackPosition, trackDuration, shuffleState, repeatState, artworkURL, trackID, spotifyURL}
on error
return {false, "Unknown", "Unknown", "Unknown", 0, 0, false, false, "", "", ""}
end try
end tell
"""
-
+
return try await AppleScriptHelper.execute(script)
}
+
+ private static func canonicalTrackURI(from trackIdentifier: String, spotifyURLString: String) -> String {
+ let trimmedIdentifier = trackIdentifier.trimmingCharacters(in: .whitespacesAndNewlines)
+ if trimmedIdentifier.hasPrefix("spotify:track:") {
+ return trimmedIdentifier
+ }
+
+ if isLikelySpotifyTrackID(trimmedIdentifier) {
+ return "spotify:track:\(trimmedIdentifier)"
+ }
+
+ if let trackID = extractTrackID(from: spotifyURLString) {
+ return "spotify:track:\(trackID)"
+ }
+
+ return ""
+ }
+
+ private static func extractTrackID(from spotifyURLString: String) -> String? {
+ guard let url = URL(string: spotifyURLString),
+ let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
+ else { return nil }
+
+ let pathComponents = components.path.split(separator: "/")
+ guard let trackIndex = pathComponents.firstIndex(of: "track"),
+ trackIndex + 1 < pathComponents.count
+ else { return nil }
+
+ let identifier = String(pathComponents[trackIndex + 1])
+ return isLikelySpotifyTrackID(identifier) ? identifier : nil
+ }
+
+ private static func isLikelySpotifyTrackID(_ value: String) -> Bool {
+ value.count == 22 && value.unicodeScalars.allSatisfy { scalar in
+ CharacterSet.alphanumerics.contains(scalar)
+ }
+ }
+}
+
+private actor SpotifyCanvasResolver {
+ private struct CacheEntry {
+ let url: URL?
+ let expiresAt: Date
+ }
+
+ private var cache: [String: CacheEntry] = [:]
+
+ func canvasURL(for trackURI: String) async -> URL? {
+ let now = Date()
+ if let entry = cache[trackURI], entry.expiresAt > now {
+ return entry.url
+ }
+
+ let resolvedURL = await fetchCanvasURL(for: trackURI, retryOnUnauthorized: true)
+ let cacheLifetime: TimeInterval = resolvedURL == nil ? 300 : 3600
+ cache[trackURI] = CacheEntry(url: resolvedURL, expiresAt: now.addingTimeInterval(cacheLifetime))
+ return resolvedURL
+ }
+
+ private func fetchCanvasURL(for trackURI: String, retryOnUnauthorized: Bool) async -> URL? {
+ guard let accessToken = await SpotifyAuthManager.shared.validAccessToken() else {
+ return nil
+ }
+
+ var request = URLRequest(url: URL(string: "https://spclient.wg.spotify.com/canvaz-cache/v0/canvases")!)
+ request.httpMethod = "POST"
+ request.httpBody = SpotifyCanvasProtobuf.makeCanvasRequest(trackURI: trackURI)
+ request.setValue("application/protobuf", forHTTPHeaderField: "Accept")
+ request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
+ request.setValue("en", forHTTPHeaderField: "Accept-Language")
+ request.setValue("Spotify/9.0.34.593 iOS/18.4 (iPhone15,3)", forHTTPHeaderField: "User-Agent")
+ request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
+
+ do {
+ let (data, response) = try await URLSession.shared.data(for: request)
+ guard let httpResponse = response as? HTTPURLResponse else {
+ return nil
+ }
+
+ if httpResponse.statusCode == 401, retryOnUnauthorized {
+ await SpotifyAuthManager.shared.invalidateAccessToken()
+ return await fetchCanvasURL(for: trackURI, retryOnUnauthorized: false)
+ }
+
+ guard (200..<300).contains(httpResponse.statusCode) else {
+ return nil
+ }
+
+ return SpotifyCanvasProtobuf.parseCanvasURL(from: data, matching: trackURI)
+ } catch {
+ return nil
+ }
+ }
+}
+
+private enum SpotifyCanvasProtobuf {
+ static func makeCanvasRequest(trackURI: String) -> Data {
+ let uriData = Data(trackURI.utf8)
+
+ var trackMessage = Data()
+ trackMessage.append(protobufKey(fieldNumber: 1, wireType: 2))
+ trackMessage.append(varint(for: uriData.count))
+ trackMessage.append(uriData)
+
+ var request = Data()
+ request.append(protobufKey(fieldNumber: 1, wireType: 2))
+ request.append(varint(for: trackMessage.count))
+ request.append(trackMessage)
+ return request
+ }
+
+ static func parseCanvasURL(from data: Data, matching trackURI: String) -> URL? {
+ do {
+ var reader = ProtobufReader(data: data)
+ var matchingURLs: [URL] = []
+
+ while !reader.isAtEnd {
+ let key = try reader.readVarint()
+ let fieldNumber = Int(key >> 3)
+ let wireType = Int(key & 0x7)
+
+ switch (fieldNumber, wireType) {
+ case (1, 2):
+ let canvasData = try reader.readLengthDelimited()
+ if let record = try parseCanvasRecord(from: canvasData),
+ record.trackURI == trackURI,
+ let canvasURL = record.canvasURL
+ {
+ matchingURLs.append(canvasURL)
+ }
+ default:
+ try reader.skipField(wireType: wireType)
+ }
+ }
+
+ return matchingURLs.first { $0.absoluteString.lowercased().hasSuffix(".mp4") } ?? matchingURLs.first
+ } catch {
+ return nil
+ }
+ }
+
+ private static func parseCanvasRecord(from data: Data) throws -> SpotifyCanvasRecord? {
+ var reader = ProtobufReader(data: data)
+ var trackURI = ""
+ var canvasURL: URL?
+
+ while !reader.isAtEnd {
+ let key = try reader.readVarint()
+ let fieldNumber = Int(key >> 3)
+ let wireType = Int(key & 0x7)
+
+ switch (fieldNumber, wireType) {
+ case (2, 2):
+ let urlString = try reader.readString()
+ canvasURL = URL(string: urlString)
+ case (5, 2):
+ trackURI = try reader.readString()
+ default:
+ try reader.skipField(wireType: wireType)
+ }
+ }
+
+ guard !trackURI.isEmpty else { return nil }
+ return SpotifyCanvasRecord(trackURI: trackURI, canvasURL: canvasURL)
+ }
+
+ private static func protobufKey(fieldNumber: Int, wireType: Int) -> Data {
+ var key = Data()
+ key.append(varint(for: (fieldNumber << 3) | wireType))
+ return key
+ }
+
+ private static func varint(for value: Int) -> Data {
+ var remaining = UInt64(value)
+ var buffer = Data()
+
+ repeat {
+ var nextByte = UInt8(remaining & 0x7F)
+ remaining >>= 7
+ if remaining != 0 {
+ nextByte |= 0x80
+ }
+ buffer.append(nextByte)
+ } while remaining != 0
+
+ return buffer
+ }
+}
+
+private struct SpotifyCanvasRecord {
+ let trackURI: String
+ let canvasURL: URL?
+}
+
+private struct ProtobufReader {
+ enum ReaderError: Error {
+ case malformedVarint
+ case unexpectedEnd
+ case unsupportedWireType
+ }
+
+ private let data: Data
+ private var index: Data.Index
+
+ init(data: Data) {
+ self.data = data
+ self.index = data.startIndex
+ }
+
+ var isAtEnd: Bool {
+ index >= data.endIndex
+ }
+
+ mutating func readVarint() throws -> UInt64 {
+ var result: UInt64 = 0
+ var shift: UInt64 = 0
+
+ while shift < 64 {
+ guard index < data.endIndex else {
+ throw ReaderError.unexpectedEnd
+ }
+
+ let byte = data[index]
+ index = data.index(after: index)
+
+ result |= UInt64(byte & 0x7F) << shift
+ if (byte & 0x80) == 0 {
+ return result
+ }
+
+ shift += 7
+ }
+
+ throw ReaderError.malformedVarint
+ }
+
+ mutating func readLengthDelimited() throws -> Data {
+ let length = Int(try readVarint())
+ guard length >= 0 else {
+ throw ReaderError.unexpectedEnd
+ }
+
+ let endIndex = data.index(index, offsetBy: length, limitedBy: data.endIndex)
+ guard let endIndex else {
+ throw ReaderError.unexpectedEnd
+ }
+
+ let slice = data[index.. String {
+ let bytes = try readLengthDelimited()
+ return String(decoding: bytes, as: UTF8.self)
+ }
+
+ mutating func skipField(wireType: Int) throws {
+ switch wireType {
+ case 0:
+ _ = try readVarint()
+ case 1:
+ try advance(by: 8)
+ case 2:
+ _ = try readLengthDelimited()
+ case 5:
+ try advance(by: 4)
+ default:
+ throw ReaderError.unsupportedWireType
+ }
+ }
+
+ private mutating func advance(by count: Int) throws {
+ let endIndex = data.index(index, offsetBy: count, limitedBy: data.endIndex)
+ guard let endIndex else {
+ throw ReaderError.unexpectedEnd
+ }
+ index = endIndex
+ }
}
diff --git a/DynamicIsland/components/Live activities/DynamicIslandBattery.swift b/DynamicIsland/components/Live activities/DynamicIslandBattery.swift
index 10eef4c..af509b6 100644
--- a/DynamicIsland/components/Live activities/DynamicIslandBattery.swift
+++ b/DynamicIsland/components/Live activities/DynamicIslandBattery.swift
@@ -21,7 +21,7 @@ import Defaults
/// A view that displays the battery status with an icon and charging indicator.
struct BatteryView: View {
-
+ @Default(.showPowerStatusIcons) var showPowerStatusIcons
var levelBattery: Float
var isPluggedIn: Bool
var isCharging: Bool
@@ -79,7 +79,7 @@ struct BatteryView: View {
)
.padding(.leading, 2)
- if iconStatus != "" && (isForNotification || Defaults[.showPowerStatusIcons]) {
+ if iconStatus != "" && (isForNotification || showPowerStatusIcons) {
ZStack {
Image(iconStatus)
.resizable()
@@ -182,6 +182,7 @@ struct BatteryMenuView: View {
/// A view that displays the battery status and allows interaction to show detailed information.
struct DynamicIslandBatteryView: View {
+ @Default(.showBatteryPercentage) var showBatteryPercentage
@State var batteryWidth: CGFloat = 26
var isCharging: Bool = false
var isInLowPowerMode: Bool = false
@@ -199,7 +200,7 @@ struct DynamicIslandBatteryView: View {
var body: some View {
HStack {
- if Defaults[.showBatteryPercentage] {
+ if showBatteryPercentage {
ZStack(alignment: .trailing) {
Text("100%")
.font(.callout)
@@ -268,6 +269,443 @@ struct DynamicIslandBatteryView: View {
}
}
+
+private struct BatteryTemporaryHUDMetrics {
+ let width: CGFloat
+ let height: CGFloat
+ let topRadius: CGFloat
+ let bottomRadius: CGFloat
+}
+
+private extension BatteryTemporaryHUDKind {
+ func metrics(
+ style: BatteryNotificationStyle,
+ closedNotchWidth: CGFloat,
+ baseHeight: CGFloat
+ ) -> BatteryTemporaryHUDMetrics {
+ let compactBaseRadius = max(baseHeight / 2, 16)
+ let compactTopRadius = max(12, compactBaseRadius - 4)
+
+ switch (self, style) {
+ case (.charging, _), (.lowBattery, .compact), (.fullBattery, .compact):
+ return BatteryTemporaryHUDMetrics(
+ width: closedNotchWidth + 180,
+ height: baseHeight,
+ topRadius: compactTopRadius,
+ bottomRadius: compactBaseRadius
+ )
+ case (.lowBattery, .standard):
+ return BatteryTemporaryHUDMetrics(
+ width: closedNotchWidth + 150,
+ height: baseHeight + 75,
+ topRadius: 22,
+ bottomRadius: 40
+ )
+ case (.fullBattery, .standard):
+ return BatteryTemporaryHUDMetrics(
+ width: closedNotchWidth + 140,
+ height: baseHeight + 70,
+ topRadius: 18,
+ bottomRadius: 36
+ )
+ }
+ }
+}
+
+private struct BatteryCompactStatusRow: View {
+ let title: String
+ let batteryLevel: Int
+ let tint: Color
+
+ var body: some View {
+ HStack {
+ Text(verbatim: title)
+ .font(.system(size: 14))
+ .foregroundColor(.white.opacity(0.8))
+
+ Spacer()
+
+ HStack(spacing: 6) {
+ Text("\(batteryLevel)%")
+ .font(.system(size: 14))
+ .foregroundColor(tint)
+
+ HStack(spacing: 1.5) {
+ ZStack(alignment: .leading) {
+ RoundedRectangle(cornerRadius: 6, style: .continuous)
+ .fill(tint.opacity(0.3))
+
+ GeometryReader { geo in
+ let clamped = max(0, min(batteryLevel, 100))
+ let width = CGFloat(clamped) / 100 * geo.size.width
+ Rectangle()
+ .fill(tint.gradient)
+ .frame(width: max(0, width))
+ }
+ }
+ .frame(width: 28, height: 16)
+ .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
+
+ RoundedRectangle(cornerRadius: 1.5, style: .continuous)
+ .fill(batteryLevel == 100 ? tint.gradient : tint.opacity(0.3).gradient)
+ .frame(width: 2, height: 6)
+ }
+ }
+ }
+ .padding(.horizontal, 16)
+ }
+}
+
+struct BatteryTemporaryActivityView: View {
+ let kind: BatteryTemporaryHUDKind
+ let batteryLevel: Int
+ let isLowPowerMode: Bool
+ let closedNotchWidth: CGFloat
+ let baseHeight: CGFloat
+ let isDynamicIslandMode: Bool
+ let topCornerRadius: CGFloat
+ @Default(.lowBatteryHUDStyle) var lowBatteryHUDStyle
+ @Default(.fullBatteryHUDStyle) var fullBatteryHUDStyle
+ var styleOverride: BatteryNotificationStyle? = nil
+
+ @State private var pulse = false
+ @State private var showBatteryIndicator = false
+ @State private var changeBatteryIndicator = true
+
+ private var style: BatteryNotificationStyle {
+ if kind == .charging {
+ return .compact
+ }
+ if let styleOverride {
+ return styleOverride
+ }
+ switch kind {
+ case .charging:
+ return .compact
+ case .lowBattery:
+ return lowBatteryHUDStyle
+ case .fullBattery:
+ return fullBatteryHUDStyle
+ }
+ }
+
+ private var metrics: BatteryTemporaryHUDMetrics {
+ kind.metrics(
+ style: style,
+ closedNotchWidth: closedNotchWidth,
+ baseHeight: baseHeight
+ )
+ }
+
+ private var batteryTint: Color {
+ switch kind {
+ case .charging:
+ if isLowPowerMode {
+ return .yellow
+ } else if batteryLevel <= 20 {
+ return .red
+ }
+ return .green
+ case .lowBattery:
+ return isLowPowerMode ? .yellow : .red
+ case .fullBattery:
+ return isLowPowerMode ? .yellow : .green
+ }
+ }
+
+
+ private var surfaceShape: AnyShape {
+ if isDynamicIslandMode {
+ return AnyShape(DynamicIslandPillShape(cornerRadius: dynamicIslandPillCornerRadiusInsets.opened))
+ } else {
+ return AnyShape(NotchShape(topCornerRadius: topCornerRadius, bottomCornerRadius: metrics.bottomRadius))
+ }
+ }
+
+ var body: some View {
+ ZStack(alignment: .bottom) {
+ content
+ }
+ .frame(width: metrics.width, height: metrics.height, alignment: .bottom)
+ .clipShape(surfaceShape)
+ .onAppear(perform: prepareAnimations)
+ }
+
+ @ViewBuilder
+ private var content: some View {
+ if style == .compact {
+ BatteryCompactStatusRow(
+ title: compactTitle,
+ batteryLevel: batteryLevel,
+ tint: batteryTint
+ )
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
+ } else {
+ VStack {
+ Spacer()
+
+ HStack {
+ VStack(alignment: .leading, spacing: kind == .lowBattery ? 2 : 3) {
+ standardTitle
+ standardDescription
+ }
+
+ Spacer()
+
+ standardIndicator
+ }
+ .padding(.leading, kind == .lowBattery ? 40 : 35)
+ .padding(.trailing, kind == .lowBattery ? 45 : 40)
+ .padding(.bottom, 20)
+ }
+ }
+ }
+
+ private var compactTitle: String {
+ switch kind {
+ case .charging:
+ return "Charging"
+ case .lowBattery:
+ return "Low Battery"
+ case .fullBattery:
+ return "Full Battery"
+ }
+ }
+
+ @ViewBuilder
+ private var standardTitle: some View {
+ HStack(spacing: 5) {
+ Text(verbatim: kind == .lowBattery ? "Battery Low" : "Full Battery")
+ .font(.system(size: kind == .lowBattery ? 13 : 13, weight: .semibold))
+ .foregroundStyle(.white.opacity(0.82))
+ .lineLimit(1)
+
+ Text("\(batteryLevel)%")
+ .font(.system(size: kind == .lowBattery ? 12 : 13, weight: .semibold))
+ .foregroundStyle(batteryTint)
+ }
+ }
+
+ @ViewBuilder
+ private var standardDescription: some View {
+ switch kind {
+ case .charging:
+ EmptyView()
+ case .lowBattery:
+ if isLowPowerMode {
+ (
+ Text(verbatim: "Low Power Mode enabled")
+ .foregroundColor(.yellow)
+ .font(.system(size: 10, weight: .medium))
+ +
+ Text(verbatim: ", it is recommended to charge it.")
+ .foregroundColor(.gray.opacity(0.6))
+ .font(.system(size: 10, weight: .medium))
+ )
+ } else {
+ Text(verbatim: "Turn on Low Power Mode or it\nis recommended to charge it.")
+ .font(.system(size: 10, weight: .medium))
+ .foregroundStyle(.gray.opacity(0.6))
+ .lineLimit(2)
+ }
+ case .fullBattery:
+ Text(verbatim: "Your Mac is fully charged.")
+ .font(.system(size: 10))
+ .foregroundStyle(.gray.opacity(0.6))
+ .fontWeight(.medium)
+ .lineLimit(1)
+ }
+ }
+
+ @ViewBuilder
+ private var standardIndicator: some View {
+ switch kind {
+ case .charging:
+ EmptyView()
+ case .lowBattery:
+ if isLowPowerMode {
+ yellowLowIndicator
+ } else {
+ redLowIndicator
+ }
+ case .fullBattery:
+ if showBatteryIndicator {
+ if isLowPowerMode {
+ yellowFullIndicator
+ .transition(.opacity.combined(with: .scale))
+ } else {
+ greenFullIndicator
+ .transition(.opacity.combined(with: .scale))
+ }
+ } else {
+ magSafeIndicator
+ .transition(.opacity.combined(with: .scale))
+ }
+ }
+ }
+
+ private var redLowIndicator: some View {
+ ZStack {
+ RoundedRectangle(cornerRadius: 30)
+ .fill(.red.opacity(0.2))
+ .frame(width: 70, height: 40)
+
+ HStack(spacing: 2) {
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.red.opacity(0.4))
+ .frame(width: 40, height: 24)
+
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.red.opacity(0.4))
+ .frame(width: 3, height: 8)
+ }
+ .padding(.trailing, 5)
+
+ RoundedRectangle(cornerRadius: 8)
+ .fill(Color.red.gradient)
+ .frame(width: 8, height: 14)
+ .opacity(pulse ? 1 : 0.3)
+ .offset(x: -15)
+
+ RoundedRectangle(cornerRadius: 30)
+ .stroke(Color.red.opacity(0.9), lineWidth: 1.5)
+ .frame(width: pulse ? 8 : 30, height: pulse ? 14 : 32)
+ .offset(x: -15)
+ .opacity(pulse ? 0.3 : 1)
+ }
+ }
+
+ private var yellowLowIndicator: some View {
+ ZStack {
+ RoundedRectangle(cornerRadius: 30)
+ .fill(.yellow.opacity(0.2))
+ .frame(width: 70, height: 40)
+
+ HStack(spacing: 2) {
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.yellow.opacity(0.4))
+ .frame(width: 40, height: 24)
+
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.yellow.opacity(0.4))
+ .frame(width: 3, height: 8)
+ }
+ .padding(.trailing, 5)
+
+ RoundedRectangle(cornerRadius: 8)
+ .fill(.yellow.gradient)
+ .frame(width: 8, height: 14)
+ .offset(x: -15)
+ }
+ }
+
+ private var greenFullIndicator: some View {
+ ZStack {
+ RoundedRectangle(cornerRadius: 30)
+ .fill(.green.opacity(0.2))
+ .frame(width: 70, height: 40)
+
+ HStack(spacing: 2) {
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.green.opacity(0.4))
+ .frame(width: 44, height: 24)
+ .overlay(
+ RoundedRectangle(cornerRadius: 4)
+ .fill(Color.green.gradient)
+ .frame(width: 34, height: 14)
+ .opacity(pulse ? 1 : 0.4)
+ )
+
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.green.opacity(0.4))
+ .frame(width: 3, height: 8)
+ }
+ }
+ }
+
+ private var yellowFullIndicator: some View {
+ ZStack {
+ RoundedRectangle(cornerRadius: 30)
+ .fill(.yellow.opacity(0.2))
+ .frame(width: 70, height: 40)
+
+ HStack(spacing: 2) {
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.yellow.opacity(0.4))
+ .frame(width: 44, height: 24)
+ .overlay(
+ RoundedRectangle(cornerRadius: 4)
+ .fill(Color.yellow.gradient)
+ .frame(width: 34, height: 14)
+ .opacity(pulse ? 1 : 0.4)
+ )
+
+ RoundedRectangle(cornerRadius: 10)
+ .fill(.yellow.opacity(0.4))
+ .frame(width: 3, height: 8)
+ }
+ }
+ }
+
+ private var magSafeIndicator: some View {
+ HStack(spacing: 0) {
+ Rectangle()
+ .fill(.gray.opacity(0.15))
+ .frame(width: 30, height: 5)
+
+ ZStack {
+ RoundedRectangle(cornerRadius: 2)
+ .fill(.gray.opacity(0.2).gradient)
+ .frame(width: 30, height: 40)
+
+ Circle()
+ .fill(changeBatteryIndicator ? .orange : .green)
+ .shadow(color: changeBatteryIndicator ? .orange : .green, radius: 5)
+ .frame(width: 5, height: 5)
+ }
+
+ Rectangle()
+ .fill(.white.opacity(0.4))
+ .frame(width: 3, height: 32)
+ }
+ }
+
+ private func prepareAnimations() {
+ pulse = false
+ showBatteryIndicator = kind == .fullBattery && style == .standard
+ changeBatteryIndicator = true
+
+ guard style == .standard else { return }
+
+ switch kind {
+ case .charging:
+ break
+ case .lowBattery:
+ if !isLowPowerMode {
+ withAnimation(.easeInOut(duration: 1).repeatForever(autoreverses: true)) {
+ pulse = true
+ }
+ }
+ case .fullBattery:
+ withAnimation(.easeInOut(duration: 1).repeatForever(autoreverses: true)) {
+ pulse = true
+ }
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+ withAnimation(.spring(duration: 0.4)) {
+ showBatteryIndicator = false
+ }
+ }
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+ withAnimation(.spring(duration: 0.2)) {
+ changeBatteryIndicator = false
+ }
+ }
+ }
+ }
+}
+
#Preview {
DynamicIslandBatteryView(
batteryWidth: 30,
diff --git a/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift b/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift
index 17716a4..f9a86dc 100644
--- a/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift
+++ b/DynamicIsland/components/LockScreen/LockScreenMusicPanel.swift
@@ -36,6 +36,7 @@ struct LockScreenMusicPanel: View {
@ObservedObject var musicManager = MusicManager.shared
@ObservedObject private var routeManager = AudioRouteManager.shared
+ @ObservedObject private var fullscreenArtworkManager = FullScreenArtworkWindowManager.shared
@StateObject private var volumeModel = MediaOutputVolumeViewModel()
@ObservedObject private var airPlayManager = AppleMusicAirPlayManager.shared
@ObservedObject private var animator: LockScreenPanelAnimator
@@ -46,6 +47,7 @@ struct LockScreenMusicPanel: View {
@State private var isExpanded = false
@State private var isVolumeSliderVisible = false
@State private var isAirPlayPopoverPresented = false
+ @State private var isArtworkFullscreen = false
@State private var collapseWorkItem: DispatchWorkItem?
@State private var parallaxResumeWorkItem: DispatchWorkItem?
@@ -66,6 +68,9 @@ struct LockScreenMusicPanel: View {
@Default(.enableLyrics) private var enableLyrics
@Default(.lockScreenMusicAlbumParallaxEnabled) private var lockScreenParallaxEnabled
@Default(.lockScreenMusicPanelWidth) private var collapsedPanelWidth
+ @Default(.lockScreenMusicFullscreenArtworkEnabled) private var fullscreenArtworkEnabled
+ @Default(.lockScreenKeepAlbumArtVisibleDuringFullscreenArtwork) private var keepAlbumArtVisibleDuringFullscreenArtwork
+ @Default(.lockScreenMusicFullscreenVideoArtwork) private var fullscreenVideoArtwork
init(animator: LockScreenPanelAnimator) {
_animator = ObservedObject(wrappedValue: animator)
@@ -118,6 +123,22 @@ struct LockScreenMusicPanel: View {
private var usesEnhancedCustomLiquidBorder: Bool {
usesCustomLiquidGlass && useEnhancedLiquidBorder
}
+
+ private var hidesInlineArtworkForSpotifyCanvasFallback: Bool {
+ fullscreenArtworkManager.isShowingSpotifyCanvasFallback
+ }
+
+ private var usesSpotifyCanvasFallbackContentPresentation: Bool {
+ fullscreenArtworkManager.isShowingSpotifyCanvasFallback
+ }
+
+ private var showsDetachedFullscreenLyrics: Bool {
+ usesSpotifyCanvasFallbackContentPresentation && enableLyrics
+ }
+
+ private var shouldShowInlineLyrics: Bool {
+ enableLyrics && !showsDetachedFullscreenLyrics
+ }
var body: some View {
if isActive && musicManager.hasActiveSession {
@@ -132,16 +153,27 @@ struct LockScreenMusicPanel: View {
ZStack(alignment: .topLeading) {
panelBackgroundLayer
panelForeground
+ .shadow(
+ color: usesSpotifyCanvasFallbackContentPresentation ? Color.black.opacity(0.55) : .clear,
+ radius: usesSpotifyCanvasFallbackContentPresentation ? 10 : 0,
+ x: 0,
+ y: usesSpotifyCanvasFallbackContentPresentation ? 2 : 0
+ )
}
.frame(width: currentSize.width, height: currentSize.height, alignment: .topLeading)
.clipShape(RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous))
.overlay {
- if showPanelBorder {
+ if showPanelBorder && !usesSpotifyCanvasFallbackContentPresentation {
panelBorderOverlay
}
}
- .shadow(color: Color.black.opacity(0.3), radius: 20, x: 0, y: 10)
- .contentShape(RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous))
+ .shadow(
+ color: usesSpotifyCanvasFallbackContentPresentation ? Color.black.opacity(0.35) : Color.black.opacity(0.3),
+ radius: usesSpotifyCanvasFallbackContentPresentation ? 26 : 20,
+ x: 0,
+ y: usesSpotifyCanvasFallbackContentPresentation ? 14 : 10
+ )
+ .contentShape(Rectangle())
.animation(.easeInOut(duration: 0.28), value: isExpanded)
.animation(.easeInOut(duration: 0.24), value: shouldShowVolumeSlider)
.onAppear {
@@ -163,6 +195,11 @@ struct LockScreenMusicPanel: View {
parallaxResumeWorkItem = nil
isParallaxSuspended = false
}
+ .onReceive(NotificationCenter.default.publisher(for: .atollArtworkWallpaperDismissed)) { _ in
+ withAnimation(.easeInOut(duration: 0.28)) {
+ isArtworkFullscreen = false
+ }
+ }
.onChange(of: isExpanded) { _, expanded in
updatePanelSize()
}
@@ -222,9 +259,13 @@ struct LockScreenMusicPanel: View {
collapsedLayout
}
}
- .padding(.horizontal, isExpanded ? 24 : 20)
- .padding(.vertical, isExpanded ? 22 : 16)
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
+ .padding(.horizontal, usesSpotifyCanvasFallbackContentPresentation ? (isExpanded ? 24 : 22) : (isExpanded ? 24 : 20))
+ .padding(.vertical, usesSpotifyCanvasFallbackContentPresentation ? (isExpanded ? 18 : 14) : (isExpanded ? 22 : 16))
+ .frame(
+ maxWidth: .infinity,
+ maxHeight: .infinity,
+ alignment: usesSpotifyCanvasFallbackContentPresentation ? .center : .topLeading
+ )
}
private var collapsedLayout: some View {
@@ -239,25 +280,40 @@ struct LockScreenMusicPanel: View {
}
private var expandedLayout: some View {
- HStack(alignment: .center, spacing: expandedContentSpacing) {
- albumArtButton(size: 230, cornerRadius: expandedAlbumArtCornerRadius)
- .frame(width: 230, height: 230)
+ Group {
+ if hidesInlineArtworkForSpotifyCanvasFallback {
+ VStack(alignment: .leading, spacing: 20) {
+ expandedHeader
+ progressBar
+ .padding(.top, 10)
+ .frame(maxWidth: .infinity)
+ playbackControls(alignment: .leading)
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ } else {
+ HStack(alignment: .center, spacing: expandedContentSpacing) {
+ albumArtButton(size: 230, cornerRadius: expandedAlbumArtCornerRadius)
+ .frame(width: 230, height: 230)
- VStack(alignment: .leading, spacing: 20) {
- expandedHeader
- progressBar
- .padding(.top, 10)
- .frame(maxWidth: .infinity)
- playbackControls(alignment: .leading)
+ VStack(alignment: .leading, spacing: 20) {
+ expandedHeader
+ progressBar
+ .padding(.top, 10)
+ .frame(maxWidth: .infinity)
+ playbackControls(alignment: .leading)
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
}
- .frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxHeight: .infinity, alignment: .center)
}
private var collapsedHeader: some View {
- HStack(alignment: .center, spacing: 16) {
- albumArtButton(size: 60, cornerRadius: collapsedAlbumArtCornerRadius)
+ HStack(alignment: .center, spacing: hidesInlineArtworkForSpotifyCanvasFallback ? 10 : 16) {
+ if !hidesInlineArtworkForSpotifyCanvasFallback {
+ albumArtButton(size: 60, cornerRadius: collapsedAlbumArtCornerRadius)
+ }
GeometryReader { geo in
VStack(alignment: .leading, spacing: 1) {
@@ -329,30 +385,48 @@ struct LockScreenMusicPanel: View {
}
private func albumArtButton(size: CGFloat, cornerRadius: CGFloat) -> some View {
- Button(action: toggleExpanded) {
- ZStack(alignment: .bottomTrailing) {
- albumArtImage(size: size, cornerRadius: cornerRadius)
- if showAppIcon, let icon = lockScreenAppIcon {
- icon
- .resizable()
- .aspectRatio(contentMode: .fit)
- .frame(width: appIconSize, height: appIconSize)
+ let artworkCornerRadius = resolvedArtworkCornerRadius(from: cornerRadius)
- .shadow(color: Color.black.opacity(0.35), radius: 6, x: 0, y: 4)
- .offset(x: appIconOffset, y: appIconOffset)
- .transition(.scale.combined(with: .opacity))
- }
+ return ZStack(alignment: .bottomTrailing) {
+ if isArtworkFullscreen && !keepAlbumArtVisibleDuringFullscreenArtwork {
+ RoundedRectangle(cornerRadius: artworkCornerRadius, style: .continuous)
+ .fill(Color.white.opacity(0.06))
+ .overlay(
+ Image(systemName: "photo.fill")
+ .font(.system(size: size * 0.3, weight: .light))
+ .foregroundColor(.white.opacity(0.2))
+ )
+ } else {
+ albumArtImage(size: size, cornerRadius: cornerRadius)
+ if showAppIcon, let icon = lockScreenAppIcon {
+ icon
+ .resizable()
+ .aspectRatio(contentMode: .fit)
+ .frame(width: appIconSize, height: appIconSize)
+ .shadow(color: Color.black.opacity(0.35), radius: 6, x: 0, y: 4)
+ .offset(x: appIconOffset, y: appIconOffset)
+ .transition(.scale.combined(with: .opacity))
}
- .albumArtFlip(angle: musicManager.flipAngle)
- .parallax3D(enableOverride: lockScreenParallaxEnabled, suspended: isParallaxSuspended)
- .frame(width: size)
- .background(albumArtBackground(cornerRadius: cornerRadius))
- .clipShape(RoundedRectangle(cornerRadius: musicManager.albumArt.size.width/musicManager.albumArt.size.height > 1.0 ? appIconCornerRadius/3 : appIconCornerRadius, style: .continuous))
+ }
}
- .buttonStyle(PlainButtonStyle())
+ .albumArtFlip(angle: isArtworkFullscreen && !keepAlbumArtVisibleDuringFullscreenArtwork ? 0 : musicManager.flipAngle)
+ .parallax3D(
+ enableOverride: lockScreenParallaxEnabled && (!isArtworkFullscreen || keepAlbumArtVisibleDuringFullscreenArtwork),
+ suspended: isParallaxSuspended
+ )
+ .frame(width: size)
+ .background(albumArtBackground(cornerRadius: artworkCornerRadius))
+ .clipShape(RoundedRectangle(cornerRadius: artworkCornerRadius, style: .continuous))
.opacity(musicManager.isPlaying ? 1 : 0.4)
.scaleEffect(musicManager.isPlaying ? 1 : 0.85)
.animation(.easeInOut(duration: 0.2), value: musicManager.isPlaying)
+ .animation(.easeInOut(duration: 0.28), value: isArtworkFullscreen)
+ .onTapGesture {
+ toggleExpanded()
+ }
+ .onRightClick {
+ expandArtworkToFullscreen()
+ }
}
@ViewBuilder
@@ -384,6 +458,41 @@ struct LockScreenMusicPanel: View {
}
}
+ private func expandArtworkToFullscreen() {
+ guard fullscreenArtworkEnabled else { return }
+
+ if FullScreenArtworkWindowManager.shared.isShowing {
+ FullScreenArtworkWindowManager.shared.hide()
+ withAnimation(.easeInOut(duration: 0.28)) {
+ isArtworkFullscreen = false
+ }
+ return
+ }
+
+ guard musicManager.hasActiveSession else { return }
+
+ let artwork = musicManager.albumArt
+ let videoURL = musicManager.videoArtworkURL
+
+ withAnimation(.easeInOut(duration: 0.28)) {
+ isArtworkFullscreen = true
+ }
+
+ FullScreenArtworkWindowManager.shared.onDismiss = {
+ Task { @MainActor in
+ withAnimation(.easeInOut(duration: 0.28)) {
+ NotificationCenter.default.post(name: .atollArtworkWallpaperDismissed, object: nil)
+ }
+ }
+ }
+
+ FullScreenArtworkWindowManager.shared.show(
+ artwork: artwork,
+ videoURL: fullscreenVideoArtwork ? videoURL : nil,
+ allowLiveWallpaper: fullscreenVideoArtwork
+ )
+ }
+
private func registerInteraction() {
cancelCollapseTimer()
guard isExpanded else { return }
@@ -484,7 +593,7 @@ struct LockScreenMusicPanel: View {
private func playbackControls(alignment: Alignment) -> some View {
let spacing: CGFloat = isExpanded ? 24 : 20
- let verticalSpacing: CGFloat = (shouldShowVolumeSlider || enableLyrics) ? 14 : 10
+ let verticalSpacing: CGFloat = (shouldShowVolumeSlider || shouldShowInlineLyrics) ? 14 : 10
return VStack(spacing: verticalSpacing) {
controlsRow(alignment: alignment, spacing: spacing)
@@ -505,7 +614,7 @@ struct LockScreenMusicPanel: View {
}
}
- if enableLyrics {
+ if shouldShowInlineLyrics {
lyricsSection(alignment: alignment)
.transition(.move(edge: .top).combined(with: .opacity))
}
@@ -949,7 +1058,7 @@ struct LockScreenMusicPanel: View {
}
private var lyricsExtraHeight: CGFloat {
- lyricsHeight(forExpanded: isExpanded, enabled: enableLyrics)
+ lyricsHeight(forExpanded: isExpanded, enabled: shouldShowInlineLyrics)
}
private var accessorySectionExtraHeight: CGFloat {
@@ -979,7 +1088,7 @@ struct LockScreenMusicPanel: View {
private func panelAdditionalHeight(forExpanded expanded: Bool) -> CGFloat {
sliderHeight(forExpanded: expanded, visible: shouldShowVolumeSlider) +
accessorySectionExtraHeight +
- lyricsHeight(forExpanded: expanded, enabled: enableLyrics)
+ lyricsHeight(forExpanded: expanded, enabled: shouldShowInlineLyrics)
}
private func updatePanelSize(animated: Bool = true) {
@@ -1034,7 +1143,9 @@ struct LockScreenMusicPanel: View {
@ViewBuilder
private var panelBackgroundLayer: some View {
- if usesCustomLiquidGlass {
+ if usesSpotifyCanvasFallbackContentPresentation {
+ canvasFallbackScrim
+ } else if usesCustomLiquidGlass {
customLiquidPanelBackdrop
} else if usesStandardLiquidGlass {
standardLiquidPanelBackdrop
@@ -1045,6 +1156,31 @@ struct LockScreenMusicPanel: View {
}
}
+ private var canvasFallbackScrim: some View {
+ RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous)
+ .fill(.ultraThinMaterial)
+ .environment(\.colorScheme, .dark)
+ .overlay {
+ LinearGradient(
+ colors: [
+ Color.black.opacity(0.32),
+ Color.black.opacity(0.18),
+ Color.black.opacity(0.32)
+ ],
+ startPoint: .top,
+ endPoint: .bottom
+ )
+ .clipShape(RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous))
+ }
+ .overlay {
+ RoundedRectangle(cornerRadius: panelCornerRadius, style: .continuous)
+ .stroke(Color.white.opacity(0.10), lineWidth: 0.6)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .allowsHitTesting(false)
+ .accessibilityHidden(true)
+ }
+
@ViewBuilder
private var standardLiquidPanelBackdrop: some View {
if #available(macOS 26.0, *) {
@@ -1132,7 +1268,8 @@ struct LockScreenMusicPanel: View {
Image(nsImage: musicManager.albumArt)
.resizable()
.aspectRatio(contentMode: .fit)
- .clipShape(RoundedRectangle(cornerRadius: musicManager.albumArt.size.width/musicManager.albumArt.size.height > 1.0 ? cornerRadius/3 : cornerRadius))
+ .compositingGroup()
+ .clipShape(RoundedRectangle(cornerRadius: resolvedArtworkCornerRadius(from: cornerRadius), style: .continuous))
}
@ViewBuilder
@@ -1170,6 +1307,13 @@ struct LockScreenMusicPanel: View {
isExpanded ? 18 : 12
}
+ private func resolvedArtworkCornerRadius(from baseCornerRadius: CGFloat) -> CGFloat {
+ let aspectRatio = musicManager.albumArt.size.height > 0
+ ? musicManager.albumArt.size.width / musicManager.albumArt.size.height
+ : 1
+ return aspectRatio > 1.0 ? baseCornerRadius / 3 : baseCornerRadius
+ }
+
@available(macOS 26.0, *)
private func clearLiquidGlassSurface(cornerRadius: CGFloat) -> some View {
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
@@ -1384,3 +1528,87 @@ private struct GlassTextBackdrop: View {
.accessibilityHidden(true)
}
}
+
+// MARK: - Right Click Gesture
+
+private struct RightClickOverlay: ViewModifier {
+ let action: () -> Void
+
+ func body(content: Content) -> some View {
+ content.overlay(
+ RightClickReceiverView(action: action)
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ )
+ }
+}
+
+private struct RightClickReceiverView: NSViewRepresentable {
+ let action: () -> Void
+
+ func makeNSView(context: Context) -> RightClickNSView {
+ let view = RightClickNSView()
+ view.onRightClick = action
+ return view
+ }
+
+ func updateNSView(_ nsView: RightClickNSView, context: Context) {
+ nsView.onRightClick = action
+ }
+}
+
+private extension View {
+ func onRightClick(perform action: @escaping () -> Void) -> some View {
+ modifier(RightClickOverlay(action: action))
+ }
+}
+
+final class RightClickNSView: NSView {
+ var onRightClick: (() -> Void)?
+ private var eventMonitor: Any?
+
+ deinit {
+ removeEventMonitor()
+ }
+
+ override func viewDidMoveToWindow() {
+ super.viewDidMoveToWindow()
+
+ if window == nil {
+ removeEventMonitor()
+ } else {
+ installEventMonitorIfNeeded()
+ }
+ }
+
+ override func hitTest(_ point: NSPoint) -> NSView? {
+ nil
+ }
+
+ private func installEventMonitorIfNeeded() {
+ guard eventMonitor == nil else { return }
+
+ eventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.rightMouseDown]) { [weak self] event in
+ guard let self, let window = self.window, event.window === window else {
+ return event
+ }
+
+ let pointInView = self.convert(event.locationInWindow, from: nil)
+ guard self.bounds.contains(pointInView) else {
+ return event
+ }
+
+ self.onRightClick?()
+ return nil
+ }
+ }
+
+ private func removeEventMonitor() {
+ guard let eventMonitor else { return }
+ NSEvent.removeMonitor(eventMonitor)
+ self.eventMonitor = nil
+ }
+}
+
+extension Notification.Name {
+ static let atollArtworkWallpaperDismissed = Notification.Name("atollArtworkWallpaperDismissed")
+}
diff --git a/DynamicIsland/components/LockScreen/LockScreenTimerWidget.swift b/DynamicIsland/components/LockScreen/LockScreenTimerWidget.swift
index 362c853..0d6ce7c 100644
--- a/DynamicIsland/components/LockScreen/LockScreenTimerWidget.swift
+++ b/DynamicIsland/components/LockScreen/LockScreenTimerWidget.swift
@@ -45,10 +45,6 @@ struct LockScreenTimerWidget: View {
}
}
- private func displayFont(size: CGFloat) -> Font {
- .system(size: size, weight: .semibold, design: .rounded)
- }
-
private var hasHoursComponent: Bool {
abs(timerManager.remainingTime) >= 3600
}
@@ -57,12 +53,6 @@ struct LockScreenTimerWidget: View {
abs(timerManager.remainingTime) >= 36_000 // 10 hours or more
}
- private var titleFrameWidth: CGFloat {
- if hasDoubleDigitHours { return 60 }
- if hasHoursComponent { return 80 }
- return 110
- }
-
private var countdownFrameWidth: CGFloat {
if hasDoubleDigitHours { return 200 }
if hasHoursComponent { return 185 }
@@ -74,10 +64,6 @@ struct LockScreenTimerWidget: View {
return .system(size: baseSize, weight: .bold, design: .rounded)
}
- private var timerLabel: String {
- timerManager.timerName.isEmpty ? "Timer" : timerManager.timerName
- }
-
private var countdownText: String {
timerManager.formattedRemainingTime()
}
@@ -195,8 +181,7 @@ struct LockScreenTimerWidget: View {
controlButtons
.padding(.trailing, 12)
- titleSection
- .frame(maxWidth: .infinity)
+ Spacer()
countdownSection
.padding(.leading, 12)
@@ -261,19 +246,6 @@ struct LockScreenTimerWidget: View {
.layoutPriority(2)
}
- private var titleSection: some View {
- MarqueeText(
- .constant(timerLabel),
- font: displayFont(size: 15),
- nsFont: .title3,
- textColor: accentColor,
- minDuration: 0.16,
- frameWidth: titleFrameWidth
- )
- .frame(maxWidth: .infinity, alignment: .center)
- .layoutPriority(0)
- }
-
private var accentRibbon: some View {
Capsule()
.fill(accentGradient)
diff --git a/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift b/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift
index 5a31718..e37bf27 100644
--- a/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift
+++ b/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift
@@ -1261,7 +1261,15 @@ struct LockScreenWeatherWidget: View {
private func chargingStatusFallback(for info: LockScreenWeatherSnapshot.ChargingInfo) -> String {
if info.isPluggedIn && !info.isCharging {
- return NSLocalizedString("Fully charged", comment: "Charging fallback label when already charged")
+ let level = info.batteryLevel.map(clampedBatteryLevel)
+ if level == 100 {
+ return NSLocalizedString("Fully charged", comment: "Charging fallback label when already charged")
+ }
+ if let level {
+ let onHold = NSLocalizedString("On hold", comment: "Plugged in but charging is paused by the system")
+ return "\(level)% • \(onHold)"
+ }
+ return NSLocalizedString("On hold", comment: "Plugged in but charging is paused by the system")
}
return NSLocalizedString("Charging", comment: "Charging fallback label when no estimate is available")
}
@@ -1456,7 +1464,17 @@ struct LockScreenWeatherWidget: View {
}
if charging.isPluggedIn && !charging.isCharging {
- return NSLocalizedString("Battery fully charged", comment: "Battery is full")
+ let level = charging.batteryLevel.map(clampedBatteryLevel)
+ if level == 100 {
+ return NSLocalizedString("Battery fully charged", comment: "Battery is full")
+ }
+ if let level {
+ return String(
+ format: NSLocalizedString("Battery at %d percent, charging on hold", comment: "Plugged in but charging is paused by the system"),
+ level
+ )
+ }
+ return NSLocalizedString("Charging on hold", comment: "Plugged in but charging is paused by the system")
}
if snapshot.showsChargingPercentage, let level = charging.batteryLevel {
diff --git a/DynamicIsland/components/Notch/DynamicIslandHeader.swift b/DynamicIsland/components/Notch/DynamicIslandHeader.swift
index c9eda7b..6e0618f 100644
--- a/DynamicIsland/components/Notch/DynamicIslandHeader.swift
+++ b/DynamicIsland/components/Notch/DynamicIslandHeader.swift
@@ -36,10 +36,12 @@ struct DynamicIslandHeader: View {
@Default(.showClipboardIcon) var showClipboardIcon
@Default(.showColorPickerIcon) var showColorPickerIcon
@Default(.clipboardDisplayMode) var clipboardDisplayMode
+ @Default(.showBatteryIndicator) var showBatteryIndicator
+ @Default(.enableMinimalisticUI) var enableMinimalisticUI
var body: some View {
HStack(spacing: 0) {
- if !Defaults[.enableMinimalisticUI] {
+ if !enableMinimalisticUI {
HStack {
let shouldShowTabs = coordinator.alwaysShowTabs || vm.notchState == .open || !shelfState.items.isEmpty
if shouldShowTabs {
@@ -55,7 +57,7 @@ struct DynamicIslandHeader: View {
}
- if vm.notchState == .open && !Defaults[.enableMinimalisticUI] {
+ if vm.notchState == .open && !enableMinimalisticUI {
let spacerWidth = min(vm.closedNotchSize.width, 300)
Rectangle()
.fill(NSScreen.screens
@@ -67,7 +69,7 @@ struct DynamicIslandHeader: View {
}
HStack(spacing: 4) {
- if vm.notchState == .open && !Defaults[.enableMinimalisticUI] {
+ if vm.notchState == .open && !enableMinimalisticUI {
if Defaults[.showMirror] {
Button(action: {
vm.toggleCameraPreview()
@@ -230,7 +232,7 @@ struct DynamicIslandHeader: View {
- if Defaults[.showBatteryIndicator] {
+ if showBatteryIndicator {
DynamicIslandBatteryView(
batteryWidth: 30,
isCharging: batteryModel.isCharging,
diff --git a/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift b/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift
index 5b020a9..91255d5 100644
--- a/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift
+++ b/DynamicIsland/components/Notch/MinimalisticMusicPlayerView.swift
@@ -113,6 +113,7 @@ struct MinimalisticMusicPlayerView: View {
// Compact progress bar
progressBar
.padding(.top, 6)
+ .clipped()
// Compact playback controls
if shouldShowControlHUDRow {
@@ -133,7 +134,7 @@ struct MinimalisticMusicPlayerView: View {
reminderList
}
.padding(.horizontal, 12)
- .padding(.top, -15)
+ .padding(.top, 12)
.padding(.bottom, ReminderLiveActivityManager.baselineMinimalisticBottomPadding)
.frame(maxWidth: .infinity)
.frame(height: calculateDynamicHeight(), alignment: .top)
@@ -254,7 +255,7 @@ struct MinimalisticMusicPlayerView: View {
height += 6 + 4 // progress bar + top padding
// Add playback controls height
- height += 40 + 2 // controls + top padding
+ height += 54 + 2 // controls + top padding
// Add lyrics height if enabled in settings (reserve space even while loading)
if enableLyrics {
@@ -706,7 +707,7 @@ private struct MinimalisticReminderDetailsView: View {
// MARK: - Playback Controls (Larger)
private var playbackControls: some View {
- HStack(spacing: 16) {
+ HStack(spacing: 10) {
ForEach(Array(displayedSlots.enumerated()), id: \.offset) { _, slot in
slotView(for: slot)
}
@@ -834,10 +835,10 @@ private struct MinimalisticReminderDetailsView: View {
private var playPauseButton: some View {
MinimalisticSquircircleButton(
icon: musicManager.isPlaying ? (musicManager.isLiveStream ? "stop.fill" : "pause.fill") : "play.fill",
- fontSize: 28,
+ fontSize: 26,
fontWeight: .semibold,
- frameSize: CGSize(width: 60, height: 60),
- cornerRadius: 24,
+ frameSize: CGSize(width: 54, height: 54),
+ cornerRadius: 22,
foregroundColor: .white,
pressEffect: .none,
symbolEffectStyle: .replace,
@@ -867,8 +868,8 @@ private struct MinimalisticReminderDetailsView: View {
icon: icon,
fontSize: size,
fontWeight: .medium,
- frameSize: CGSize(width: 40, height: 40),
- cornerRadius: 16,
+ frameSize: CGSize(width: 36, height: 36),
+ cornerRadius: 14,
foregroundColor: isActive ? resolvedActiveColor : .white.opacity(0.85),
pressEffect: pressEffect,
symbolEffectStyle: symbolEffect,
@@ -991,8 +992,8 @@ private struct MinimalisticReminderDetailsView: View {
icon: routeManager.activeDevice?.iconName ?? "speaker.wave.2",
fontSize: 18,
fontWeight: .medium,
- frameSize: CGSize(width: 40, height: 40),
- cornerRadius: 16,
+ frameSize: CGSize(width: 36, height: 36),
+ cornerRadius: 14,
foregroundColor: .white.opacity(0.85),
symbolEffectStyle: .replace
) {
@@ -1051,8 +1052,8 @@ private struct MinimalisticReminderDetailsView: View {
icon: "airplayaudio",
fontSize: 18,
fontWeight: .medium,
- frameSize: CGSize(width: 40, height: 40),
- cornerRadius: 16,
+ frameSize: CGSize(width: 36, height: 36),
+ cornerRadius: 14,
foregroundColor: .white.opacity(0.85),
symbolEffectStyle: .replace
) {
@@ -1113,8 +1114,17 @@ private struct MinimalisticReminderDetailsView: View {
struct MinimalisticAlbumArtView: View {
@ObservedObject var musicManager = MusicManager.shared
@ObservedObject var vm: DynamicIslandViewModel
+ @Default(.showLiveCanvasInDynamicIsland) private var showLiveCanvasInDynamicIsland
let albumArtNamespace: Namespace.ID
+ private var usesLiveCanvasArtwork: Bool {
+ showLiveCanvasInDynamicIsland && musicManager.videoArtworkURL != nil
+ }
+
+ private var albumArtCornerRadius: CGFloat {
+ musicManager.albumArt.size.width / musicManager.albumArt.size.height > 1.0 ? 4 : 12
+ }
+
var body: some View {
ZStack(alignment: .bottomTrailing) {
if Defaults[.lightingEffect] {
@@ -1128,16 +1138,27 @@ struct MinimalisticAlbumArtView: View {
Color.clear
.aspectRatio(1, contentMode: .fit)
.background(
- Image(nsImage: musicManager.albumArt)
- .resizable()
- .scaledToFill()
+ DynamicIslandArtworkSourceView(
+ cornerRadius: albumArtCornerRadius,
+ contentMode: .fill
+ )
)
.clipped()
- .clipShape(RoundedRectangle(cornerRadius: 12))
- .scaleEffect(x: 1.3, y: 1.4)
+ .clipShape(RoundedRectangle(cornerRadius: albumArtCornerRadius))
+ .scaleEffect(x: 1.08, y: 1.1)
.rotationEffect(.degrees(92))
- .blur(radius: 35)
- .opacity(min(0.6, 1 - max(musicManager.albumArt.getBrightness(), 0.3)))
+ .blur(radius: 16)
+ .opacity(
+ usesLiveCanvasArtwork
+ ? (musicManager.isPlaying ? 0.55 : 0.18)
+ : min(0.45, 1 - max(musicManager.albumArt.getBrightness(), 0.3))
+ )
+ .shadow(
+ color: Color(nsColor: musicManager.avgColor).opacity(usesLiveCanvasArtwork ? 0.22 : 0.12),
+ radius: usesLiveCanvasArtwork ? 10 : 7,
+ x: 0,
+ y: 0
+ )
}
private var albumArtButton: some View {
@@ -1147,13 +1168,10 @@ struct MinimalisticAlbumArtView: View {
Color.clear
.aspectRatio(1, contentMode: .fit)
.background(
-
- Image(nsImage: musicManager.albumArt)
- .resizable()
- .aspectRatio(contentMode: .fit)
- .clipShape(RoundedRectangle(cornerRadius: musicManager.albumArt.size.width/musicManager.albumArt.size.height > 1.0 ? 4 : 12))
-
-
+ DynamicIslandArtworkSourceView(
+ cornerRadius: albumArtCornerRadius,
+ contentMode: .fit
+ )
)
.clipped()
.matchedGeometryEffect(id: "albumArt", in: albumArtNamespace)
diff --git a/DynamicIsland/components/Notch/NotchHomeView.swift b/DynamicIsland/components/Notch/NotchHomeView.swift
index 9d10cd5..da388b2 100644
--- a/DynamicIsland/components/Notch/NotchHomeView.swift
+++ b/DynamicIsland/components/Notch/NotchHomeView.swift
@@ -23,6 +23,129 @@
import Combine
import Defaults
import SwiftUI
+import AppKit
+import AVFoundation
+
+private final class DynamicIslandArtworkLoopController {
+ let player: AVQueuePlayer
+ private var looper: AVPlayerLooper?
+ private var playbackStateCancellable: AnyCancellable?
+
+ init(url: URL) {
+ let item = AVPlayerItem(url: url)
+ player = AVQueuePlayer()
+ player.isMuted = true
+ player.actionAtItemEnd = .none
+ looper = AVPlayerLooper(player: player, templateItem: item)
+
+ if MusicManager.shared.isPlaying {
+ player.play()
+ }
+
+ playbackStateCancellable = MusicManager.shared.$isPlaying
+ .removeDuplicates()
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] isPlaying in
+ guard let self else { return }
+ if isPlaying {
+ self.player.play()
+ } else {
+ self.player.pause()
+ }
+ }
+ }
+
+ deinit {
+ player.pause()
+ looper = nil
+ playbackStateCancellable = nil
+ }
+}
+
+private final class DynamicIslandArtworkVideoContainerView: NSView {
+ let playerLayer = AVPlayerLayer()
+
+ override init(frame frameRect: NSRect) {
+ super.init(frame: frameRect)
+ wantsLayer = true
+ layer?.backgroundColor = NSColor.clear.cgColor
+ playerLayer.videoGravity = .resizeAspectFill
+ playerLayer.backgroundColor = NSColor.clear.cgColor
+ layer?.addSublayer(playerLayer)
+ }
+
+ required init?(coder: NSCoder) {
+ nil
+ }
+
+ override func layout() {
+ super.layout()
+ playerLayer.frame = bounds
+ }
+}
+
+private struct DynamicIslandArtworkVideoView: NSViewRepresentable {
+ let url: URL
+ let videoGravity: AVLayerVideoGravity
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator()
+ }
+
+ func makeNSView(context: Context) -> DynamicIslandArtworkVideoContainerView {
+ let view = DynamicIslandArtworkVideoContainerView(frame: .zero)
+ context.coordinator.attach(layer: view.playerLayer, url: url, gravity: videoGravity)
+ return view
+ }
+
+ func updateNSView(_ nsView: DynamicIslandArtworkVideoContainerView, context: Context) {
+ context.coordinator.attach(layer: nsView.playerLayer, url: url, gravity: videoGravity)
+ }
+
+ final class Coordinator {
+ private var controller: DynamicIslandArtworkLoopController?
+ private var currentURL: URL?
+
+ func attach(layer: AVPlayerLayer, url: URL, gravity: AVLayerVideoGravity) {
+ layer.videoGravity = gravity
+
+ if currentURL != url || controller == nil {
+ currentURL = url
+ controller = DynamicIslandArtworkLoopController(url: url)
+ }
+
+ if layer.player !== controller?.player {
+ layer.player = controller?.player
+ }
+ }
+ }
+}
+
+struct DynamicIslandArtworkSourceView: View {
+ @ObservedObject private var musicManager = MusicManager.shared
+ @Default(.showLiveCanvasInDynamicIsland) private var showLiveCanvasInDynamicIsland
+
+ let cornerRadius: CGFloat
+ let contentMode: ContentMode
+
+ private var liveCanvasURL: URL? {
+ guard showLiveCanvasInDynamicIsland else { return nil }
+ return musicManager.videoArtworkURL
+ }
+
+ var body: some View {
+ Group {
+ if let liveCanvasURL {
+ DynamicIslandArtworkVideoView(url: liveCanvasURL, videoGravity: .resizeAspectFill)
+ } else {
+ Image(nsImage: musicManager.albumArt)
+ .resizable()
+ .aspectRatio(contentMode: contentMode)
+ }
+ }
+ .clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
+ }
+}
// MARK: - Music Player Components
@@ -43,8 +166,23 @@ struct MusicPlayerView: View {
struct AlbumArtView: View {
@ObservedObject var musicManager = MusicManager.shared
@ObservedObject var vm: DynamicIslandViewModel
+ @Default(.showLiveCanvasInDynamicIsland) private var showLiveCanvasInDynamicIsland
let albumArtNamespace: Namespace.ID
+ private var usesLiveCanvasArtwork: Bool {
+ showLiveCanvasInDynamicIsland && musicManager.videoArtworkURL != nil
+ }
+
+ private var albumArtCornerRadius: CGFloat {
+ Defaults[.cornerRadiusScaling]
+ ? musicManager.albumArt.size.width / musicManager.albumArt.size.height > 1.0
+ ? MusicPlayerImageSizes.cornerRadiusInset.opened / 3
+ : MusicPlayerImageSizes.cornerRadiusInset.opened
+ : musicManager.albumArt.size.width / musicManager.albumArt.size.height > 1.0
+ ? MusicPlayerImageSizes.cornerRadiusInset.closed / 3
+ : MusicPlayerImageSizes.cornerRadiusInset.closed
+ }
+
var body: some View {
ZStack(alignment: .bottomTrailing) {
if Defaults[.lightingEffect] {
@@ -58,24 +196,26 @@ struct AlbumArtView: View {
Color.clear
.aspectRatio(1, contentMode: .fit)
.background(
- Image(nsImage: musicManager.albumArt)
- .resizable()
- .aspectRatio(contentMode: .fit)
- .clipShape(
- RoundedRectangle(
- cornerRadius: Defaults[.cornerRadiusScaling]
- ? musicManager.albumArt.size.width/musicManager.albumArt.size.height > 1.0 ? MusicPlayerImageSizes.cornerRadiusInset.opened/3 : MusicPlayerImageSizes.cornerRadiusInset.opened
- : musicManager.albumArt.size.width/musicManager.albumArt.size.height > 1.0 ? MusicPlayerImageSizes.cornerRadiusInset.closed/3 : MusicPlayerImageSizes.cornerRadiusInset.closed
-
-
- )
- )
+ DynamicIslandArtworkSourceView(
+ cornerRadius: albumArtCornerRadius,
+ contentMode: .fill
+ )
)
.clipped()
.scaleEffect(x: 1.3, y: 1.4)
.rotationEffect(.degrees(92))
.blur(radius: 40)
- .opacity(musicManager.isPlaying ? 0.5 : 0)
+ .opacity(
+ usesLiveCanvasArtwork
+ ? (musicManager.isPlaying ? 0.62 : 0.18)
+ : (musicManager.isPlaying ? 0.5 : 0)
+ )
+ .shadow(
+ color: Color(nsColor: musicManager.avgColor).opacity(usesLiveCanvasArtwork ? 0.24 : 0.16),
+ radius: usesLiveCanvasArtwork ? 22 : 14,
+ x: 0,
+ y: 0
+ )
}
private var albumArtButton: some View {
@@ -111,18 +251,10 @@ struct AlbumArtView: View {
Color.clear
.aspectRatio(1, contentMode: .fit)
.overlay {
- Image(nsImage: musicManager.albumArt)
- .resizable()
- .aspectRatio(contentMode: .fit)
- .clipShape(
- RoundedRectangle(
- cornerRadius: Defaults[.cornerRadiusScaling]
- ? musicManager.albumArt.size.width/musicManager.albumArt.size.height > 1.0 ? MusicPlayerImageSizes.cornerRadiusInset.opened/3 : MusicPlayerImageSizes.cornerRadiusInset.opened
- : musicManager.albumArt.size.width/musicManager.albumArt.size.height > 1.0 ? MusicPlayerImageSizes.cornerRadiusInset.closed/3 : MusicPlayerImageSizes.cornerRadiusInset.closed
-
-
- )
- ) // ensures that even the rectagular album cover is rounded just like the MacOS media player
+ DynamicIslandArtworkSourceView(
+ cornerRadius: albumArtCornerRadius,
+ contentMode: .fit
+ )
}
.matchedGeometryEffect(id: "albumArt", in: albumArtNamespace)
.clipped()
diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift
index 27c6d1a..4c23d3d 100644
--- a/DynamicIsland/components/Settings/SettingsView.swift
+++ b/DynamicIsland/components/Settings/SettingsView.swift
@@ -731,6 +731,19 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .battery, title: "Show power status notifications", keywords: ["notifications", "power"], highlightID: SettingsTab.battery.highlightID(for: "Show power status notifications")),
SettingsSearchEntry(tab: .battery, title: "Show power status icons", keywords: ["power icons", "charging icon"], highlightID: SettingsTab.battery.highlightID(for: "Show power status icons")),
SettingsSearchEntry(tab: .battery, title: "Play low battery alert sound", keywords: ["low battery", "alert", "sound"], highlightID: SettingsTab.battery.highlightID(for: "Play low battery alert sound")),
+ SettingsSearchEntry(tab: .battery, title: "Charging HUD", keywords: ["battery", "charging", "temporary activity"], highlightID: SettingsTab.battery.highlightID(for: "Charging HUD")),
+ SettingsSearchEntry(tab: .battery, title: "Low battery HUD", keywords: ["battery", "low", "temporary activity"], highlightID: SettingsTab.battery.highlightID(for: "Low battery HUD")),
+ SettingsSearchEntry(tab: .battery, title: "Fully charged HUD", keywords: ["battery", "full", "temporary activity"], highlightID: SettingsTab.battery.highlightID(for: "Fully charged HUD")),
+ SettingsSearchEntry(tab: .battery, title: "Charging duration", keywords: ["charging", "duration", "seconds"], highlightID: SettingsTab.battery.highlightID(for: "Charging duration")),
+ SettingsSearchEntry(tab: .battery, title: "Low battery duration", keywords: ["low battery", "duration", "seconds"], highlightID: SettingsTab.battery.highlightID(for: "Low battery duration")),
+ SettingsSearchEntry(tab: .battery, title: "Full battery duration", keywords: ["full battery", "duration", "seconds"], highlightID: SettingsTab.battery.highlightID(for: "Full battery duration")),
+ SettingsSearchEntry(tab: .battery, title: "Test charging HUD", keywords: ["battery", "test", "charging", "preview"], highlightID: nil),
+ SettingsSearchEntry(tab: .battery, title: "Test low battery HUD", keywords: ["battery", "test", "low", "preview"], highlightID: nil),
+ SettingsSearchEntry(tab: .battery, title: "Test full battery HUD", keywords: ["battery", "test", "full", "preview"], highlightID: nil),
+ SettingsSearchEntry(tab: .battery, title: "Low battery style", keywords: ["battery", "style", "compact", "standard"], highlightID: SettingsTab.battery.highlightID(for: "Low battery style")),
+ SettingsSearchEntry(tab: .battery, title: "Low battery threshold", keywords: ["battery", "threshold", "percent"], highlightID: SettingsTab.battery.highlightID(for: "Low battery threshold")),
+ SettingsSearchEntry(tab: .battery, title: "Full battery style", keywords: ["battery", "style", "compact", "standard"], highlightID: SettingsTab.battery.highlightID(for: "Full battery style")),
+ SettingsSearchEntry(tab: .battery, title: "Full charge threshold", keywords: ["battery", "threshold", "full"], highlightID: SettingsTab.battery.highlightID(for: "Full charge threshold")),
// HUDs
SettingsSearchEntry(tab: .devices, title: "Show Bluetooth device connections", keywords: ["bluetooth", "hud"], highlightID: SettingsTab.devices.highlightID(for: "Show Bluetooth device connections")),
@@ -754,6 +767,10 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .hudAndOSD, title: "Keyboard Backlight OSD", keywords: ["keyboard", "backlight", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Keyboard Backlight OSD")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Material", keywords: ["material", "frosted", "liquid", "glass", "solid", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Material")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Icon & Progress Color", keywords: ["color", "icon", "white", "black", "gray", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Icon & Progress Color")),
+ SettingsSearchEntry(tab: .hudAndOSD, title: "Volume step", keywords: ["volume", "step", "percent"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Volume step")),
+ SettingsSearchEntry(tab: .hudAndOSD, title: "Volume fine step", keywords: ["volume", "fine", "step", "percent"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Volume fine step")),
+ SettingsSearchEntry(tab: .hudAndOSD, title: "Brightness step", keywords: ["brightness", "step", "percent"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Brightness step")),
+ SettingsSearchEntry(tab: .hudAndOSD, title: "Brightness fine step", keywords: ["brightness", "fine", "step", "percent"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Brightness fine step")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Third-party DDC app integration", keywords: ["ddc", "third party", "external", "display", "betterdisplay", "lunar"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Third-party DDC app integration")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Third-party DDC provider", keywords: ["provider", "betterdisplay", "lunar", "integration", "refresh detection"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Third-party DDC provider")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Enable external volume control listener", keywords: ["external volume", "ddc volume", "betterdisplay volume", "lunar volume", "disable native volume"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Enable external volume control listener")),
@@ -763,6 +780,7 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .media, title: "Skip buttons", keywords: ["skip", "controls", "±10"], highlightID: SettingsTab.media.highlightID(for: "Skip buttons")),
SettingsSearchEntry(tab: .media, title: "Sneak Peek Style", keywords: ["sneak peek", "preview"], highlightID: SettingsTab.media.highlightID(for: "Sneak Peek Style")),
SettingsSearchEntry(tab: .media, title: "Enable lyrics", keywords: ["lyrics", "song text"], highlightID: SettingsTab.media.highlightID(for: "Enable lyrics")),
+ SettingsSearchEntry(tab: .media, title: "Show live canvas in Dynamic Island", keywords: ["canvas", "live canvas", "album art", "dynamic island", "spotify canvas"], highlightID: SettingsTab.media.highlightID(for: "Show live canvas in Dynamic Island")),
SettingsSearchEntry(tab: .media, title: "Auto-hide inactive notch media player", keywords: ["auto hide", "inactive", "placeholder", "notch media"], highlightID: SettingsTab.media.highlightID(for: "Auto-hide inactive notch media player")),
SettingsSearchEntry(tab: .media, title: "Show Change Media Output control", keywords: ["airplay", "route picker", "media output"], highlightID: SettingsTab.media.highlightID(for: "Show Change Media Output control")),
SettingsSearchEntry(tab: .media, title: "Enable album art parallax", keywords: ["parallax", "lock screen", "album art"], highlightID: SettingsTab.media.highlightID(for: "Enable album art parallax")),
@@ -1305,10 +1323,62 @@ struct GeneralSettings: View {
}
struct Charge: View {
+ @ObservedObject private var batteryStatusViewModel = BatteryStatusViewModel.shared
+ @Default(.showPowerStatusNotifications) private var showPowerStatusNotifications
+ @Default(.showChargingBatteryHUD) private var showChargingBatteryHUD
+ @Default(.showLowBatteryHUD) private var showLowBatteryHUD
+ @Default(.showFullBatteryHUD) private var showFullBatteryHUD
+ @Default(.chargingBatteryHUDDuration) private var chargingBatteryHUDDuration
+ @Default(.lowBatteryHUDDuration) private var lowBatteryHUDDuration
+ @Default(.fullBatteryHUDDuration) private var fullBatteryHUDDuration
+ @Default(.lowBatteryHUDThreshold) private var lowBatteryHUDThreshold
+ @Default(.fullBatteryHUDThreshold) private var fullBatteryHUDThreshold
+ @Default(.lowBatteryHUDStyle) private var lowBatteryHUDStyle
+ @Default(.fullBatteryHUDStyle) private var fullBatteryHUDStyle
+
private func highlightID(_ title: String) -> String {
SettingsTab.battery.highlightID(for: title)
}
+ private var chargingDurationBinding: Binding {
+ Binding(
+ get: { Double(chargingBatteryHUDDuration) },
+ set: { chargingBatteryHUDDuration = Int($0.rounded()) }
+ )
+ }
+
+ private var lowBatteryDurationBinding: Binding {
+ Binding(
+ get: { Double(lowBatteryHUDDuration) },
+ set: { lowBatteryHUDDuration = Int($0.rounded()) }
+ )
+ }
+
+ private var fullBatteryDurationBinding: Binding {
+ Binding(
+ get: { Double(fullBatteryHUDDuration) },
+ set: { fullBatteryHUDDuration = Int($0.rounded()) }
+ )
+ }
+
+ private var lowBatteryThresholdBinding: Binding {
+ Binding(
+ get: { Double(lowBatteryHUDThreshold) },
+ set: { lowBatteryHUDThreshold = Int($0.rounded()) }
+ )
+ }
+
+ private var fullBatteryThresholdBinding: Binding {
+ Binding(
+ get: { Double(fullBatteryHUDThreshold) },
+ set: { fullBatteryHUDThreshold = Int($0.rounded()) }
+ )
+ }
+
+ private func sectionOpacity(_ isEnabled: Bool) -> Double {
+ isEnabled ? 1 : 0.5
+ }
+
var body: some View {
Form {
if BatteryActivityManager.shared.hasBattery() {
@@ -1340,6 +1410,157 @@ struct Charge: View {
} header: {
Text("Battery Information")
}
+ Section {
+ Defaults.Toggle(key: .showChargingBatteryHUD) {
+ Text("Charging HUD")
+ }
+ .settingsHighlight(id: highlightID("Charging HUD"))
+
+ Defaults.Toggle(key: .showLowBatteryHUD) {
+ Text("Low battery HUD")
+ }
+ .settingsHighlight(id: highlightID("Low battery HUD"))
+
+ Defaults.Toggle(key: .showFullBatteryHUD) {
+ Text("Fully charged HUD")
+ }
+ .settingsHighlight(id: highlightID("Fully charged HUD"))
+ } header: {
+ Text("Battery HUDs")
+ } footer: {
+ Text("These temporary HUDs recreate the charging, low-battery, and full-battery notch alerts.")
+ }
+ Section {
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text("Charging duration")
+ Spacer()
+ Text("\(chargingBatteryHUDDuration)s")
+ .foregroundStyle(.secondary)
+ }
+ Slider(value: chargingDurationBinding, in: 1...10, step: 1)
+ }
+ .settingsHighlight(id: highlightID("Charging duration"))
+ .disabled(!showPowerStatusNotifications || !showChargingBatteryHUD)
+ .opacity(sectionOpacity(showPowerStatusNotifications && showChargingBatteryHUD))
+
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text("Low battery duration")
+ Spacer()
+ Text("\(lowBatteryHUDDuration)s")
+ .foregroundStyle(.secondary)
+ }
+ Slider(value: lowBatteryDurationBinding, in: 1...10, step: 1)
+ }
+ .settingsHighlight(id: highlightID("Low battery duration"))
+ .disabled(!showPowerStatusNotifications || !showLowBatteryHUD)
+ .opacity(sectionOpacity(showPowerStatusNotifications && showLowBatteryHUD))
+
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text("Full battery duration")
+ Spacer()
+ Text("\(fullBatteryHUDDuration)s")
+ .foregroundStyle(.secondary)
+ }
+ Slider(value: fullBatteryDurationBinding, in: 1...10, step: 1)
+ }
+ .settingsHighlight(id: highlightID("Full battery duration"))
+ .disabled(!showPowerStatusNotifications || !showFullBatteryHUD)
+ .opacity(sectionOpacity(showPowerStatusNotifications && showFullBatteryHUD))
+ } header: {
+ Text("HUD Duration")
+ }
+ Section {
+ Button {
+ batteryStatusViewModel.triggerTestHUD(kind: .charging)
+ } label: {
+ Label("Test charging HUD", systemImage: "bolt.fill")
+ }
+ .disabled(!showPowerStatusNotifications || !showChargingBatteryHUD)
+
+ Button {
+ batteryStatusViewModel.triggerTestHUD(kind: .lowBattery)
+ } label: {
+ Label("Test low battery HUD", systemImage: "battery.25")
+ }
+ .disabled(!showPowerStatusNotifications || !showLowBatteryHUD)
+
+ Button {
+ batteryStatusViewModel.triggerTestHUD(kind: .fullBattery)
+ } label: {
+ Label("Test full battery HUD", systemImage: "battery.100")
+ }
+ .disabled(!showPowerStatusNotifications || !showFullBatteryHUD)
+ } header: {
+ Text("HUD Tests")
+ } footer: {
+ Text("Runs the real notch animation on the current target display. If an external screen is using Dynamic Island mode, the battery HUD is sent there first.")
+ }
+ Section {
+ VStack(alignment: .leading, spacing: 10) {
+ Picker("Low battery style", selection: $lowBatteryHUDStyle) {
+ ForEach(BatteryNotificationStyle.allCases) { style in
+ Text(style.title)
+ .tag(style)
+ }
+ }
+ .pickerStyle(.segmented)
+ Text("Compact matches the charging HUD. Standard uses the expanded DynamicNotch-style card.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .settingsHighlight(id: highlightID("Low battery style"))
+
+
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text("Low battery threshold")
+ Spacer()
+ Text("\(lowBatteryHUDThreshold)%")
+ .foregroundStyle(.secondary)
+ }
+ Slider(value: lowBatteryThresholdBinding, in: 5...30, step: 1)
+ }
+ .settingsHighlight(id: highlightID("Low battery threshold"))
+ } header: {
+ Text("Low Battery")
+ }
+ .disabled(!showPowerStatusNotifications || !showLowBatteryHUD)
+ .opacity(sectionOpacity(showPowerStatusNotifications && showLowBatteryHUD))
+
+ Section {
+ VStack(alignment: .leading, spacing: 10) {
+ Picker("Full battery style", selection: $fullBatteryHUDStyle) {
+ ForEach(BatteryNotificationStyle.allCases) { style in
+ Text(style.title)
+ .tag(style)
+ }
+ }
+ .pickerStyle(.segmented)
+ Text("Compact keeps the alert inline. Standard uses the taller full-charge HUD with the charging animation.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .settingsHighlight(id: highlightID("Full battery style"))
+
+
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Text("Full charge threshold")
+ Spacer()
+ Text("\(fullBatteryHUDThreshold)%")
+ .foregroundStyle(.secondary)
+ }
+ Slider(value: fullBatteryThresholdBinding, in: 80...100, step: 1)
+ }
+ .settingsHighlight(id: highlightID("Full charge threshold"))
+ } header: {
+ Text("Full Battery")
+ }
+ .disabled(!showPowerStatusNotifications || !showFullBatteryHUD)
+ .opacity(sectionOpacity(showPowerStatusNotifications && showFullBatteryHUD))
} else {
ContentUnavailableView {
VStack(spacing: 16) {
@@ -1997,6 +2218,10 @@ private struct ExternalDisplayIntegrationsSection: View {
@Default(.enableThirdPartyDDCIntegration) var enableThirdPartyDDCIntegration
@Default(.thirdPartyDDCProvider) var thirdPartyDDCProvider
@Default(.enableExternalVolumeControlListener) var enableExternalVolumeControlListener
+ @Default(.volumeStepPercent) var volumeStepPercent
+ @Default(.volumeFineStepPercent) var volumeFineStepPercent
+ @Default(.brightnessStepPercent) var brightnessStepPercent
+ @Default(.brightnessFineStepPercent) var brightnessFineStepPercent
@ObservedObject private var betterDisplayManager = BetterDisplayManager.shared
@ObservedObject private var lunarManager = LunarManager.shared
@@ -2067,6 +2292,74 @@ private struct ExternalDisplayIntegrationsSection: View {
var body: some View {
Form {
+ Section {
+ Stepper(value: $volumeStepPercent, in: 1...25) {
+ HStack {
+ Text("Volume step")
+ Spacer()
+ Text("\(volumeStepPercent)%")
+ .foregroundStyle(.secondary)
+ .monospacedDigit()
+ }
+ }
+ .settingsHighlight(id: highlightID("Volume step"))
+ .disabled(enableExternalVolumeControlListener)
+
+ Stepper(value: $volumeFineStepPercent, in: 1...25) {
+ HStack {
+ Text("Volume fine step")
+ Spacer()
+ Text("\(volumeFineStepPercent)%")
+ .foregroundStyle(.secondary)
+ .monospacedDigit()
+ }
+ }
+ .settingsHighlight(id: highlightID("Volume fine step"))
+ .disabled(enableExternalVolumeControlListener)
+
+ if enableExternalVolumeControlListener {
+ Text("Disabled while external display volume integration is active.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+
+ Stepper(value: $brightnessStepPercent, in: 1...25) {
+ HStack {
+ Text("Brightness step")
+ Spacer()
+ Text("\(brightnessStepPercent)%")
+ .foregroundStyle(.secondary)
+ .monospacedDigit()
+ }
+ }
+ .settingsHighlight(id: highlightID("Brightness step"))
+ .disabled(enableThirdPartyDDCIntegration)
+
+ Stepper(value: $brightnessFineStepPercent, in: 1...25) {
+ HStack {
+ Text("Brightness fine step")
+ Spacer()
+ Text("\(brightnessFineStepPercent)%")
+ .foregroundStyle(.secondary)
+ .monospacedDigit()
+ }
+ }
+ .settingsHighlight(id: highlightID("Brightness fine step"))
+ .disabled(enableThirdPartyDDCIntegration)
+
+ if enableThirdPartyDDCIntegration {
+ Text("Disabled while external display brightness integration is active.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ } header: {
+ Text("Step size")
+ } footer: {
+ Text("Percent change per key press. Fine step applies when holding Shift+Option.")
+ .foregroundStyle(.secondary)
+ .font(.caption)
+ }
+
Section {
Toggle("Enable third-party DDC app integration", isOn: $enableThirdPartyDDCIntegration)
.settingsHighlight(id: highlightID("Third-party DDC app integration"))
@@ -2450,6 +2743,7 @@ struct Media: View {
@Default(.lockScreenGlassStyle) private var lockScreenGlassStyle
@Default(.lockScreenGlassCustomizationMode) private var lockScreenGlassCustomizationMode
@Default(.lockScreenMusicAlbumParallaxEnabled) private var lockScreenMusicAlbumParallaxEnabled
+ @Default(.lockScreenMusicFullscreenArtworkEnabled) private var lockScreenMusicFullscreenArtworkEnabled
@Default(.showStandardMediaControls) private var showStandardMediaControls
@Default(.autoHideInactiveNotchMediaPlayer) private var autoHideInactiveNotchMediaPlayer
@Default(.parallaxEffectIntensity) private var parallaxEffectIntensity
@@ -2505,6 +2799,11 @@ struct Media: View {
.font(.caption)
}
}
+
+ if mediaController == .spotify {
+ SpotifyAuthSettingsSection()
+ }
+
Section {
Defaults.Toggle(key: .showStandardMediaControls) {
Text("Show media controls in Dynamic Island")
@@ -2603,6 +2902,11 @@ struct Media: View {
Text("Enable lyrics")
}
.settingsHighlight(id: highlightID("Enable lyrics"))
+ Defaults.Toggle(key: .showLiveCanvasInDynamicIsland) {
+ Text("Show live canvas in Dynamic Island")
+ }
+ .settingsHighlight(id: highlightID("Show live canvas in Dynamic Island"))
+ .help("Replaces the artwork tile with the live canvas when the current app provides one, and reuses that moving canvas for the surrounding lighting effect.")
//Parallax Effect Intensity to control how much parallax is wanted
Slider(value: $parallaxEffectIntensity, in: 0...12, step: 1.0) {
@@ -2696,6 +3000,27 @@ struct Media: View {
.opacity(enableLockScreenMediaWidget ? 1 : 0.5)
.settingsHighlight(id: highlightID("Enable media panel blur"))
}
+ VStack(alignment: .leading, spacing: 4) {
+ Defaults.Toggle(key: .lockScreenMusicFullscreenArtworkEnabled) {
+ Text("Fullscreen artwork on right-click")
+ }
+ .disabled(!enableLockScreenMediaWidget)
+ .settingsHighlight(id: highlightID("Fullscreen artwork on right-click"))
+ Defaults.Toggle(key: .lockScreenUseArtworkLayoutOverFullscreenCanvas) {
+ Text("Use album art layout over fullscreen canvas")
+ }
+ .disabled(!enableLockScreenMediaWidget || !lockScreenMusicFullscreenArtworkEnabled)
+ .settingsHighlight(id: highlightID("Use album art layout over fullscreen canvas"))
+ Defaults.Toggle(key: .lockScreenKeepAlbumArtVisibleDuringFullscreenArtwork) {
+ Text("Keep album art visible during fullscreen artwork")
+ }
+ .disabled(!enableLockScreenMediaWidget || !lockScreenMusicFullscreenArtworkEnabled)
+ .settingsHighlight(id: highlightID("Keep album art visible during fullscreen artwork"))
+ Text("Right-click the album art on the lock screen to set it as the wallpaper. Right-click again or click the background to restore the original wallpaper. If a canvas is available, Atoll can also keep the same album art + player layout on top of the live canvas.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+ }
} header: {
Text("Lock Screen Integration")
} footer: {
@@ -4576,6 +4901,7 @@ struct LockScreenSettings: View {
@Default(.lockScreenTimerGlassCustomizationMode) private var lockScreenTimerGlassCustomizationMode
@Default(.lockScreenTimerWidgetUsesBlur) private var timerGlassModeIsGlass
@Default(.enableLockScreenMediaWidget) private var enableLockScreenMediaWidget
+ @Default(.lockScreenMusicFullscreenArtworkEnabled) private var lockScreenMusicFullscreenArtworkEnabled
@Default(.enableLockScreenTimerWidget) private var enableLockScreenTimerWidget
@Default(.enableLockScreenWeatherWidget) private var enableLockScreenWeatherWidget
@Default(.enableLockScreenFocusWidget) private var enableLockScreenFocusWidget
@@ -4809,6 +5135,27 @@ struct LockScreenSettings: View {
.opacity(enableLockScreenMediaWidget ? 1 : 0.5)
.settingsHighlight(id: highlightID("Enable media panel blur"))
}
+ VStack(alignment: .leading, spacing: 4) {
+ Defaults.Toggle(key: .lockScreenMusicFullscreenArtworkEnabled) {
+ Text("Fullscreen artwork on right-click")
+ }
+ .disabled(!enableLockScreenMediaWidget)
+ .settingsHighlight(id: highlightID("Fullscreen artwork on right-click"))
+ Defaults.Toggle(key: .lockScreenUseArtworkLayoutOverFullscreenCanvas) {
+ Text("Use album art layout over fullscreen canvas")
+ }
+ .disabled(!enableLockScreenMediaWidget || !lockScreenMusicFullscreenArtworkEnabled)
+ .settingsHighlight(id: highlightID("Use album art layout over fullscreen canvas"))
+ Defaults.Toggle(key: .lockScreenKeepAlbumArtVisibleDuringFullscreenArtwork) {
+ Text("Keep album art visible during fullscreen artwork")
+ }
+ .disabled(!enableLockScreenMediaWidget || !lockScreenMusicFullscreenArtworkEnabled)
+ .settingsHighlight(id: highlightID("Keep album art visible during fullscreen artwork"))
+ Text("Right-click the album art on the lock screen to set it as the wallpaper. Right-click again or click the background to restore the original wallpaper. If a canvas is available, Atoll can also keep the same album art + player layout on top of the live canvas.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+ }
if !showStandardMediaControls {
Text("Enable Dynamic Island media controls to manage the lock screen panel.")
diff --git a/DynamicIsland/components/Settings/SpotifyAuthSettingsSection.swift b/DynamicIsland/components/Settings/SpotifyAuthSettingsSection.swift
new file mode 100644
index 0000000..890e14a
--- /dev/null
+++ b/DynamicIsland/components/Settings/SpotifyAuthSettingsSection.swift
@@ -0,0 +1,129 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import AppKit
+import Defaults
+import SwiftUI
+
+struct SpotifyAuthSettingsSection: View {
+ @Default(.spotifySPDCCookie) private var spotifySPDCCookie
+ @ObservedObject private var spotifyAuthManager = SpotifyAuthManager.shared
+ @State private var showingLoginSheet = false
+
+ private var hasCookie: Bool {
+ !SpotifyAuthManager.sanitizeCookie(spotifySPDCCookie).isEmpty
+ }
+
+ var body: some View {
+ Section {
+ VStack(alignment: .leading, spacing: 8) {
+ Text("Sign in to Spotify to capture the `sp_dc` cookie automatically, or paste it in below.")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+
+ Button {
+ showingLoginSheet = true
+ } label: {
+ Label("Sign in with Spotify", systemImage: "person.crop.circle.badge.checkmark")
+ }
+ .buttonStyle(.borderedProminent)
+
+ TextField("sp_dc cookie", text: $spotifySPDCCookie, axis: .vertical)
+ .textFieldStyle(.roundedBorder)
+ .font(.caption.monospaced())
+ .lineLimit(2...4)
+ .textSelection(.enabled)
+ }
+
+ HStack(spacing: 10) {
+ Circle()
+ .fill(spotifyAuthManager.isAuthenticated ? Color.green : (hasCookie ? Color.orange : Color.secondary))
+ .frame(width: 8, height: 8)
+
+ Text(spotifyAuthManager.sessionStatusText)
+ .foregroundStyle(.secondary)
+ }
+
+ if let authErrorMessage = spotifyAuthManager.authErrorMessage, !authErrorMessage.isEmpty {
+ Text(authErrorMessage)
+ .font(.caption)
+ .foregroundStyle(.red)
+ }
+
+ HStack {
+ Button("Paste from Clipboard") {
+ pasteCookieFromClipboard()
+ }
+
+ Button(spotifyAuthManager.isAuthorizing ? "Validating..." : "Validate Cookie") {
+ spotifySPDCCookie = SpotifyAuthManager.sanitizeCookie(spotifySPDCCookie)
+ Task {
+ await spotifyAuthManager.validateSession()
+ }
+ }
+ .disabled(!hasCookie || spotifyAuthManager.isAuthorizing)
+
+ Button("Clear") {
+ spotifySPDCCookie = ""
+ spotifyAuthManager.clearSession()
+ }
+ .disabled(!hasCookie && !spotifyAuthManager.isAuthenticated)
+ }
+
+ DisclosureGroup("Get the cookie manually") {
+ VStack(alignment: .leading, spacing: 6) {
+ Text("1. Open Spotify in a browser and log in")
+ .font(.caption)
+ Text("2. Developer Tools -> Application/Storage -> Cookies -> https://open.spotify.com")
+ .font(.caption)
+ Text("3. Copy the value of `sp_dc` and paste it here")
+ .font(.caption)
+
+ HStack(spacing: 12) {
+ Link("Open Spotify Web Player", destination: URL(string: "https://open.spotify.com")!)
+ Link("Method source", destination: URL(string: "https://github.com/Paxsenix0/Spotify-Canvas-API")!)
+ }
+ .font(.caption)
+ }
+ .padding(.top, 4)
+ }
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ } header: {
+ Text("Spotify Canvas Session")
+ } footer: {
+ Text("Atoll uses the local `sp_dc` cookie only to request Spotify's internal web-player token and fetch the matching Canvas for the current track.")
+ .foregroundStyle(.secondary)
+ .font(.caption)
+ }
+ .sheet(isPresented: $showingLoginSheet) {
+ SpotifyLoginSheet { capturedValue in
+ let sanitized = SpotifyAuthManager.sanitizeCookie(capturedValue)
+ spotifySPDCCookie = sanitized
+ Task {
+ await spotifyAuthManager.validateSession()
+ }
+ }
+ }
+ }
+
+ private func pasteCookieFromClipboard() {
+ guard let clipboardText = NSPasteboard.general.string(forType: .string) else { return }
+ spotifySPDCCookie = SpotifyAuthManager.sanitizeCookie(clipboardText)
+ }
+}
diff --git a/DynamicIsland/components/Settings/SpotifyLoginSheet.swift b/DynamicIsland/components/Settings/SpotifyLoginSheet.swift
new file mode 100644
index 0000000..7eecc8d
--- /dev/null
+++ b/DynamicIsland/components/Settings/SpotifyLoginSheet.swift
@@ -0,0 +1,272 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import AppKit
+import os
+import SwiftUI
+@preconcurrency import WebKit
+
+private enum SpotifyLoginConstants {
+ static let loginURL = URL(string: "https://accounts.spotify.com/en/login")!
+ static let externalLoginURL = URL(string: "https://open.spotify.com/")!
+ static let cookieInstructionsURL = URL(
+ string: "https://github.com/Paxsenix0/Spotify-Canvas-API#how-to-get-sp_dc-cookie"
+ )!
+ static let cookieDomainSuffix = "spotify.com"
+ static let cookieName = "sp_dc"
+ static let userAgent =
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
+ + "(KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
+ static let blockedOAuthHostSuffixes = ["accounts.google.com", "accounts.youtube.com"]
+ static let googleSignInRedirectURL = URL(string: "https://accounts.spotify.com/en/login")!
+}
+
+private let spotifyLoginLogger = os.Logger(subsystem: "com.Ebullioscopic.Atoll", category: "SpotifyLogin")
+
+struct SpotifyLoginSheet: View {
+ @Environment(\.dismiss) private var dismiss
+
+ let onCapture: (String) -> Void
+
+ @State private var statusText: String =
+ "Sign in with email & password, Apple, or Facebook. Google blocks in-app sign-in — use “Open in Browser” for Google accounts."
+ @State private var didCapture = false
+
+ var body: some View {
+ VStack(spacing: 0) {
+ HStack {
+ VStack(alignment: .leading, spacing: 2) {
+ Text("Connect Spotify")
+ .font(.headline)
+ Text(statusText)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ Spacer()
+ Button {
+ NSWorkspace.shared.open(SpotifyLoginConstants.externalLoginURL)
+ } label: {
+ Label("Open in Browser", systemImage: "safari")
+ }
+ .help("Spotify often blocks in-app logins. Sign in via your normal browser, then paste sp_dc in Settings.")
+ Button("Reset Session") {
+ NotificationCenter.default.post(name: .spotifyLoginSheetReset, object: nil)
+ statusText = "Session cleared. Sign in again to capture the cookie."
+ }
+ Button("Cancel") { dismiss() }
+ .keyboardShortcut(.cancelAction)
+ }
+ .padding(12)
+ Divider()
+ SpotifyLoginWebView { value in
+ guard !didCapture else { return }
+ didCapture = true
+ statusText = "Captured sp_dc cookie. Validating…"
+ onCapture(value)
+ dismiss()
+ } onStatus: { message in
+ statusText = message
+ }
+ }
+ .frame(minWidth: 520, minHeight: 640)
+ }
+}
+
+extension Notification.Name {
+ static let spotifyLoginSheetReset = Notification.Name("SpotifyLoginSheetReset")
+}
+
+struct SpotifyLoginWebView: NSViewRepresentable {
+ let onCapture: (String) -> Void
+ let onStatus: (String) -> Void
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator(onCapture: onCapture, onStatus: onStatus)
+ }
+
+ func makeNSView(context: Context) -> WKWebView {
+ let configuration = WKWebViewConfiguration()
+ configuration.websiteDataStore = .nonPersistent()
+ configuration.preferences.javaScriptCanOpenWindowsAutomatically = false
+
+ let webView = FirstClickWebView(frame: .zero, configuration: configuration)
+ webView.customUserAgent = SpotifyLoginConstants.userAgent
+ webView.navigationDelegate = context.coordinator
+ webView.uiDelegate = context.coordinator
+ webView.allowsBackForwardNavigationGestures = true
+
+ context.coordinator.attach(webView: webView)
+ webView.load(URLRequest(url: SpotifyLoginConstants.loginURL))
+ DispatchQueue.main.async { [weak webView] in
+ webView?.window?.makeFirstResponder(webView)
+ }
+ return webView
+ }
+
+ func updateNSView(_ nsView: WKWebView, context: Context) {}
+
+ static func dismantleNSView(_ nsView: WKWebView, coordinator: Coordinator) {
+ coordinator.detach()
+ }
+
+ private final class FirstClickWebView: WKWebView {
+ override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
+ }
+
+ @MainActor
+ final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate, WKHTTPCookieStoreObserver {
+ private let onCapture: (String) -> Void
+ private let onStatus: (String) -> Void
+ private weak var webView: WKWebView?
+ private var resetObserver: NSObjectProtocol?
+
+ init(onCapture: @escaping (String) -> Void, onStatus: @escaping (String) -> Void) {
+ self.onCapture = onCapture
+ self.onStatus = onStatus
+ super.init()
+ resetObserver = NotificationCenter.default.addObserver(
+ forName: .spotifyLoginSheetReset,
+ object: nil,
+ queue: .main
+ ) { [weak self] _ in
+ Task { @MainActor in self?.resetSession() }
+ }
+ }
+
+ deinit {
+ if let resetObserver {
+ NotificationCenter.default.removeObserver(resetObserver)
+ }
+ }
+
+ func attach(webView: WKWebView) {
+ self.webView = webView
+ webView.configuration.websiteDataStore.httpCookieStore.add(self)
+ }
+
+ func detach() {
+ webView?.configuration.websiteDataStore.httpCookieStore.remove(self)
+ webView = nil
+ }
+
+ private func resetSession() {
+ guard let webView else { return }
+ let store = webView.configuration.websiteDataStore
+ let types = WKWebsiteDataStore.allWebsiteDataTypes()
+ store.removeData(ofTypes: types, modifiedSince: .distantPast) { [weak self] in
+ Task { @MainActor in
+ self?.webView?.load(URLRequest(url: SpotifyLoginConstants.loginURL))
+ }
+ }
+ }
+
+ func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
+ cookieStore.getAllCookies { [weak self] cookies in
+ Task { @MainActor in
+ self?.handle(cookies: cookies)
+ }
+ }
+ }
+
+ // MARK: - WKNavigationDelegate
+
+ func webView(
+ _ webView: WKWebView,
+ decidePolicyFor navigationAction: WKNavigationAction,
+ decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
+ ) {
+ if let url = navigationAction.request.url, Self.isBlockedOAuthHost(url.host) {
+ decisionHandler(.cancel)
+ spotifyLoginLogger.debug(
+ "Blocked embedded OAuth navigation to host=\(url.host ?? "", privacy: .public)"
+ )
+ onStatus(
+ "Google blocks in-app sign-in. Use email & password, Apple, or tap “Open in Browser” to sign in there and paste sp_dc in Settings."
+ )
+ webView.load(URLRequest(url: SpotifyLoginConstants.googleSignInRedirectURL))
+ return
+ }
+ decisionHandler(.allow)
+ }
+
+ private static func isBlockedOAuthHost(_ host: String?) -> Bool {
+ guard let host else { return false }
+ return SpotifyLoginConstants.blockedOAuthHostSuffixes.contains { host.hasSuffix($0) }
+ }
+
+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+ let host = webView.url?.host ?? ""
+ spotifyLoginLogger.debug("didFinish host=\(host, privacy: .public)")
+ if host.contains("accounts.spotify.com") {
+ onStatus("Sign in to your Spotify account.")
+ } else if host.contains("open.spotify.com") {
+ onStatus("Open Spotify shouldn't load here — capture should fire before this. Falling back.")
+ } else if host.contains("www.spotify.com") || host.contains("spotify.com") {
+ onStatus("Logged in. Finishing capture…")
+ }
+ webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { [weak self] cookies in
+ let names = cookies
+ .filter { $0.domain.hasSuffix("spotify.com") }
+ .map { "\($0.name)@\($0.domain)" }
+ .joined(separator: ", ")
+ spotifyLoginLogger.debug("cookies after load: \(names, privacy: .public)")
+ Task { @MainActor in
+ self?.handle(cookies: cookies)
+ }
+ }
+ }
+
+ func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
+ onStatus("Load failed: \(error.localizedDescription)")
+ }
+
+ func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
+ onStatus("Load failed: \(error.localizedDescription)")
+ }
+
+ // Allow pop-ups (e.g. SSO providers) to load inline in the same webview.
+ func webView(
+ _ webView: WKWebView,
+ createWebViewWith configuration: WKWebViewConfiguration,
+ for navigationAction: WKNavigationAction,
+ windowFeatures: WKWindowFeatures
+ ) -> WKWebView? {
+ if navigationAction.targetFrame == nil, let url = navigationAction.request.url {
+ if Self.isBlockedOAuthHost(url.host) {
+ onStatus(
+ "Google blocks in-app sign-in. Use email & password, Apple, or tap “Open in Browser” to sign in there and paste sp_dc in Settings."
+ )
+ return nil
+ }
+ webView.load(URLRequest(url: url))
+ }
+ return nil
+ }
+
+ // MARK: - Capture
+
+ private func handle(cookies: [HTTPCookie]) {
+ let match = cookies.first { cookie in
+ cookie.name == SpotifyLoginConstants.cookieName
+ && cookie.domain.hasSuffix(SpotifyLoginConstants.cookieDomainSuffix)
+ }
+ guard let match, !match.value.isEmpty else { return }
+ onCapture(match.value)
+ }
+ }
+}
diff --git a/DynamicIsland/components/Shelf/Views/ShelfInlineLiveActivity.swift b/DynamicIsland/components/Shelf/Views/ShelfInlineLiveActivity.swift
new file mode 100644
index 0000000..b190182
--- /dev/null
+++ b/DynamicIsland/components/Shelf/Views/ShelfInlineLiveActivity.swift
@@ -0,0 +1,56 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import SwiftUI
+
+/// Standalone inline HUD shown in the closed Dynamic Island when the
+/// File Tray (Shelf) has items and no higher-priority live activity is active.
+///
+/// Layout mirrors the battery inline HUD:
+/// [tray icon] ─── [ notch ] ─── [bold green count]
+struct ShelfInlineLiveActivity: View {
+ @EnvironmentObject var vm: DynamicIslandViewModel
+ @ObservedObject private var shelfState = ShelfStateViewModel.shared
+
+ private let sideWidth: CGFloat = 24
+
+ var body: some View {
+ HStack(spacing: 0) {
+
+ // LEFT of notch — tray icon (white, same style as the screenshot)
+ Image(systemName: "tray.fill")
+ .font(.system(size: 13, weight: .medium))
+ .foregroundStyle(.white)
+ .frame(width: sideWidth, alignment: .leading)
+
+ // Physical notch / pill space
+ Rectangle()
+ .fill(Color.black)
+ .frame(width: vm.closedNotchSize.width)
+
+ // RIGHT of notch — file count in white
+ Text("\(shelfState.items.count)")
+ .font(.system(.callout, design: .rounded, weight: .bold))
+ .foregroundStyle(.white)
+ .contentTransition(.numericText(countsDown: false))
+ .animation(.smooth(duration: 0.3), value: shelfState.items.count)
+ .frame(width: sideWidth, alignment: .trailing)
+ }
+ .frame(height: vm.effectiveClosedNotchHeight, alignment: .center)
+ }
+}
diff --git a/DynamicIsland/helpers/CoreBrightnessDisplayClient.swift b/DynamicIsland/helpers/CoreBrightnessDisplayClient.swift
new file mode 100644
index 0000000..a6e2032
--- /dev/null
+++ b/DynamicIsland/helpers/CoreBrightnessDisplayClient.swift
@@ -0,0 +1,82 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import ObjectiveC.runtime
+
+final class CoreBrightnessDisplayClient {
+ static let shared = CoreBrightnessDisplayClient()
+
+ private static let builtInDisplayID: UInt64 = 0
+
+ private var clientInstance: NSObject?
+ private let getSelector = NSSelectorFromString("brightnessForDisplay:")
+ private let setSelector = NSSelectorFromString("setBrightness:forDisplay:")
+ private let notificationSelector = NSSelectorFromString("setNotificationBlock:")
+
+ var onBrightnessChange: ((Float) -> Void)?
+ private var available = false
+
+ private init() {
+ var loaded = false
+ let bundlePaths = [
+ "/System/Library/PrivateFrameworks/CoreBrightness.framework",
+ "/System/Library/PrivateFrameworks/CoreBrightness.framework/CoreBrightness"
+ ]
+ for path in bundlePaths where !loaded {
+ if let bundle = Bundle(path: path) {
+ loaded = bundle.load()
+ }
+ }
+ guard loaded, let cls = NSClassFromString("DisplayBrightnessClient") as? NSObject.Type else {
+ NSLog("⚠️ CoreBrightnessDisplayClient: DisplayBrightnessClient class not found")
+ return
+ }
+ clientInstance = cls.init()
+ available = clientInstance != nil
+ }
+
+ var isAvailable: Bool { available }
+
+ func currentBrightness() -> Float? {
+ guard let clientInstance,
+ let getter: BrightnessGetter = methodIMP(on: clientInstance, selector: getSelector, as: BrightnessGetter.self)
+ else { return nil }
+ let value = getter(clientInstance, getSelector, Self.builtInDisplayID)
+ guard value >= 0, value <= 1 else { return nil }
+ return value
+ }
+
+ func setBrightness(_ value: Float) -> Bool {
+ guard let clientInstance,
+ let setter: BrightnessSetter = methodIMP(on: clientInstance, selector: setSelector, as: BrightnessSetter.self)
+ else { return false }
+ return setter(clientInstance, setSelector, value, Self.builtInDisplayID).boolValue
+ }
+
+ private typealias BrightnessGetter = @convention(c) (NSObject, Selector, UInt64) -> Float
+ private typealias BrightnessSetter = @convention(c) (NSObject, Selector, Float, UInt64) -> ObjCBool
+
+ private func methodIMP(on object: NSObject, selector: Selector, as type: T.Type) -> T? {
+ guard let cls = object_getClass(object),
+ let method = class_getInstanceMethod(cls, selector)
+ else { return nil }
+ let imp = method_getImplementation(method)
+ return unsafeBitCast(imp, to: T.self)
+ }
+}
diff --git a/DynamicIsland/managers/AnimatedArtworkManager.swift b/DynamicIsland/managers/AnimatedArtworkManager.swift
new file mode 100644
index 0000000..2993e63
--- /dev/null
+++ b/DynamicIsland/managers/AnimatedArtworkManager.swift
@@ -0,0 +1,149 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import MusicKit
+
+actor AnimatedArtworkManager {
+ static let shared = AnimatedArtworkManager()
+
+ private var cachedSongID: String?
+ private var cachedVideoURL: URL?
+ private var cachedKey: String?
+
+ func fetchAnimatedArtworkURL(title: String, artist: String) async -> URL? {
+ let key = "\(title)|\(artist)"
+ if key == cachedKey, let url = cachedVideoURL {
+ return url
+ }
+
+ guard await requestMusicAuthorization() else { return nil }
+
+ guard let songID = await searchSongID(title: title, artist: artist) else {
+ return nil
+ }
+
+ guard let videoURL = await fetchEditorialVideoURL(songID: songID) else {
+ return nil
+ }
+
+ cachedKey = key
+ cachedSongID = songID
+ cachedVideoURL = videoURL
+ return videoURL
+ }
+
+ func clearCache() {
+ cachedKey = nil
+ cachedSongID = nil
+ cachedVideoURL = nil
+ }
+
+ // MARK: - MusicKit Authorization
+
+ private func requestMusicAuthorization() async -> Bool {
+ let status = MusicAuthorization.currentStatus
+ if status == .authorized { return true }
+
+ let newStatus = await MusicAuthorization.request()
+ return newStatus == .authorized
+ }
+
+ // MARK: - Song Search
+
+ private func searchSongID(title: String, artist: String) async -> String? {
+ let term = "\(title) \(artist)"
+ var request = MusicCatalogSearchRequest(term: term, types: [Song.self])
+ request.limit = 5
+
+ do {
+ let response = try await request.response()
+ let normalizedTitle = title.lowercased()
+ let normalizedArtist = artist.lowercased()
+
+ let match = response.songs.first(where: {
+ $0.title.lowercased() == normalizedTitle &&
+ $0.artistName.lowercased() == normalizedArtist
+ }) ?? response.songs.first(where: {
+ $0.title.lowercased() == normalizedTitle
+ }) ?? response.songs.first
+
+ guard let song = match else { return nil }
+ return song.id.rawValue
+ } catch {
+ print("[AnimatedArtworkManager] Search failed: \(error)")
+ return nil
+ }
+ }
+
+ // MARK: - Editorial Video Fetch
+
+ private func fetchEditorialVideoURL(songID: String) async -> URL? {
+ let storefront: String
+ if let code = try? await MusicDataRequest.currentCountryCode {
+ storefront = code
+ } else {
+ storefront = "us"
+ }
+
+ return await fetchEditorialVideoURLFallback(songID: songID, storefront: storefront)
+ }
+
+ private func fetchEditorialVideoURLFallback(songID: String, storefront: String) async -> URL? {
+ guard let url = URL(string: "https://api.music.apple.com/v1/catalog/\(storefront)/songs/\(songID)?extend=editorialVideo") else {
+ return nil
+ }
+
+ do {
+ let request = MusicDataRequest(urlRequest: URLRequest(url: url))
+ let response = try await request.response()
+
+ guard let json = try JSONSerialization.jsonObject(with: response.data) as? [String: Any],
+ let data = json["data"] as? [[String: Any]],
+ let first = data.first,
+ let attributes = first["attributes"] as? [String: Any],
+ let editorialVideo = attributes["editorialVideo"] as? [String: Any]
+ else { return nil }
+
+ if let motionTall = editorialVideo["motionTallVideo3x4"] as? [String: Any],
+ let videoURLString = motionTall["video"] as? String,
+ let videoURL = URL(string: videoURLString) {
+ return videoURL
+ }
+
+ if let motionSquare = editorialVideo["motionSquareVideo1x1"] as? [String: Any],
+ let videoURLString = motionSquare["video"] as? String,
+ let videoURL = URL(string: videoURLString) {
+ return videoURL
+ }
+
+ let hlsPattern = try? NSRegularExpression(pattern: "https://mvod\\.itunes\\.apple\\.com[^\"]+\\.m3u8")
+ let jsonString = String(data: response.data, encoding: .utf8) ?? ""
+ let range = NSRange(jsonString.startIndex..., in: jsonString)
+ if let match = hlsPattern?.firstMatch(in: jsonString, range: range),
+ let matchRange = Range(match.range, in: jsonString) {
+ return URL(string: String(jsonString[matchRange]))
+ }
+
+ return nil
+ } catch {
+ print("[AnimatedArtworkManager] Editorial video fetch failed: \(error)")
+ return nil
+ }
+ }
+}
diff --git a/DynamicIsland/managers/FullScreenArtworkWindowManager.swift b/DynamicIsland/managers/FullScreenArtworkWindowManager.swift
new file mode 100644
index 0000000..ef80189
--- /dev/null
+++ b/DynamicIsland/managers/FullScreenArtworkWindowManager.swift
@@ -0,0 +1,1801 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import AppKit
+import AVFoundation
+import Combine
+import CoreImage
+import Defaults
+import SkyLightWindow
+import SwiftUI
+
+// MARK: - Click Receiver Window
+
+private class ClickReceiverWindow: NSWindow {
+ var onClick: (() -> Void)?
+
+ override func mouseDown(with event: NSEvent) {
+ // Keep left click inert so the fallback only dismisses on right click.
+ }
+
+ override func rightMouseDown(with event: NSEvent) {
+ onClick?()
+ }
+}
+
+private final class LoopingVideoView: NSView {
+ private var playerLayer: AVPlayerLayer?
+ private var queuePlayer: AVQueuePlayer?
+ private var playerLooper: AVPlayerLooper?
+
+ override init(frame frameRect: NSRect) {
+ super.init(frame: frameRect)
+ wantsLayer = true
+ layer?.backgroundColor = NSColor.clear.cgColor
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ wantsLayer = true
+ layer?.backgroundColor = NSColor.clear.cgColor
+ }
+
+ override func layout() {
+ super.layout()
+ playerLayer?.frame = bounds
+ }
+
+ func play(url: URL) {
+ stop()
+
+ let playerItem = AVPlayerItem(url: url)
+ let player = AVQueuePlayer()
+ player.isMuted = true
+ player.actionAtItemEnd = .none
+
+ let layer = AVPlayerLayer(player: player)
+ layer.videoGravity = .resizeAspectFill
+ layer.frame = bounds
+ layer.backgroundColor = NSColor.clear.cgColor
+
+ self.layer?.sublayers?.forEach { $0.removeFromSuperlayer() }
+ self.layer?.addSublayer(layer)
+
+ playerLayer = layer
+ queuePlayer = player
+ playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)
+ player.play()
+ }
+
+ func stop() {
+ queuePlayer?.pause()
+ playerLooper = nil
+ queuePlayer = nil
+ playerLayer?.removeFromSuperlayer()
+ playerLayer = nil
+ }
+
+ func pause() {
+ queuePlayer?.pause()
+ }
+
+ func resume() {
+ queuePlayer?.play()
+ }
+}
+
+private final class WallpaperTransitionImageView: NSView {
+ private let imageLayer = CALayer()
+
+ override init(frame frameRect: NSRect) {
+ super.init(frame: frameRect)
+ wantsLayer = true
+ layer?.backgroundColor = NSColor.clear.cgColor
+ imageLayer.contentsGravity = .resizeAspectFill
+ imageLayer.masksToBounds = true
+ imageLayer.contentsScale = NSScreen.main?.backingScaleFactor ?? 2
+ layer?.addSublayer(imageLayer)
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ wantsLayer = true
+ layer?.backgroundColor = NSColor.clear.cgColor
+ imageLayer.contentsGravity = .resizeAspectFill
+ imageLayer.masksToBounds = true
+ imageLayer.contentsScale = NSScreen.main?.backingScaleFactor ?? 2
+ layer?.addSublayer(imageLayer)
+ }
+
+ override func layout() {
+ super.layout()
+ imageLayer.frame = bounds
+ }
+
+ func updateImage(_ image: NSImage) {
+ imageLayer.contents = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
+ }
+}
+
+private final class SpotifyCanvasFallbackArtworkOverlayView: NSView {
+ var onDismiss: (() -> Void)?
+
+ private let imageLayer = CALayer()
+ private let borderLayer = CAShapeLayer()
+ private var artwork: NSImage
+
+ init(artwork: NSImage) {
+ self.artwork = artwork
+ super.init(frame: .zero)
+
+ wantsLayer = true
+ layer?.backgroundColor = NSColor.clear.cgColor
+ layer?.shadowColor = NSColor.black.withAlphaComponent(0.34).cgColor
+ layer?.shadowOpacity = 1
+ layer?.shadowRadius = 26
+ layer?.shadowOffset = CGSize(width: 0, height: -22)
+
+ imageLayer.contentsGravity = .resizeAspectFill
+ imageLayer.masksToBounds = true
+ imageLayer.contentsScale = NSScreen.main?.backingScaleFactor ?? 2
+ imageLayer.contents = artwork.cgImage(forProposedRect: nil, context: nil, hints: nil)
+
+ borderLayer.fillColor = NSColor.clear.cgColor
+ borderLayer.strokeColor = NSColor.white.withAlphaComponent(0.18).cgColor
+ borderLayer.lineWidth = 1.1
+
+ layer?.addSublayer(imageLayer)
+ layer?.addSublayer(borderLayer)
+ }
+
+ required init?(coder: NSCoder) {
+ nil
+ }
+
+ override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
+ true
+ }
+
+ override func layout() {
+ super.layout()
+
+ let radius = cornerRadius
+ imageLayer.frame = bounds
+ imageLayer.cornerRadius = radius
+ borderLayer.frame = bounds
+ borderLayer.path = CGPath(
+ roundedRect: bounds.insetBy(dx: 0.55, dy: 0.55),
+ cornerWidth: radius,
+ cornerHeight: radius,
+ transform: nil
+ )
+
+ layer?.shadowPath = CGPath(
+ roundedRect: bounds,
+ cornerWidth: radius,
+ cornerHeight: radius,
+ transform: nil
+ )
+ }
+
+ override func mouseDown(with event: NSEvent) {
+ if event.clickCount >= 2 {
+ onDismiss?()
+ }
+ }
+
+ override func rightMouseDown(with event: NSEvent) {
+ onDismiss?()
+ }
+
+ func updateArtwork(_ artwork: NSImage) {
+ self.artwork = artwork
+ imageLayer.contents = artwork.cgImage(forProposedRect: nil, context: nil, hints: nil)
+ needsLayout = true
+ }
+
+ private var aspectRatio: CGFloat {
+ let height = max(artwork.size.height, 1)
+ return artwork.size.width / height
+ }
+
+ private var cornerRadius: CGFloat {
+ aspectRatio > 1 ? 16 : 34
+ }
+}
+
+private struct FullScreenLyricsOverlayContent: View {
+ let text: String
+ let fontSize: CGFloat
+
+ private var isPlaceholder: Bool {
+ text == "Loading lyrics..." || text == "No lyrics found"
+ }
+
+ var body: some View {
+ VStack {
+ Text(text)
+ .font(.system(size: fontSize, weight: .semibold, design: .rounded))
+ .foregroundColor(.white.opacity(isPlaceholder ? 0.72 : 0.94))
+ .multilineTextAlignment(.center)
+ .lineLimit(4)
+ .minimumScaleFactor(0.62)
+ .allowsTightening(true)
+ .shadow(color: .black.opacity(0.42), radius: 18, x: 0, y: 6)
+ .frame(maxWidth: .infinity, alignment: .center)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
+ .padding(.horizontal, 18)
+ .padding(.vertical, 8)
+ .background(Color.clear)
+ }
+}
+
+private struct SpotifyCanvasFallbackLayoutFrames {
+ let artworkFrame: NSRect
+ let panelFrame: NSRect
+ let groupFrame: NSRect
+ let lyricsFrame: NSRect?
+}
+
+// MARK: - Window Manager
+
+@MainActor
+final class FullScreenArtworkWindowManager: ObservableObject {
+ private enum ArtworkOverlayMode {
+ case none
+ case spotifyFallback
+ }
+
+ static let shared = FullScreenArtworkWindowManager()
+
+ @Published private(set) var isShowing = false
+ @Published private(set) var isShowingSpotifyCanvasFallback = false
+ var onDismiss: (() -> Void)?
+
+ private let wallpaperPlistURL: URL = {
+ FileManager.default.homeDirectoryForCurrentUser
+ .appendingPathComponent("Library/Application Support/com.apple.wallpaper/Store/Index.plist")
+ }()
+ private let backupPlistURL: URL = {
+ FileManager.default.temporaryDirectory
+ .appendingPathComponent("atoll_wallpaper_backup.plist")
+ }()
+ private let aerialManifestURL: URL = {
+ FileManager.default.homeDirectoryForCurrentUser
+ .appendingPathComponent("Library/Application Support/com.apple.wallpaper/aerials/manifest/entries.json")
+ }()
+ private let aerialVideosDirectoryURL: URL = {
+ FileManager.default.homeDirectoryForCurrentUser
+ .appendingPathComponent("Library/Application Support/com.apple.wallpaper/aerials/videos", isDirectory: true)
+ }()
+ private let aerialThumbnailsDirectoryURL: URL = {
+ FileManager.default.homeDirectoryForCurrentUser
+ .appendingPathComponent("Library/Application Support/com.apple.wallpaper/aerials/thumbnails", isDirectory: true)
+ }()
+ private let customLiveWallpaperAssetID = "6D6834A4-2F0F-479A-B053-7D4DC5CB8EB7"
+ private let liveWallpaperManifestBackupURL: URL = {
+ FileManager.default.temporaryDirectory
+ .appendingPathComponent("atoll_aerial_manifest_backup.json")
+ }()
+ private let liveWallpaperVideoBackupURL: URL = {
+ FileManager.default.temporaryDirectory
+ .appendingPathComponent("atoll_aerial_video_backup.mov")
+ }()
+ private let liveWallpaperThumbnailBackupURL: URL = {
+ FileManager.default.temporaryDirectory
+ .appendingPathComponent("atoll_aerial_thumbnail_backup.png")
+ }()
+ private var artworkFileURL: URL?
+ private var cachedArtworkPNG: URL?
+ private var cachedArtworkFingerprint: String?
+ private var cachedBlurredArtworkPNG: URL?
+ private var cachedBlurredArtworkFingerprint: String?
+ private var cachedBlurredArtworkPixelSize: CGSize = .zero
+ private var activeSongTitle: String?
+ private var activeArtist: String?
+ private var trackChangeCancellable: AnyCancellable?
+ private var artworkCacheCancellable: AnyCancellable?
+ private var videoArtworkCancellable: AnyCancellable?
+ private var playbackStateCancellable: AnyCancellable?
+ private var hasSuspendedWallpaperAgent = false
+ private var appTerminationObserver: NSObjectProtocol?
+ private var clickWindow: ClickReceiverWindow?
+ private var clickWindowDelegated = false
+ private var videoWindow: NSWindow?
+ private var videoView: LoopingVideoView?
+ private var activeVideoWindowURL: URL?
+ private var videoWindowHideTask: Task?
+ private var artworkOverlayWindow: NSWindow?
+ private var artworkOverlayWindowDelegated = false
+ private var lyricsOverlayWindow: NSWindow?
+ private var lyricsOverlayWindowDelegated = false
+ private var currentArtworkOverlayMode: ArtworkOverlayMode = .none
+ private var wallpaperTransitionWindow: NSWindow?
+ private var wallpaperTransitionView: WallpaperTransitionImageView?
+ private var wallpaperTransitionHideTask: Task?
+ private var panelFrameChangeObserver: NSObjectProtocol?
+ private var liveWallpaperTask: Task?
+ private var deferredTrackRefreshTask: Task?
+ private var isLiveWallpaperAllowed = false
+ private var activeLiveWallpaperFingerprint: String?
+ private var activeWallpaperKey: String?
+ private var pendingFallbackStaticURL: URL?
+ private var pendingFallbackWallpaperKey: String?
+ private var fallbackRightClickMonitor: Any?
+ private var artworkLayoutOverCanvasPreferenceCancellable: AnyCancellable?
+ private var lyricsTextCancellable: AnyCancellable?
+ private var lyricsPreferenceCancellable: AnyCancellable?
+ private let spotifyCanvasFallbackHorizontalMargin: CGFloat = 48
+
+ private init() {
+ observeArtworkChanges()
+ observeVideoArtworkChanges()
+ observePanelFrameChanges()
+ observeArtworkLayoutOverCanvasPreference()
+ observeLyricsChanges()
+ observeLyricsPreference()
+ observePlaybackStateChanges()
+ observeAppTermination()
+ }
+
+ func show(artwork: NSImage, videoURL: URL? = nil, allowLiveWallpaper: Bool = false) {
+ guard !isShowing else { return }
+ guard let screen = NSScreen.main else { return }
+ applyPresentation(
+ artwork: artwork,
+ videoURL: videoURL,
+ allowLiveWallpaper: allowLiveWallpaper,
+ on: screen,
+ backupWallpaperConfiguration: true
+ )
+ observeTrackChanges()
+ }
+
+ func hide() {
+ guard isShowing else { return }
+ resumeWallpaperAgentIfNeeded()
+ isShowing = false
+ isLiveWallpaperAllowed = false
+ activeLiveWallpaperFingerprint = nil
+ activeWallpaperKey = nil
+ pendingFallbackStaticURL = nil
+ pendingFallbackWallpaperKey = nil
+ let shouldRestoreStandardPanelLayout = isShowingSpotifyCanvasFallback
+ isShowingSpotifyCanvasFallback = false
+ removeFallbackRightClickMonitor()
+ trackChangeCancellable?.cancel()
+ trackChangeCancellable = nil
+ liveWallpaperTask?.cancel()
+ liveWallpaperTask = nil
+ deferredTrackRefreshTask?.cancel()
+ deferredTrackRefreshTask = nil
+
+ hideVideoWindow()
+ hideArtworkOverlay()
+ hideLyricsOverlay()
+ hideWallpaperTransition()
+ hideClickReceiver()
+ if shouldRestoreStandardPanelLayout, LockScreenManager.shared.isLocked {
+ LockScreenPanelManager.shared.applyOffsetAdjustment(animated: true)
+ }
+ restoreWallpaper()
+
+ activeSongTitle = nil
+ activeArtist = nil
+
+ let callback = onDismiss
+ onDismiss = nil
+ callback?()
+
+ print("[FullScreenArtworkWindowManager] Original wallpaper restored")
+ }
+
+ // MARK: - Artwork Pre-Cache
+
+ private func observeArtworkChanges() {
+ artworkCacheCancellable = MusicManager.shared.$albumArt
+ .debounce(for: .milliseconds(180), scheduler: DispatchQueue.main)
+ .sink { [weak self] newArt in
+ guard let self else { return }
+ guard let fingerprint = self.imageFingerprint(for: newArt) else { return }
+ guard fingerprint != self.cachedArtworkFingerprint else { return }
+ let url = self.artworkCacheFileURL(for: fingerprint)
+ Task.detached(priority: .utility) {
+ guard let pngData = self.encodeToPNG(newArt) else { return }
+ try? pngData.write(to: url, options: .atomic)
+ await MainActor.run {
+ self.cachedArtworkPNG = url
+ self.cachedArtworkFingerprint = fingerprint
+ if self.isShowing {
+ self.refreshPresentationForCurrentTrack()
+ }
+ }
+ }
+ }
+ }
+
+ private func observeVideoArtworkChanges() {
+ videoArtworkCancellable = MusicManager.shared.$videoArtworkURL
+ .debounce(for: .milliseconds(120), scheduler: DispatchQueue.main)
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ guard let self, self.isShowing, self.isLiveWallpaperAllowed else { return }
+ self.refreshPresentationForCurrentTrack()
+ }
+ }
+
+ private func observePlaybackStateChanges() {
+ playbackStateCancellable = MusicManager.shared.$isPlaying
+ .removeDuplicates()
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] isPlaying in
+ self?.applyCanvasPlaybackState(isPlaying: isPlaying)
+ }
+ }
+
+ private static let wallpaperRenderProcessNames: [String] = [
+ "WallpaperAgent",
+ "WallpaperAerialsExtension",
+ "WallpaperVideoExtension",
+ "wallpaperexportd"
+ ]
+
+ private func applyCanvasPlaybackState(isPlaying: Bool) {
+ if let videoView, videoWindow != nil {
+ if isPlaying {
+ videoView.resume()
+ } else {
+ videoView.pause()
+ }
+ }
+
+ let canSuspend = isShowing && isLiveWallpaperAllowed && activeLiveWallpaperFingerprint != nil
+ print("[FullScreenArtworkWindowManager] playback state -> isPlaying=\(isPlaying) showing=\(isShowing) liveAllowed=\(isLiveWallpaperAllowed) fingerprint=\(activeLiveWallpaperFingerprint ?? "nil")")
+
+ if isPlaying {
+ resumeWallpaperAgentIfNeeded()
+ } else if canSuspend {
+ suspendWallpaperAgent()
+ }
+ }
+
+ private func suspendWallpaperAgent() {
+ guard !hasSuspendedWallpaperAgent else { return }
+ var anySucceeded = false
+ for name in Self.wallpaperRenderProcessNames {
+ if signalProcess(name: name, signalFlag: "-STOP") {
+ anySucceeded = true
+ }
+ }
+ hasSuspendedWallpaperAgent = anySucceeded
+ print("[FullScreenArtworkWindowManager] SIGSTOP wallpaper processes -> anySucceeded=\(anySucceeded)")
+ }
+
+ private func resumeWallpaperAgentIfNeeded() {
+ guard hasSuspendedWallpaperAgent else { return }
+ for name in Self.wallpaperRenderProcessNames {
+ _ = signalProcess(name: name, signalFlag: "-CONT")
+ }
+ hasSuspendedWallpaperAgent = false
+ print("[FullScreenArtworkWindowManager] SIGCONT wallpaper processes")
+ }
+
+ @discardableResult
+ private func signalProcess(name: String, signalFlag: String) -> Bool {
+ let task = Process()
+ task.executableURL = URL(fileURLWithPath: "/usr/bin/killall")
+ task.arguments = [signalFlag, name]
+ task.standardError = Pipe()
+ task.standardOutput = Pipe()
+ do {
+ try task.run()
+ task.waitUntilExit()
+ return task.terminationStatus == 0
+ } catch {
+ print("[FullScreenArtworkWindowManager] killall \(signalFlag) \(name) threw: \(error)")
+ return false
+ }
+ }
+
+ private func observeAppTermination() {
+ appTerminationObserver = NotificationCenter.default.addObserver(
+ forName: NSApplication.willTerminateNotification,
+ object: nil,
+ queue: .main
+ ) { [weak self] _ in
+ self?.resumeWallpaperAgentIfNeeded()
+ }
+ }
+
+ private func applyPresentation(
+ artwork: NSImage,
+ videoURL: URL?,
+ allowLiveWallpaper: Bool,
+ on screen: NSScreen,
+ backupWallpaperConfiguration: Bool
+ ) {
+ let artworkID = "\(MusicManager.shared.songTitle)|\(MusicManager.shared.artistName)"
+ guard let artworkFingerprint = imageFingerprint(for: artwork) else { return }
+ isLiveWallpaperAllowed = allowLiveWallpaper
+ let shouldUseStaticFallbackWallpaper = shouldUseSpotifyStaticFallbackWallpaper(videoURL: videoURL)
+ let shouldUseFallbackLayout = shouldUseSpotifyFallbackLayout(videoURL: videoURL)
+ let effectiveVideoURL = resolvedVideoURL(videoURL: videoURL)
+ let overlayMode = artworkOverlayMode(shouldUseFallbackLayout: shouldUseFallbackLayout)
+ let previousFallbackState = isShowingSpotifyCanvasFallback
+
+ guard let rawArtworkFileURL = persistedArtworkFileURL(for: artwork, fingerprint: artworkFingerprint) else { return }
+ let expectCanvas = effectiveVideoURL != nil
+ let blurredFallbackURL = blurredWallpaperFileURL(for: artwork, fingerprint: artworkFingerprint, screen: screen) ?? rawArtworkFileURL
+ let wallpaperURL = (shouldUseStaticFallbackWallpaper || expectCanvas)
+ ? blurredFallbackURL
+ : rawArtworkFileURL
+ let wallpaperKey = wallpaperIdentityKey(
+ fingerprint: artworkFingerprint,
+ usesFallback: shouldUseStaticFallbackWallpaper || expectCanvas,
+ screen: screen
+ )
+ let desiredLiveWallpaperFingerprint = effectiveVideoURL?.absoluteString
+ let requiresWallpaperRefresh = backupWallpaperConfiguration
+ || activeWallpaperKey != wallpaperKey
+ || activeLiveWallpaperFingerprint != desiredLiveWallpaperFingerprint
+
+ artworkFileURL = wallpaperURL
+
+ if backupWallpaperConfiguration {
+ backupWallpaperConfig()
+ }
+
+ if requiresWallpaperRefresh {
+ if expectCanvas {
+ pendingFallbackStaticURL = blurredFallbackURL
+ pendingFallbackWallpaperKey = wallpaperKey
+ activeWallpaperKey = nil
+ activeLiveWallpaperFingerprint = nil
+ } else {
+ showWallpaperTransition(on: screen, imageURL: wallpaperURL)
+
+ guard applyArtworkToPlist(imageURL: wallpaperURL) else {
+ print("[FullScreenArtworkWindowManager] Failed to patch plist")
+ hideWallpaperTransition()
+ return
+ }
+
+ restartWallpaperAgent()
+ activeWallpaperKey = wallpaperKey
+ activeLiveWallpaperFingerprint = nil
+ pendingFallbackStaticURL = nil
+ pendingFallbackWallpaperKey = nil
+ }
+ }
+
+ isShowing = true
+ isShowingSpotifyCanvasFallback = shouldUseFallbackLayout
+ activeSongTitle = MusicManager.shared.songTitle
+ activeArtist = MusicManager.shared.artistName
+
+ if overlayMode == .spotifyFallback {
+ installFallbackRightClickMonitorIfNeeded()
+ } else {
+ removeFallbackRightClickMonitor()
+ }
+
+ if overlayMode == .none {
+ hideArtworkOverlay()
+ } else {
+ showArtworkOverlay(on: screen, artwork: artwork, mode: overlayMode)
+ }
+
+ updateLyricsOverlayIfNeeded(on: screen)
+
+ if previousFallbackState != shouldUseFallbackLayout, LockScreenManager.shared.isLocked {
+ LockScreenPanelManager.shared.applyOffsetAdjustment(animated: true)
+ }
+
+ showClickReceiver(on: screen)
+ scheduleLiveWallpaperPreparation(for: effectiveVideoURL, artwork: artwork, identifier: artworkID)
+
+ if effectiveVideoURL == nil {
+ hideVideoWindow()
+ scheduleWallpaperTransitionHide(after: .milliseconds(900))
+ }
+
+ print("[FullScreenArtworkWindowManager] Artwork applied as wallpaper")
+ }
+
+ private func refreshPresentationForCurrentTrack() {
+ guard isShowing else { return }
+ guard MusicManager.shared.hasActiveSession else { return }
+ guard let screen = NSScreen.main else { return }
+
+ applyPresentation(
+ artwork: MusicManager.shared.albumArt,
+ videoURL: isLiveWallpaperAllowed ? MusicManager.shared.videoArtworkURL : nil,
+ allowLiveWallpaper: isLiveWallpaperAllowed,
+ on: screen,
+ backupWallpaperConfiguration: false
+ )
+ }
+
+ private func observePanelFrameChanges() {
+ panelFrameChangeObserver = NotificationCenter.default.addObserver(
+ forName: .atollLockScreenPanelFrameDidChange,
+ object: nil,
+ queue: .main
+ ) { [weak self] _ in
+ Task { @MainActor [weak self] in
+ self?.updateArtworkOverlayFrameIfNeeded()
+ self?.updateLyricsOverlayIfNeeded()
+ }
+ }
+ }
+
+ private func observeArtworkLayoutOverCanvasPreference() {
+ artworkLayoutOverCanvasPreferenceCancellable = Defaults.publisher(.lockScreenUseArtworkLayoutOverFullscreenCanvas)
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ guard let self, self.isShowing else { return }
+ self.refreshPresentationForCurrentTrack()
+ }
+ }
+
+ private func observeLyricsChanges() {
+ lyricsTextCancellable = MusicManager.shared.$currentLyrics
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ guard let self, self.isShowing else { return }
+ self.updateLyricsOverlayIfNeeded()
+ }
+ }
+
+ private func observeLyricsPreference() {
+ lyricsPreferenceCancellable = Defaults.publisher(.enableLyrics)
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] _ in
+ guard let self, self.isShowing else { return }
+ self.updateLyricsOverlayIfNeeded()
+ }
+ }
+
+ private func shouldUseSpotifyStaticFallbackWallpaper(videoURL: URL?) -> Bool {
+ guard MusicManager.shared.bundleIdentifier == SpotifyController.bundleIdentifier else {
+ return false
+ }
+
+ return videoURL == nil
+ }
+
+ private func shouldUseSpotifyFallbackLayout(videoURL: URL?) -> Bool {
+ guard MusicManager.shared.bundleIdentifier == SpotifyController.bundleIdentifier else {
+ return false
+ }
+
+ if videoURL == nil {
+ return true
+ }
+
+ return Defaults[.lockScreenUseArtworkLayoutOverFullscreenCanvas]
+ }
+
+ private func resolvedVideoURL(videoURL: URL?) -> URL? {
+ guard isLiveWallpaperAllowed else { return nil }
+ return videoURL
+ }
+
+ private func artworkOverlayMode(shouldUseFallbackLayout: Bool) -> ArtworkOverlayMode {
+ if shouldUseFallbackLayout {
+ return .spotifyFallback
+ }
+
+ return .none
+ }
+
+ func spotifyCanvasFallbackInterItemSpacing(screenFrame: NSRect) -> CGFloat {
+ min(max(screenFrame.width * 0.018, 28), 42)
+ }
+
+ func spotifyCanvasFallbackArtworkSideLength(screenFrame: NSRect, panelSize: CGSize) -> CGFloat {
+ let preferred = min(max(screenFrame.height * 0.28, 250), 330)
+ let spacing = spotifyCanvasFallbackInterItemSpacing(screenFrame: screenFrame)
+ let maxByWidth = max(
+ min(screenFrame.width - (spotifyCanvasFallbackHorizontalMargin * 2) - spacing - panelSize.width, 360),
+ 180
+ )
+ let maxByHeight = max(min(screenFrame.height * 0.42, 360), 180)
+ return min(preferred, maxByWidth, maxByHeight)
+ }
+
+ private func persistedArtworkFileURL(for artwork: NSImage, fingerprint: String) -> URL? {
+ if let cached = cachedArtworkPNG,
+ cachedArtworkFingerprint == fingerprint,
+ FileManager.default.fileExists(atPath: cached.path) {
+ return cached
+ }
+
+ guard let encoded = encodeToPNG(artwork) else { return nil }
+
+ let tempURL = artworkCacheFileURL(for: fingerprint)
+
+ do {
+ try encoded.write(to: tempURL, options: .atomic)
+ cachedArtworkPNG = tempURL
+ cachedArtworkFingerprint = fingerprint
+ return tempURL
+ } catch {
+ return nil
+ }
+ }
+
+ private func blurredWallpaperFileURL(for artwork: NSImage, fingerprint: String, screen: NSScreen) -> URL? {
+ let pixelSize = wallpaperPixelSize(for: screen)
+
+ if let cached = cachedBlurredArtworkPNG,
+ cachedBlurredArtworkFingerprint == fingerprint,
+ cachedBlurredArtworkPixelSize == pixelSize,
+ FileManager.default.fileExists(atPath: cached.path) {
+ return cached
+ }
+
+ guard let encoded = encodeBlurredWallpaperPNG(from: artwork, targetPixelSize: pixelSize) else {
+ return nil
+ }
+
+ let tempURL = blurredArtworkCacheFileURL(for: fingerprint, pixelSize: pixelSize)
+
+ do {
+ try encoded.write(to: tempURL, options: .atomic)
+ cachedBlurredArtworkPNG = tempURL
+ cachedBlurredArtworkFingerprint = fingerprint
+ cachedBlurredArtworkPixelSize = pixelSize
+ return tempURL
+ } catch {
+ return nil
+ }
+ }
+
+ private func wallpaperPixelSize(for screen: NSScreen) -> CGSize {
+ let scale = max(screen.backingScaleFactor, 1)
+ let frame = screen.frame
+ return CGSize(
+ width: max(frame.width * scale, 1),
+ height: max(frame.height * scale, 1)
+ )
+ }
+
+ private func artworkCacheFileURL(for fingerprint: String) -> URL {
+ let normalizedFingerprint = normalizedFingerprintComponent(fingerprint)
+ return FileManager.default.temporaryDirectory
+ .appendingPathComponent("atoll_artwork_wallpaper_\(normalizedFingerprint).png")
+ }
+
+ private func blurredArtworkCacheFileURL(for fingerprint: String, pixelSize: CGSize) -> URL {
+ let normalizedFingerprint = normalizedFingerprintComponent(fingerprint)
+ let width = Int(pixelSize.width.rounded())
+ let height = Int(pixelSize.height.rounded())
+ return FileManager.default.temporaryDirectory
+ .appendingPathComponent("atoll_artwork_wallpaper_blurred_\(normalizedFingerprint)_\(width)x\(height).png")
+ }
+
+ private func normalizedFingerprintComponent(_ fingerprint: String) -> String {
+ fingerprint.replacingOccurrences(of: "-", with: "_")
+ }
+
+ private func wallpaperIdentityKey(fingerprint: String, usesFallback: Bool, screen: NSScreen) -> String {
+ if usesFallback {
+ let pixelSize = wallpaperPixelSize(for: screen)
+ let width = Int(pixelSize.width.rounded())
+ let height = Int(pixelSize.height.rounded())
+ return "fallback:\(fingerprint):\(width)x\(height)"
+ }
+
+ return "artwork:\(fingerprint)"
+ }
+
+ private nonisolated func encodeToPNG(_ image: NSImage) -> Data? {
+ guard let tiffData = image.tiffRepresentation,
+ let bitmap = NSBitmapImageRep(data: tiffData)
+ else { return nil }
+ return bitmap.representation(using: .png, properties: [:])
+ }
+
+ private nonisolated func imageFingerprint(for image: NSImage) -> String? {
+ guard let pngData = encodeToPNG(image) else { return nil }
+ var hasher = Hasher()
+ hasher.combine(pngData)
+ hasher.combine(Int(image.size.width.rounded()))
+ hasher.combine(Int(image.size.height.rounded()))
+ return String(hasher.finalize())
+ }
+
+ private func encodeBlurredWallpaperPNG(from artwork: NSImage, targetPixelSize: CGSize) -> Data? {
+ guard let tiffData = artwork.tiffRepresentation,
+ let sourceImage = CIImage(data: tiffData)
+ else { return nil }
+
+ let targetRect = CGRect(origin: .zero, size: targetPixelSize)
+ let sourceExtent = sourceImage.extent
+ guard sourceExtent.width > 0, sourceExtent.height > 0 else { return nil }
+
+ let scale = max(
+ targetRect.width / sourceExtent.width,
+ targetRect.height / sourceExtent.height
+ )
+ let scaledWidth = sourceExtent.width * scale
+ let scaledHeight = sourceExtent.height * scale
+ let offsetX = (targetRect.width - scaledWidth) / 2
+ let offsetY = (targetRect.height - scaledHeight) / 2
+
+ let scaledImage = sourceImage
+ .transformed(by: CGAffineTransform(scaleX: scale, y: scale))
+ .transformed(by: CGAffineTransform(translationX: offsetX, y: offsetY))
+ .cropped(to: targetRect)
+
+ let blurredImage = scaledImage
+ .clampedToExtent()
+ .applyingFilter("CIGaussianBlur", parameters: [kCIInputRadiusKey: 28])
+ .cropped(to: targetRect)
+ .applyingFilter(
+ "CIColorControls",
+ parameters: [
+ kCIInputSaturationKey: 0.92,
+ kCIInputBrightnessKey: -0.03,
+ kCIInputContrastKey: 0.98
+ ]
+ )
+
+ let context = CIContext(options: nil)
+ guard let cgImage = context.createCGImage(blurredImage, from: targetRect) else { return nil }
+
+ let renderedImage = NSImage(cgImage: cgImage, size: NSSize(width: targetPixelSize.width, height: targetPixelSize.height))
+ let composedImage = NSImage(size: renderedImage.size)
+
+ composedImage.lockFocus()
+ renderedImage.draw(in: NSRect(origin: .zero, size: renderedImage.size))
+
+ NSColor(calibratedWhite: 0.02, alpha: 0.08).setFill()
+ NSBezierPath(rect: NSRect(origin: .zero, size: renderedImage.size)).fill()
+
+ let gradient = NSGradient(colors: [
+ NSColor.white.withAlphaComponent(0.05),
+ NSColor.clear,
+ NSColor.black.withAlphaComponent(0.16)
+ ])
+ gradient?.draw(in: NSRect(origin: .zero, size: renderedImage.size), angle: -90)
+ composedImage.unlockFocus()
+
+ guard let finalTIFF = composedImage.tiffRepresentation,
+ let finalBitmap = NSBitmapImageRep(data: finalTIFF)
+ else { return nil }
+
+ return finalBitmap.representation(using: .png, properties: [:])
+ }
+
+ private func applyDeferredStaticFallback() {
+ guard let url = pendingFallbackStaticURL else { return }
+ let key = pendingFallbackWallpaperKey
+ pendingFallbackStaticURL = nil
+ pendingFallbackWallpaperKey = nil
+
+ if let screen = NSScreen.main {
+ showWallpaperTransition(on: screen, imageURL: url)
+ }
+ guard applyArtworkToPlist(imageURL: url) else {
+ print("[FullScreenArtworkWindowManager] Failed to apply deferred static fallback")
+ hideWallpaperTransition()
+ return
+ }
+ restartWallpaperAgent()
+ activeWallpaperKey = key
+ activeLiveWallpaperFingerprint = nil
+ hideVideoWindow()
+ scheduleWallpaperTransitionHide(after: .milliseconds(900))
+ }
+
+ private func scheduleLiveWallpaperPreparation(for videoURL: URL?, artwork: NSImage, identifier: String) {
+ liveWallpaperTask?.cancel()
+ liveWallpaperTask = nil
+
+ guard isLiveWallpaperAllowed else {
+ scheduleWallpaperTransitionHide(after: .milliseconds(260))
+ return
+ }
+
+ guard let videoURL else {
+ scheduleWallpaperTransitionHide(after: .milliseconds(260))
+ return
+ }
+
+ let nextFingerprint = videoURL.absoluteString
+ if activeLiveWallpaperFingerprint == nextFingerprint {
+ scheduleWallpaperTransitionHide(after: .milliseconds(220))
+ scheduleHideVideoWindow(after: .milliseconds(220), expectedURL: videoURL)
+ return
+ }
+
+ if let screen = NSScreen.main {
+ showVideoWindow(on: screen, videoURL: videoURL)
+ }
+ scheduleWallpaperTransitionHide(after: .milliseconds(420))
+
+ let assetID = customLiveWallpaperAssetID
+ let manifestURL = aerialManifestURL
+ let videosDirectoryURL = aerialVideosDirectoryURL
+ let thumbnailsDirectoryURL = aerialThumbnailsDirectoryURL
+ let manifestBackupURL = liveWallpaperManifestBackupURL
+ let videoBackupURL = liveWallpaperVideoBackupURL
+ let thumbnailBackupURL = liveWallpaperThumbnailBackupURL
+ let thumbnailData = encodeToPNG(artwork)
+ let title = activeSongTitle ?? MusicManager.shared.songTitle
+ let artist = activeArtist ?? MusicManager.shared.artistName
+ let displayName = [title, artist]
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
+ .filter { !$0.isEmpty }
+ .joined(separator: " - ")
+
+ liveWallpaperTask = Task(priority: .utility) { [weak self] in
+ let prepared = await Self.prepareCustomLiveWallpaperAsset(
+ from: videoURL,
+ assetID: assetID,
+ displayName: displayName.isEmpty ? "Atoll Canvas" : displayName,
+ manifestURL: manifestURL,
+ videosDirectoryURL: videosDirectoryURL,
+ thumbnailsDirectoryURL: thumbnailsDirectoryURL,
+ thumbnailData: thumbnailData,
+ manifestBackupURL: manifestBackupURL,
+ videoBackupURL: videoBackupURL,
+ thumbnailBackupURL: thumbnailBackupURL
+ )
+
+ guard !Task.isCancelled else { return }
+
+ await MainActor.run {
+ guard let self, self.isShowing, self.isLiveWallpaperAllowed else { return }
+ let currentIdentifier = "\(MusicManager.shared.songTitle)|\(MusicManager.shared.artistName)"
+ let currentVideoURL = self.isLiveWallpaperAllowed ? MusicManager.shared.videoArtworkURL : nil
+ guard currentIdentifier == identifier else { return }
+ guard currentVideoURL?.absoluteString == nextFingerprint else { return }
+ guard !self.shouldUseSpotifyStaticFallbackWallpaper(videoURL: currentVideoURL) else { return }
+ guard prepared else {
+ self.applyDeferredStaticFallback()
+ return
+ }
+ guard self.applyAerialToPlist(assetID: assetID) else {
+ self.applyDeferredStaticFallback()
+ return
+ }
+
+ self.activeLiveWallpaperFingerprint = nextFingerprint
+ self.pendingFallbackStaticURL = nil
+ self.pendingFallbackWallpaperKey = nil
+ self.restartWallpaperAgent()
+ self.scheduleHideVideoWindow(after: .milliseconds(950), expectedURL: videoURL)
+ print("[FullScreenArtworkWindowManager] Live wallpaper applied")
+ }
+ }
+ }
+
+ private nonisolated static func prepareCustomLiveWallpaperAsset(
+ from sourceURL: URL,
+ assetID: String,
+ displayName: String,
+ manifestURL: URL,
+ videosDirectoryURL: URL,
+ thumbnailsDirectoryURL: URL,
+ thumbnailData: Data?,
+ manifestBackupURL: URL,
+ videoBackupURL: URL,
+ thumbnailBackupURL: URL
+ ) async -> Bool {
+ let fm = FileManager.default
+ let videoDestinationURL = videosDirectoryURL.appendingPathComponent("\(assetID).mov")
+ let thumbnailDestinationURL = thumbnailsDirectoryURL.appendingPathComponent("\(assetID).png")
+
+ do {
+ try fm.createDirectory(at: videosDirectoryURL, withIntermediateDirectories: true)
+ try fm.createDirectory(at: thumbnailsDirectoryURL, withIntermediateDirectories: true)
+ } catch {
+ return false
+ }
+
+ backupItemIfNeeded(at: manifestURL, backupURL: manifestBackupURL)
+ backupItemIfNeeded(at: videoDestinationURL, backupURL: videoBackupURL)
+ backupItemIfNeeded(at: thumbnailDestinationURL, backupURL: thumbnailBackupURL)
+
+ guard await materializeVideo(from: sourceURL, to: videoDestinationURL) else {
+ return false
+ }
+
+ if let thumbnailData {
+ try? thumbnailData.write(to: thumbnailDestinationURL, options: .atomic)
+ }
+
+ return updateAerialManifest(
+ manifestURL: manifestURL,
+ assetID: assetID,
+ videoURL: videoDestinationURL,
+ thumbnailURL: fm.fileExists(atPath: thumbnailDestinationURL.path) ? thumbnailDestinationURL : nil,
+ displayName: displayName
+ )
+ }
+
+ private nonisolated static func materializeVideo(from sourceURL: URL, to destinationURL: URL) async -> Bool {
+ let fm = FileManager.default
+ try? fm.removeItem(at: destinationURL)
+
+ if !sourceURL.isFileURL {
+ guard let downloadedURL = await downloadRemoteVideo(from: sourceURL) else {
+ return false
+ }
+ defer { try? fm.removeItem(at: downloadedURL) }
+ return await materializeVideo(from: downloadedURL, to: destinationURL)
+ }
+
+ if sourceURL.isFileURL, sourceURL.pathExtension.lowercased() == "mov" {
+ do {
+ try fm.copyItem(at: sourceURL, to: destinationURL)
+ return validateVideo(at: destinationURL)
+ } catch {
+ return false
+ }
+ }
+
+ let asset = AVURLAsset(url: sourceURL)
+ let presets = [AVAssetExportPresetPassthrough, AVAssetExportPresetHighestQuality]
+ for preset in presets {
+ try? fm.removeItem(at: destinationURL)
+ guard let exportSession = AVAssetExportSession(asset: asset, presetName: preset) else {
+ continue
+ }
+
+ exportSession.outputURL = destinationURL
+ exportSession.outputFileType = .mov
+ exportSession.shouldOptimizeForNetworkUse = true
+
+ let exported = await export(exportSession)
+ if exported, validateVideo(at: destinationURL) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ private nonisolated static func downloadRemoteVideo(from sourceURL: URL) async -> URL? {
+ do {
+ let (temporaryURL, response) = try await URLSession.shared.download(from: sourceURL)
+ guard let httpResponse = response as? HTTPURLResponse,
+ (200..<300).contains(httpResponse.statusCode)
+ else {
+ return nil
+ }
+
+ let destinationURL = FileManager.default.temporaryDirectory
+ .appendingPathComponent(UUID().uuidString)
+ .appendingPathExtension(sourceURL.pathExtension.isEmpty ? "mp4" : sourceURL.pathExtension)
+ try? FileManager.default.removeItem(at: destinationURL)
+ try FileManager.default.moveItem(at: temporaryURL, to: destinationURL)
+ return destinationURL
+ } catch {
+ return nil
+ }
+ }
+
+ private nonisolated static func export(_ exportSession: AVAssetExportSession) async -> Bool {
+ await withCheckedContinuation { continuation in
+ exportSession.exportAsynchronously {
+ continuation.resume(returning: exportSession.status == .completed)
+ }
+ }
+ }
+
+ private nonisolated static func validateVideo(at url: URL) -> Bool {
+ guard FileManager.default.fileExists(atPath: url.path) else { return false }
+ let asset = AVURLAsset(url: url)
+ return !asset.tracks(withMediaType: .video).isEmpty
+ }
+
+ private nonisolated static func updateAerialManifest(
+ manifestURL: URL,
+ assetID: String,
+ videoURL: URL,
+ thumbnailURL: URL?,
+ displayName: String
+ ) -> Bool {
+ guard let manifestData = try? Data(contentsOf: manifestURL),
+ var root = try? JSONSerialization.jsonObject(with: manifestData) as? [String: Any],
+ var assets = root["assets"] as? [[String: Any]]
+ else {
+ return false
+ }
+
+ guard let existingIndex = assets.firstIndex(where: { ($0["id"] as? String) == assetID }) else {
+ return false
+ }
+
+ var customAsset = assets[existingIndex]
+ customAsset["accessibilityLabel"] = displayName
+ customAsset["previewImage"] = thumbnailURL?.absoluteString ?? ""
+ customAsset["url-4K-SDR-240FPS"] = videoURL.absoluteString
+ customAsset["pointsOfInterest"] = [:]
+ assets[existingIndex] = customAsset
+
+ root["assets"] = assets
+
+ guard let encoded = try? JSONSerialization.data(withJSONObject: root, options: []) else {
+ return false
+ }
+
+ do {
+ try encoded.write(to: manifestURL, options: .atomic)
+ return true
+ } catch {
+ return false
+ }
+ }
+
+ private nonisolated static func backupItemIfNeeded(at sourceURL: URL, backupURL: URL) {
+ let fm = FileManager.default
+ guard fm.fileExists(atPath: sourceURL.path) else { return }
+ guard !fm.fileExists(atPath: backupURL.path) else { return }
+ try? fm.copyItem(at: sourceURL, to: backupURL)
+ }
+
+ private func restoreLiveWallpaperResources() {
+ let fm = FileManager.default
+ let assetID = customLiveWallpaperAssetID
+ let videoDestinationURL = aerialVideosDirectoryURL.appendingPathComponent("\(assetID).mov")
+ let thumbnailDestinationURL = aerialThumbnailsDirectoryURL.appendingPathComponent("\(assetID).png")
+
+ if fm.fileExists(atPath: liveWallpaperManifestBackupURL.path) {
+ try? fm.removeItem(at: aerialManifestURL)
+ try? fm.copyItem(at: liveWallpaperManifestBackupURL, to: aerialManifestURL)
+ try? fm.removeItem(at: liveWallpaperManifestBackupURL)
+ }
+
+ if fm.fileExists(atPath: liveWallpaperVideoBackupURL.path) {
+ try? fm.removeItem(at: videoDestinationURL)
+ try? fm.copyItem(at: liveWallpaperVideoBackupURL, to: videoDestinationURL)
+ try? fm.removeItem(at: liveWallpaperVideoBackupURL)
+ }
+
+ if fm.fileExists(atPath: liveWallpaperThumbnailBackupURL.path) {
+ try? fm.removeItem(at: thumbnailDestinationURL)
+ try? fm.copyItem(at: liveWallpaperThumbnailBackupURL, to: thumbnailDestinationURL)
+ try? fm.removeItem(at: liveWallpaperThumbnailBackupURL)
+ }
+ }
+
+ // MARK: - Click Receiver
+
+ private func installFallbackRightClickMonitorIfNeeded() {
+ guard fallbackRightClickMonitor == nil else { return }
+
+ fallbackRightClickMonitor = NSEvent.addLocalMonitorForEvents(matching: [.rightMouseDown]) { [weak self] event in
+ guard let self, self.isShowing, self.isShowingSpotifyCanvasFallback else {
+ return event
+ }
+
+ self.hide()
+ return nil
+ }
+ }
+
+ private func removeFallbackRightClickMonitor() {
+ guard let fallbackRightClickMonitor else { return }
+ NSEvent.removeMonitor(fallbackRightClickMonitor)
+ self.fallbackRightClickMonitor = nil
+ }
+
+ private func showClickReceiver(on screen: NSScreen) {
+ let screenFrame = screen.frame
+
+ let window: ClickReceiverWindow
+ if let existing = clickWindow {
+ window = existing
+ } else {
+ let newWindow = ClickReceiverWindow(
+ contentRect: screenFrame,
+ styleMask: [.borderless, .nonactivatingPanel],
+ backing: .buffered,
+ defer: false
+ )
+ newWindow.isReleasedWhenClosed = false
+ newWindow.isOpaque = false
+ newWindow.backgroundColor = .clear
+ newWindow.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()))
+ newWindow.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
+ newWindow.isMovable = false
+ newWindow.hasShadow = false
+
+ ScreenCaptureVisibilityManager.shared.register(newWindow, scope: .entireInterface)
+ clickWindow = newWindow
+ window = newWindow
+ clickWindowDelegated = false
+ }
+
+ window.setFrame(screenFrame, display: true)
+ window.onClick = { [weak self] in
+ self?.hide()
+ }
+
+ if !clickWindowDelegated {
+ SkyLightOperator.shared.delegateWindow(window)
+ clickWindowDelegated = true
+ }
+
+ window.orderFrontRegardless()
+
+ for other in NSApp.windows where other !== window && other.level.rawValue >= Int(CGShieldingWindowLevel()) && other.isVisible {
+ window.order(.below, relativeTo: other.windowNumber)
+ }
+ }
+
+ private func hideClickReceiver() {
+ clickWindow?.orderOut(nil)
+ clickWindow?.onClick = nil
+ }
+
+ private func showArtworkOverlay(on screen: NSScreen, artwork: NSImage, mode: ArtworkOverlayMode) {
+ currentArtworkOverlayMode = mode
+ guard let targetFrame = artworkOverlayFrame(on: screen) else { return }
+
+ let window: NSWindow
+ if let existing = artworkOverlayWindow {
+ window = existing
+ } else {
+ let newWindow = NSWindow(
+ contentRect: targetFrame,
+ styleMask: [.borderless],
+ backing: .buffered,
+ defer: false
+ )
+ newWindow.isReleasedWhenClosed = false
+ newWindow.isOpaque = false
+ newWindow.backgroundColor = .clear
+ newWindow.ignoresMouseEvents = false
+ newWindow.hasShadow = false
+ newWindow.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()))
+ newWindow.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
+
+ ScreenCaptureVisibilityManager.shared.register(newWindow, scope: .entireInterface)
+ artworkOverlayWindow = newWindow
+ window = newWindow
+ artworkOverlayWindowDelegated = false
+ }
+
+ window.setFrame(targetFrame, display: true)
+
+ let contentView: SpotifyCanvasFallbackArtworkOverlayView
+ if let existing = window.contentView as? SpotifyCanvasFallbackArtworkOverlayView {
+ contentView = existing
+ contentView.frame = NSRect(origin: .zero, size: targetFrame.size)
+ contentView.updateArtwork(artwork)
+ } else {
+ let newContentView = SpotifyCanvasFallbackArtworkOverlayView(artwork: artwork)
+ newContentView.frame = NSRect(origin: .zero, size: targetFrame.size)
+ window.contentView = newContentView
+ contentView = newContentView
+ }
+
+ contentView.onDismiss = { [weak self] in
+ self?.hide()
+ }
+
+ if !artworkOverlayWindowDelegated {
+ SkyLightOperator.shared.delegateWindow(window)
+ artworkOverlayWindowDelegated = true
+ }
+
+ window.orderFrontRegardless()
+ }
+
+ private func hideArtworkOverlay() {
+ currentArtworkOverlayMode = .none
+ if let contentView = artworkOverlayWindow?.contentView as? SpotifyCanvasFallbackArtworkOverlayView {
+ contentView.onDismiss = nil
+ }
+ artworkOverlayWindow?.orderOut(nil)
+ artworkOverlayWindow?.contentView = nil
+ }
+
+ private func updateArtworkOverlayFrameIfNeeded() {
+ guard isShowing, currentArtworkOverlayMode != .none else { return }
+ guard let window = artworkOverlayWindow else { return }
+ guard let screen = NSScreen.main, let frame = artworkOverlayFrame(on: screen) else { return }
+ window.setFrame(frame, display: true)
+ }
+
+ private func artworkOverlayFrame(on screen: NSScreen) -> NSRect? {
+ switch currentArtworkOverlayMode {
+ case .none:
+ return nil
+ case .spotifyFallback:
+ return spotifyFallbackArtworkOverlayFrame(on: screen)
+ }
+ }
+
+ private func spotifyFallbackArtworkOverlayFrame(on screen: NSScreen) -> NSRect? {
+ guard let frames = spotifyCanvasFallbackLayoutFrames(on: screen) else { return nil }
+ return frames.artworkFrame
+ }
+
+ private func updateLyricsOverlayIfNeeded(on screen: NSScreen? = NSScreen.main) {
+ guard isShowing, isShowingSpotifyCanvasFallback, Defaults[.enableLyrics] else {
+ hideLyricsOverlay()
+ return
+ }
+
+ let line = MusicManager.shared.currentLyrics.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !line.isEmpty else {
+ hideLyricsOverlay()
+ return
+ }
+
+ guard let screen, let frame = spotifyCanvasFallbackLayoutFrames(on: screen)?.lyricsFrame else {
+ hideLyricsOverlay()
+ return
+ }
+
+ let fontSize = min(max(frame.height * 0.24, 24), 40)
+ let content = FullScreenLyricsOverlayContent(text: line, fontSize: fontSize)
+
+ let window: NSWindow
+ let hostingView: NSHostingView
+ if let existingWindow = lyricsOverlayWindow,
+ let existingView = existingWindow.contentView as? NSHostingView {
+ window = existingWindow
+ hostingView = existingView
+ } else {
+ let newWindow = NSWindow(
+ contentRect: frame,
+ styleMask: [.borderless],
+ backing: .buffered,
+ defer: false
+ )
+ newWindow.isReleasedWhenClosed = false
+ newWindow.isOpaque = false
+ newWindow.backgroundColor = .clear
+ newWindow.ignoresMouseEvents = true
+ newWindow.hasShadow = false
+ newWindow.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()))
+ newWindow.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
+
+ let newHostingView = NSHostingView(rootView: content)
+ newHostingView.frame = NSRect(origin: .zero, size: frame.size)
+ newHostingView.wantsLayer = true
+ newHostingView.layer?.backgroundColor = NSColor.clear.cgColor
+ newWindow.contentView = newHostingView
+
+ ScreenCaptureVisibilityManager.shared.register(newWindow, scope: .entireInterface)
+ lyricsOverlayWindow = newWindow
+ window = newWindow
+ hostingView = newHostingView
+ lyricsOverlayWindowDelegated = false
+ }
+
+ window.setFrame(frame, display: true)
+ hostingView.frame = NSRect(origin: .zero, size: frame.size)
+ hostingView.rootView = content
+
+ if !lyricsOverlayWindowDelegated {
+ SkyLightOperator.shared.delegateWindow(window)
+ lyricsOverlayWindowDelegated = true
+ }
+
+ window.orderFrontRegardless()
+ }
+
+ private func hideLyricsOverlay() {
+ lyricsOverlayWindow?.orderOut(nil)
+ }
+
+ private func spotifyCanvasFallbackLayoutFrames(on screen: NSScreen) -> SpotifyCanvasFallbackLayoutFrames? {
+ let screenFrame = screen.frame
+ guard let panelFrame = LockScreenPanelManager.shared.latestFrame else { return nil }
+
+ let overlaySide = spotifyCanvasFallbackArtworkSideLength(
+ screenFrame: screenFrame,
+ panelSize: panelFrame.size
+ )
+ let spacing = spotifyCanvasFallbackInterItemSpacing(screenFrame: screenFrame)
+ let targetX = panelFrame.minX - spacing - overlaySide
+ let targetY = panelFrame.midY - (overlaySide / 2)
+ let artworkFrame = NSRect(x: targetX, y: targetY, width: overlaySide, height: overlaySide)
+ let groupFrame = artworkFrame.union(panelFrame)
+ let lyricsGap = min(max(screenFrame.height * 0.022, 18), 28)
+ let bottomMargin = min(max(screenFrame.height * 0.16, 140), 220)
+ let availableHeight = groupFrame.minY - lyricsGap - (screenFrame.minY + bottomMargin)
+ let desiredHeight = min(max(screenFrame.height * 0.13, 108), 156)
+ let lyricsHeight = min(desiredHeight, max(availableHeight, 0))
+ let lyricsFrame: NSRect?
+
+ if lyricsHeight >= 56 {
+ lyricsFrame = NSRect(
+ x: groupFrame.minX,
+ y: groupFrame.minY - lyricsGap - lyricsHeight,
+ width: groupFrame.width,
+ height: lyricsHeight
+ )
+ } else {
+ lyricsFrame = nil
+ }
+
+ return SpotifyCanvasFallbackLayoutFrames(
+ artworkFrame: artworkFrame,
+ panelFrame: panelFrame,
+ groupFrame: groupFrame,
+ lyricsFrame: lyricsFrame
+ )
+ }
+
+ private func showWallpaperTransition(on screen: NSScreen, imageURL: URL) {
+ guard let image = NSImage(contentsOf: imageURL) else { return }
+
+ wallpaperTransitionHideTask?.cancel()
+ wallpaperTransitionHideTask = nil
+
+ let screenFrame = screen.frame
+
+ let window: NSWindow
+ let view: WallpaperTransitionImageView
+
+ if let existingWindow = wallpaperTransitionWindow, let existingView = wallpaperTransitionView {
+ window = existingWindow
+ view = existingView
+ } else {
+ let newWindow = NSWindow(
+ contentRect: screenFrame,
+ styleMask: [.borderless],
+ backing: .buffered,
+ defer: false
+ )
+ newWindow.isReleasedWhenClosed = false
+ newWindow.isOpaque = false
+ newWindow.backgroundColor = .clear
+ newWindow.ignoresMouseEvents = true
+ newWindow.hasShadow = false
+ newWindow.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.desktopWindow)) + 1)
+ newWindow.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
+
+ let newView = WallpaperTransitionImageView(frame: screenFrame)
+ newWindow.contentView = newView
+
+ wallpaperTransitionWindow = newWindow
+ wallpaperTransitionView = newView
+ window = newWindow
+ view = newView
+ }
+
+ window.setFrame(screenFrame, display: true)
+ view.frame = NSRect(origin: .zero, size: screenFrame.size)
+ view.updateImage(image)
+ window.orderFrontRegardless()
+ }
+
+ private func scheduleWallpaperTransitionHide(after delay: Duration) {
+ wallpaperTransitionHideTask?.cancel()
+ wallpaperTransitionHideTask = Task { @MainActor [weak self] in
+ try? await Task.sleep(for: delay)
+ self?.hideWallpaperTransition()
+ }
+ }
+
+ private func hideWallpaperTransition() {
+ wallpaperTransitionHideTask?.cancel()
+ wallpaperTransitionHideTask = nil
+ wallpaperTransitionWindow?.orderOut(nil)
+ }
+
+ private func showVideoWindow(on screen: NSScreen, videoURL: URL) {
+ videoWindowHideTask?.cancel()
+ videoWindowHideTask = nil
+
+ let screenFrame = screen.frame
+
+ let window: NSWindow
+ let view: LoopingVideoView
+
+ if let existingWindow = videoWindow, let existingView = videoView {
+ window = existingWindow
+ view = existingView
+ } else {
+ let newWindow = NSWindow(
+ contentRect: screenFrame,
+ styleMask: [.borderless],
+ backing: .buffered,
+ defer: false
+ )
+ newWindow.isReleasedWhenClosed = false
+ newWindow.isOpaque = false
+ newWindow.backgroundColor = .clear
+ newWindow.ignoresMouseEvents = true
+ newWindow.hasShadow = false
+ newWindow.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.desktopWindow)) + 1)
+ newWindow.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
+
+ let newView = LoopingVideoView(frame: screenFrame)
+ newWindow.contentView = newView
+
+ videoWindow = newWindow
+ videoView = newView
+ window = newWindow
+ view = newView
+ }
+
+ window.setFrame(screenFrame, display: true)
+ view.frame = NSRect(origin: .zero, size: screenFrame.size)
+ if activeVideoWindowURL != videoURL {
+ view.play(url: videoURL)
+ activeVideoWindowURL = videoURL
+ }
+ window.orderFrontRegardless()
+ applyCanvasPlaybackState(isPlaying: MusicManager.shared.isPlaying)
+ }
+
+ private func scheduleHideVideoWindow(after delay: Duration, expectedURL: URL?) {
+ videoWindowHideTask?.cancel()
+ let expectedURLString = expectedURL?.absoluteString
+
+ videoWindowHideTask = Task { @MainActor [weak self] in
+ try? await Task.sleep(for: delay)
+ guard let self else { return }
+
+ if let expectedURLString,
+ self.activeVideoWindowURL?.absoluteString != expectedURLString {
+ return
+ }
+
+ self.hideVideoWindow()
+ }
+ }
+
+ private func hideVideoWindow() {
+ videoWindowHideTask?.cancel()
+ videoWindowHideTask = nil
+ activeVideoWindowURL = nil
+ videoView?.stop()
+ videoWindow?.orderOut(nil)
+ }
+
+ // MARK: - Backup / Restore
+
+ private var hasValidBackup: Bool {
+ FileManager.default.fileExists(atPath: backupPlistURL.path)
+ }
+
+ private func backupWallpaperConfig() {
+ guard !hasValidBackup else { return }
+ try? FileManager.default.copyItem(at: wallpaperPlistURL, to: backupPlistURL)
+ }
+
+ private func applyArtworkToPlist(imageURL: URL) -> Bool {
+ guard let plistData = try? Data(contentsOf: wallpaperPlistURL),
+ var plist = try? PropertyListSerialization.propertyList(from: plistData, format: nil) as? [String: Any]
+ else { return false }
+
+ let desktopBlock = makeImageWallpaperBlock(imageURL: imageURL)
+ let idleBlock = makeImageWallpaperBlock(imageURL: imageURL)
+
+ patchWallpaperEntries(&plist, desktopBlock: desktopBlock, idleBlock: idleBlock)
+
+ guard let newData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0)
+ else { return false }
+
+ return (try? newData.write(to: wallpaperPlistURL, options: .atomic)) != nil
+ }
+
+ private func applyAerialToPlist(assetID: String) -> Bool {
+ guard let plistData = try? Data(contentsOf: wallpaperPlistURL),
+ var plist = try? PropertyListSerialization.propertyList(from: plistData, format: nil) as? [String: Any]
+ else { return false }
+
+ let desktopTemplate = wallpaperBlock(
+ named: "Desktop",
+ in: plist["AllSpacesAndDisplays"] as? [String: Any],
+ fallback: plist["SystemDefault"] as? [String: Any]
+ )
+ let idleTemplate = wallpaperBlock(
+ named: "Idle",
+ in: plist["AllSpacesAndDisplays"] as? [String: Any],
+ fallback: plist["SystemDefault"] as? [String: Any]
+ )
+
+ let desktopBlock = makeAerialWallpaperBlock(assetID: assetID, existingBlock: desktopTemplate)
+ let idleBlock = makeAerialWallpaperBlock(assetID: assetID, existingBlock: idleTemplate)
+
+ patchWallpaperEntries(&plist, desktopBlock: desktopBlock, idleBlock: idleBlock)
+
+ guard let newData = try? PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0)
+ else { return false }
+
+ return (try? newData.write(to: wallpaperPlistURL, options: .atomic)) != nil
+ }
+
+ private func wallpaperBlock(named name: String, in primary: [String: Any]?, fallback: [String: Any]?) -> [String: Any]? {
+ if let primaryBlock = primary?[name] as? [String: Any] {
+ return primaryBlock
+ }
+ return fallback?[name] as? [String: Any]
+ }
+
+ private func makeImageWallpaperBlock(imageURL: URL) -> [String: Any] {
+ let config: [String: Any] = [
+ "type": "imageFile",
+ "url": ["relative": imageURL.absoluteString]
+ ]
+ let configData = (try? PropertyListSerialization.data(fromPropertyList: config, format: .binary, options: 0)) ?? Data()
+
+ let imageChoice: [String: Any] = [
+ "Provider": "com.apple.wallpaper.choice.image",
+ "Files": [] as [Any],
+ "Configuration": configData
+ ]
+
+ let contentBlock: [String: Any] = [
+ "Choices": [imageChoice],
+ "Shuffle": "$null"
+ ]
+
+ return [
+ "Content": contentBlock,
+ "LastSet": Date(),
+ "LastUse": Date()
+ ]
+ }
+
+ private func makeAerialWallpaperBlock(assetID: String, existingBlock: [String: Any]?) -> [String: Any] {
+ let config: [String: Any] = [
+ "assetID": assetID
+ ]
+ let configData = (try? PropertyListSerialization.data(fromPropertyList: config, format: .binary, options: 0)) ?? Data()
+
+ let aerialChoice: [String: Any] = [
+ "Provider": "com.apple.wallpaper.choice.aerials",
+ "Files": [] as [Any],
+ "Configuration": configData
+ ]
+
+ var contentBlock: [String: Any] = [
+ "Choices": [aerialChoice],
+ "Shuffle": "$null"
+ ]
+
+ if let existingContent = existingBlock?["Content"] as? [String: Any],
+ let encodedOptionValues = existingContent["EncodedOptionValues"] {
+ contentBlock["EncodedOptionValues"] = encodedOptionValues
+ }
+
+ return [
+ "Content": contentBlock,
+ "LastSet": Date(),
+ "LastUse": Date()
+ ]
+ }
+
+ private func patchWallpaperEntries(_ plist: inout [String: Any], desktopBlock: [String: Any], idleBlock: [String: Any]) {
+ var allSpaces = (plist["AllSpacesAndDisplays"] as? [String: Any]) ?? [:]
+ allSpaces["Desktop"] = desktopBlock
+ allSpaces["Idle"] = idleBlock
+ allSpaces["Type"] = "individual"
+ plist["AllSpacesAndDisplays"] = allSpaces
+
+ var systemDefault = (plist["SystemDefault"] as? [String: Any]) ?? [:]
+ systemDefault["Desktop"] = desktopBlock
+ systemDefault["Idle"] = idleBlock
+ systemDefault["Type"] = "individual"
+ plist["SystemDefault"] = systemDefault
+
+ if var displays = plist["Displays"] as? [String: Any] {
+ for key in displays.keys {
+ if var display = displays[key] as? [String: Any] {
+ display["Desktop"] = desktopBlock
+ display["Idle"] = idleBlock
+ if display["Type"] != nil {
+ display["Type"] = "individual"
+ }
+ displays[key] = display
+ }
+ }
+ plist["Displays"] = displays
+ }
+
+ if var spaces = plist["Spaces"] as? [String: Any] {
+ for spaceKey in spaces.keys {
+ if var space = spaces[spaceKey] as? [String: Any] {
+ if var defaultEntry = space["Default"] as? [String: Any] {
+ defaultEntry["Desktop"] = desktopBlock
+ defaultEntry["Idle"] = idleBlock
+ if defaultEntry["Type"] != nil {
+ defaultEntry["Type"] = "individual"
+ }
+ space["Default"] = defaultEntry
+ }
+ if var spaceDisplays = space["Displays"] as? [String: Any] {
+ for displayKey in spaceDisplays.keys {
+ if var display = spaceDisplays[displayKey] as? [String: Any] {
+ display["Desktop"] = desktopBlock
+ display["Idle"] = idleBlock
+ if display["Type"] != nil {
+ display["Type"] = "individual"
+ }
+ spaceDisplays[displayKey] = display
+ }
+ }
+ space["Displays"] = spaceDisplays
+ }
+ spaces[spaceKey] = space
+ }
+ }
+ plist["Spaces"] = spaces
+ }
+ }
+
+ private func restartWallpaperAgent() {
+ let task = Process()
+ task.executableURL = URL(fileURLWithPath: "/usr/bin/killall")
+ task.arguments = ["WallpaperAgent"]
+ try? task.run()
+ }
+
+ private func restoreWallpaper() {
+ let fm = FileManager.default
+ guard hasValidBackup else { return }
+
+ do {
+ try fm.removeItem(at: wallpaperPlistURL)
+ try fm.copyItem(at: backupPlistURL, to: wallpaperPlistURL)
+ try fm.removeItem(at: backupPlistURL)
+ } catch {
+ print("[FullScreenArtworkWindowManager] Failed to restore plist: \(error)")
+ }
+
+ restoreLiveWallpaperResources()
+ restartWallpaperAgent()
+ }
+
+ // MARK: - Track Change Observer
+
+ private func observeTrackChanges() {
+ trackChangeCancellable?.cancel()
+ trackChangeCancellable = MusicManager.shared.$songTitle
+ .combineLatest(MusicManager.shared.$artistName)
+ .dropFirst()
+ .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
+ .receive(on: DispatchQueue.main)
+ .sink { [weak self] title, artist in
+ guard let self, self.isShowing else { return }
+ if title != self.activeSongTitle || artist != self.activeArtist {
+ self.refreshPresentationForCurrentTrack()
+ self.scheduleDeferredTrackRefresh(expectedTitle: title, expectedArtist: artist)
+ }
+ }
+ }
+
+ private func scheduleDeferredTrackRefresh(expectedTitle: String, expectedArtist: String) {
+ deferredTrackRefreshTask?.cancel()
+ deferredTrackRefreshTask = Task { @MainActor [weak self] in
+ try? await Task.sleep(for: .milliseconds(700))
+ guard let self, self.isShowing else { return }
+ guard MusicManager.shared.songTitle == expectedTitle,
+ MusicManager.shared.artistName == expectedArtist
+ else { return }
+
+ self.refreshPresentationForCurrentTrack()
+ }
+ }
+}
diff --git a/DynamicIsland/managers/LockScreenManager.swift b/DynamicIsland/managers/LockScreenManager.swift
index 6e98688..95da4fc 100644
--- a/DynamicIsland/managers/LockScreenManager.swift
+++ b/DynamicIsland/managers/LockScreenManager.swift
@@ -48,6 +48,7 @@ class LockScreenManager: ObservableObject {
private var debounceIdleTask: Task?
private var collapseTask: Task?
private var postUnlockMusicHUDTask: Task?
+ private var lockStatePollTask: Task?
// MARK: - Helpers
@@ -65,9 +66,11 @@ class LockScreenManager: ObservableObject {
deinit {
DistributedNotificationCenter.default().removeObserver(self)
+ NSWorkspace.shared.notificationCenter.removeObserver(self)
debounceIdleTask?.cancel()
collapseTask?.cancel()
postUnlockMusicHUDTask?.cancel()
+ lockStatePollTask?.cancel()
}
// MARK: - Setup
@@ -80,7 +83,7 @@ class LockScreenManager: ObservableObject {
name: .init("com.apple.screenIsLocked"),
object: nil
)
-
+
// Observe screen unlocked event
DistributedNotificationCenter.default().addObserver(
self,
@@ -88,7 +91,18 @@ class LockScreenManager: ObservableObject {
name: .init("com.apple.screenIsUnlocked"),
object: nil
)
-
+
+ // Fallback: macOS sometimes delays `com.apple.screenIsUnlocked`, leaving
+ // lock-screen widgets visible after the user-perceived unlock.
+ // The workspace session-active notification typically fires earlier; the
+ // guard at the top of `screenUnlocked` makes the call idempotent.
+ NSWorkspace.shared.notificationCenter.addObserver(
+ self,
+ selector: #selector(screenUnlocked),
+ name: NSWorkspace.sessionDidBecomeActiveNotification,
+ object: nil
+ )
+
print("LockScreenManager: ✅ Observers registered for lock/unlock events")
}
@@ -141,9 +155,11 @@ class LockScreenManager: ObservableObject {
print("[\(timestamp())] LockScreenManager: ⏭️ Lock icon disabled in settings")
}
+ startLockStatePolling()
+
print("[\(timestamp())] LockScreenManager: ✅ Lock screen activated")
}
-
+
@objc private func screenUnlocked() {
guard isLocked else {
print("[\(timestamp())] LockScreenManager: 🔁 Unlock event ignored (already unlocked)")
@@ -156,6 +172,7 @@ class LockScreenManager: ObservableObject {
lastUpdated = Date()
updateIdleState(locked: false)
isLocked = false
+ stopLockStatePolling()
postUnlockMusicHUDTask?.cancel()
shouldDelayPostUnlockMusicHUD = Defaults[.enableLockScreenLiveActivity]
@@ -187,6 +204,7 @@ class LockScreenManager: ObservableObject {
// Hide panel window immediately and synchronously
print("[\(timestamp())] LockScreenManager: 🚪 Hiding panel window")
LockScreenPanelManager.shared.hidePanel()
+ FullScreenArtworkWindowManager.shared.hide()
LockScreenLiveActivityWindowManager.shared.showUnlockAndScheduleHide()
LockScreenWeatherManager.shared.hideWeatherWidget()
LockScreenTimerWidgetManager.shared.handleLockStateChange(isLocked: false)
@@ -206,8 +224,46 @@ class LockScreenManager: ObservableObject {
print("[\(self.timestamp())] LockScreenManager: ✅ Lock screen deactivated")
}
+ // MARK: - Lock State Polling
+
+ // Defensive fallback against late/missed `com.apple.screenIsUnlocked` and
+ // `NSWorkspace.sessionDidBecomeActiveNotification` notifications, which
+ // macOS sometimes delivers well after the user-perceived unlock — leaving
+ // lock-screen widgets visible for an extra moment. While we believe we are
+ // locked, poll the canonical session-lock state and fire `screenUnlocked()`
+ // the moment the OS flips. The handler's duplicate-event guard makes this
+ // safe to call alongside any later-arriving notification.
+ private static func isSessionScreenLocked() -> Bool {
+ guard let session = CGSessionCopyCurrentDictionary() as? [String: Any] else {
+ return false
+ }
+ return session["CGSSessionScreenIsLocked"] as? Bool ?? false
+ }
+
+ private func startLockStatePolling() {
+ lockStatePollTask?.cancel()
+ lockStatePollTask = Task { [weak self] in
+ while !Task.isCancelled {
+ try? await Task.sleep(for: .milliseconds(500))
+ if Task.isCancelled { return }
+ await MainActor.run {
+ guard let self, self.isLocked else { return }
+ if !Self.isSessionScreenLocked() {
+ print("[\(self.timestamp())] LockScreenManager: 🔓 Polling detected unlock ahead of notification")
+ self.screenUnlocked()
+ }
+ }
+ }
+ }
+ }
+
+ private func stopLockStatePolling() {
+ lockStatePollTask?.cancel()
+ lockStatePollTask = nil
+ }
+
// MARK: - Idle State Management
-
+
/// Copy EXACT logic from ScreenRecordingManager
private func updateIdleState(locked: Bool) {
if locked {
diff --git a/DynamicIsland/managers/LockScreenPanelManager.swift b/DynamicIsland/managers/LockScreenPanelManager.swift
index c259971..e076fe7 100644
--- a/DynamicIsland/managers/LockScreenPanelManager.swift
+++ b/DynamicIsland/managers/LockScreenPanelManager.swift
@@ -58,13 +58,30 @@ class LockScreenPanelManager {
return formatter.string(from: Date())
}
+ private func publishPanelFrame(_ frame: NSRect?) {
+ latestFrame = frame
+
+ var userInfo: [AnyHashable: Any] = [:]
+ if let frame {
+ userInfo["frame"] = NSValue(rect: frame)
+ }
+
+ NotificationCenter.default.post(
+ name: .atollLockScreenPanelFrameDidChange,
+ object: self,
+ userInfo: userInfo
+ )
+ }
+
private func registerScreenChangeObservers() {
screenChangeObserver = NotificationCenter.default.addObserver(
forName: NSApplication.didChangeScreenParametersNotification,
object: nil,
queue: .main
) { [weak self] _ in
- self?.handleScreenGeometryChange(reason: "screen-parameters")
+ Task { @MainActor [weak self] in
+ self?.handleScreenGeometryChange(reason: "screen-parameters")
+ }
}
let workspaceCenter = NSWorkspace.shared.notificationCenter
@@ -73,7 +90,9 @@ class LockScreenPanelManager {
object: nil,
queue: .main
) { [weak self] _ in
- self?.handleScreenGeometryChange(reason: "screens-did-wake")
+ Task { @MainActor [weak self] in
+ self?.handleScreenGeometryChange(reason: "screens-did-wake")
+ }
}
workspaceObservers = [wakeObserver]
@@ -94,8 +113,9 @@ class LockScreenPanelManager {
}
let screenFrame = screen.frame
- let targetFrame = collapsedFrame(for: screenFrame)
- collapsedFrame = targetFrame
+ let defaultCollapsedFrame = collapsedFrame(for: screenFrame)
+ let targetFrame = targetFrame(for: screenFrame, panelSize: LockScreenMusicPanel.collapsedSize)
+ collapsedFrame = defaultCollapsedFrame
isPanelExpanded = false
currentAdditionalHeight = 0
@@ -127,14 +147,14 @@ class LockScreenPanelManager {
}
window.setFrame(targetFrame, display: true)
- latestFrame = targetFrame
+ publishPanelFrame(targetFrame)
hideTask?.cancel()
panelAnimator.isPresented = false
LockScreenTimerWidgetManager.shared.notifyMusicPanelFrameChanged(animated: false)
- let hosting = NSHostingView(rootView: LockScreenMusicPanel(animator: panelAnimator))
- hosting.frame = NSRect(origin: .zero, size: targetFrame.size)
- hosting.autoresizingMask = [.width, .height]
+ let hosting = NSHostingView(rootView: LockScreenMusicPanel(animator: panelAnimator))
+ hosting.frame = NSRect(origin: .zero, size: targetFrame.size)
+ hosting.autoresizingMask = [.width, .height]
window.contentView = hosting
// Ensure the underlying window content is clipped to rounded corners
@@ -162,18 +182,17 @@ class LockScreenPanelManager {
}
func updatePanelSize(expanded: Bool, additionalHeight: CGFloat = 0, animated: Bool = true) {
- guard let window = panelWindow, let baseFrame = collapsedFrame else {
+ guard let window = panelWindow, let screen = currentScreen() else {
return
}
let resizeDuration: CFTimeInterval = 0.28
let baseSize = expanded ? LockScreenMusicPanel.expandedSize : LockScreenMusicPanel.collapsedSize
- let targetWidth = baseSize.width
- let targetHeight = baseSize.height + additionalHeight
- let originX = baseFrame.midX - (targetWidth / 2)
- let originY = baseFrame.origin.y
- let targetFrame = NSRect(x: originX, y: originY, width: targetWidth, height: targetHeight)
+ let targetFrame = targetFrame(
+ for: screen.frame,
+ panelSize: CGSize(width: baseSize.width, height: baseSize.height + additionalHeight)
+ )
if animated {
NSAnimationContext.runAnimationGroup { context in
@@ -185,7 +204,7 @@ class LockScreenPanelManager {
window.setFrame(targetFrame, display: true)
}
- latestFrame = targetFrame
+ publishPanelFrame(targetFrame)
LockScreenTimerWidgetManager.shared.notifyMusicPanelFrameChanged(animated: animated)
@@ -229,7 +248,7 @@ class LockScreenPanelManager {
guard let window = panelWindow else {
print("LockScreenPanelManager: no panel to hide")
- latestFrame = nil
+ publishPanelFrame(nil)
return
}
@@ -239,7 +258,7 @@ class LockScreenPanelManager {
await MainActor.run {
window?.orderOut(nil)
window?.contentView = nil
- self.latestFrame = nil
+ self.publishPanelFrame(nil)
print("[\(self.timestamp())] LockScreenPanelManager: panel hidden")
}
}
@@ -267,6 +286,74 @@ class LockScreenPanelManager {
.store(in: &cancellables)
}
+ private func targetFrame(for screenFrame: NSRect, panelSize: CGSize) -> NSRect {
+ let defaultCollapsedFrame = collapsedFrame(for: screenFrame)
+
+ if FullScreenArtworkWindowManager.shared.isShowingSpotifyCanvasFallback {
+ return spotifyCanvasFallbackPanelFrame(
+ screenFrame: screenFrame,
+ defaultCollapsedFrame: defaultCollapsedFrame,
+ panelSize: panelSize
+ )
+ }
+
+ let originX = defaultCollapsedFrame.midX - (panelSize.width / 2)
+ return NSRect(
+ x: originX,
+ y: defaultCollapsedFrame.origin.y,
+ width: panelSize.width,
+ height: panelSize.height
+ )
+ }
+
+ private func spotifyCanvasFallbackPanelFrame(
+ screenFrame: NSRect,
+ defaultCollapsedFrame: NSRect,
+ panelSize: CGSize
+ ) -> NSRect {
+ let fullscreenArtworkManager = FullScreenArtworkWindowManager.shared
+ let spacing = fullscreenArtworkManager.spotifyCanvasFallbackInterItemSpacing(screenFrame: screenFrame)
+ let artworkSide = fullscreenArtworkManager.spotifyCanvasFallbackArtworkSideLength(
+ screenFrame: screenFrame,
+ panelSize: panelSize
+ )
+ let totalWidth = artworkSide + spacing + panelSize.width
+ let horizontalMargin: CGFloat = 48
+ let minGroupOriginX = screenFrame.minX + horizontalMargin
+ let maxGroupOriginX = max(screenFrame.maxX - horizontalMargin - totalWidth, minGroupOriginX)
+ let groupOriginX = min(
+ max(screenFrame.midX - (totalWidth / 2), minGroupOriginX),
+ maxGroupOriginX
+ )
+
+ let baseCenterY = screenFrame.midY
+ let largestHalfHeight = max(artworkSide, panelSize.height) / 2
+ let minCenterY = screenFrame.minY + largestHalfHeight + 56
+ var maxCenterY = screenFrame.maxY - largestHalfHeight - 84
+
+ let widgetClearance: CGFloat = 20
+ if let weatherFrame = LockScreenWeatherPanelManager.shared.latestFrame,
+ !weatherFrame.isEmpty {
+ let weatherTopConstrainedCenterY = weatherFrame.minY - widgetClearance - largestHalfHeight
+ maxCenterY = min(maxCenterY, weatherTopConstrainedCenterY)
+ }
+ if let timerFrame = LockScreenTimerWidgetPanelManager.shared.latestFrame,
+ !timerFrame.isEmpty {
+ let timerTopConstrainedCenterY = timerFrame.minY - widgetClearance - largestHalfHeight
+ maxCenterY = min(maxCenterY, timerTopConstrainedCenterY)
+ }
+
+ let resolvedMaxCenterY = max(maxCenterY, minCenterY)
+ let centerY = min(max(baseCenterY, minCenterY), resolvedMaxCenterY)
+
+ return NSRect(
+ x: groupOriginX + artworkSide + spacing,
+ y: centerY - (panelSize.height / 2),
+ width: panelSize.width,
+ height: panelSize.height
+ )
+ }
+
private func collapsedFrame(for screenFrame: NSRect) -> NSRect {
let collapsedSize = LockScreenMusicPanel.collapsedSize
let originX = screenFrame.midX - (collapsedSize.width / 2)
@@ -289,3 +376,7 @@ class LockScreenPanelManager {
LockScreenDisplayContextProvider.shared.contextSnapshot()?.screen ?? NSScreen.main
}
}
+
+extension Notification.Name {
+ static let atollLockScreenPanelFrameDidChange = Notification.Name("atollLockScreenPanelFrameDidChange")
+}
diff --git a/DynamicIsland/managers/LockScreenTimerWidgetManager.swift b/DynamicIsland/managers/LockScreenTimerWidgetManager.swift
index 0337d89..d907c01 100644
--- a/DynamicIsland/managers/LockScreenTimerWidgetManager.swift
+++ b/DynamicIsland/managers/LockScreenTimerWidgetManager.swift
@@ -279,6 +279,11 @@ final class LockScreenTimerWidgetPanelManager {
originY = min(originY, topLimit)
}
+ if let musicFrame = LockScreenPanelManager.shared.latestFrame {
+ let minAllowedY = musicFrame.maxY + 12
+ originY = max(originY, minAllowedY)
+ }
+
let minY = screen.frame.minY + 100
originY = max(originY, minY)
diff --git a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift
index 9b6b5b1..493b5b9 100644
--- a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift
+++ b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift
@@ -49,8 +49,10 @@ final class LockScreenWeatherPanelManager {
func hide() {
guard let window else { return }
+ window.alphaValue = 0
window.orderOut(nil)
window.contentView = nil
+ window.alphaValue = 1
latestFrame = nil
lastSnapshot = nil
lastContentSize = nil
diff --git a/DynamicIsland/managers/MemoryUsageMonitor.swift b/DynamicIsland/managers/MemoryUsageMonitor.swift
index 34c9898..d03b785 100644
--- a/DynamicIsland/managers/MemoryUsageMonitor.swift
+++ b/DynamicIsland/managers/MemoryUsageMonitor.swift
@@ -24,7 +24,11 @@ import Darwin
final class MemoryUsageMonitor {
static let shared = MemoryUsageMonitor()
+#if DEBUG
+ private let thresholdBytes: UInt64 = 4 * 1_024 * 1_024 * 1_024
+#else
private let thresholdBytes: UInt64 = 1_024 * 1_024 * 1_024
+#endif
private let pollInterval: TimeInterval = 8 // Clamp within 5-10 seconds to limit battery impact
private let restartCooldown: TimeInterval = 300
private let logSampleInterval: TimeInterval = 300
diff --git a/DynamicIsland/managers/MusicManager.swift b/DynamicIsland/managers/MusicManager.swift
index d3f723d..db6ac60 100644
--- a/DynamicIsland/managers/MusicManager.swift
+++ b/DynamicIsland/managers/MusicManager.swift
@@ -38,6 +38,16 @@ struct LyricLine: Identifiable, Codable {
}
}
+private struct LyricsLookupKey: Hashable {
+ let title: String
+ let artist: String
+ let album: String
+
+ var isValid: Bool {
+ !title.isEmpty && !artist.isEmpty
+ }
+}
+
let defaultImage: NSImage = .init(
systemSymbolName: "heart.fill",
accessibilityDescription: "Album Art"
@@ -475,10 +485,16 @@ class MusicManager: ObservableObject {
// Task used to periodically sync displayed lyric with playback position
private var lyricSyncTask: Task?
+ private var lyricsFetchTask: Task?
+ private var lyricsFetchKey: LyricsLookupKey?
+ private var activeLyricsKey: LyricsLookupKey?
+ private var lyricsCache: [LyricsLookupKey: [LyricLine]] = [:]
private var explicitLookupTask: Task?
private var explicitLookupKey: String?
- private var artworkData: Data? = nil
+ private(set) var artworkData: Data? = nil
+
+ @Published var videoArtworkURL: URL? = nil
private var liveStreamUnknownDurationCount: Int = 0
private var liveStreamEdgeObservationCount: Int = 0
@@ -512,6 +528,13 @@ class MusicManager: ObservableObject {
}
.store(in: &cancellables)
+ Defaults.publisher(.enableLyrics)
+ .receive(on: RunLoop.main)
+ .sink { [weak self] change in
+ self?.handleLyricsPreferenceChange(isEnabled: change.newValue)
+ }
+ .store(in: &cancellables)
+
// Observe Pear Desktop launch/terminate for auto-detection
setupPearDesktopAutoDetection()
@@ -580,6 +603,8 @@ class MusicManager: ObservableObject {
public func destroy() {
debounceIdleTask?.cancel()
+ lyricsFetchTask?.cancel()
+ lyricSyncTask?.cancel()
explicitLookupTask?.cancel()
cancellables.removeAll()
controllerCancellables.removeAll()
@@ -701,6 +726,7 @@ class MusicManager: ObservableObject {
// Check for artwork changes
let artworkChanged = state.artwork != nil && state.artwork != self.artworkData
+
let hasContentChange =
titleChanged
|| artistChanged
@@ -709,6 +735,11 @@ class MusicManager: ObservableObject {
|| bundleChanged
|| contentIdentifierChanged
|| contentURLChanged
+ let liveArtworkChanged = state.liveArtworkURL != self.videoArtworkURL
+
+ if liveArtworkChanged {
+ self.videoArtworkURL = state.liveArtworkURL
+ }
// Handle artwork and visual transitions for changed content
let shouldAutoPeekOnTrackChange = Defaults[.showSneakPeekOnTrackChange]
@@ -735,10 +766,16 @@ class MusicManager: ObservableObject {
self.lastArtworkContentIdentifier = state.contentIdentifier
self.lastArtworkContentURL = state.contentURL
- // Fetch lyrics for new track whenever content changes
- self.fetchLyrics()
+ self.prepareLyricsForCurrentTrack()
+ if let liveArtworkURL = state.liveArtworkURL {
+ self.videoArtworkURL = liveArtworkURL
+ } else {
+ self.fetchVideoArtwork()
+ }
+
self.refreshExplicitFlag(for: state)
+
// Only update sneak peek if there's actual content and something changed
if shouldAutoPeekOnTrackChange && !state.title.isEmpty && !state.artist.isEmpty && state.isPlaying {
self.updateSneakPeek()
@@ -1238,53 +1275,108 @@ class MusicManager: ObservableObject {
// MARK: - Lyrics Methods
func fetchLyrics() {
- guard Defaults[.enableLyrics] else { return }
- // If the lyrics panel is visible already, provide immediate feedback
- if showLyrics {
- Task { @MainActor in
- self.currentLyrics = "Loading lyrics..."
- self.syncedLyrics = []
- self.currentLyricIndex = -1
- }
+ prepareLyricsForCurrentTrack(forceFetch: true, prioritizeVisibleResult: Defaults[.enableLyrics])
+ }
+
+ private func handleLyricsPreferenceChange(isEnabled: Bool) {
+ showLyrics = isEnabled
+
+ if isEnabled {
+ prepareLyricsForCurrentTrack(prioritizeVisibleResult: true)
+ } else {
+ stopLyricSync()
+ }
+ }
+
+ private func prepareLyricsForCurrentTrack(forceFetch: Bool = false, prioritizeVisibleResult: Bool = false) {
+ guard let lookup = currentLyricsLookupContext() else {
+ activeLyricsKey = nil
+ lyricsFetchKey = nil
+ lyricsFetchTask?.cancel()
+ lyricsFetchTask = nil
+ syncedLyrics = []
+ currentLyrics = ""
+ currentLyricIndex = -1
+ stopLyricSync()
+ return
}
- Task {
- do {
- let lyrics = try await fetchLyricsFromAPI(artist: artistName, title: songTitle)
- await MainActor.run {
- self.syncedLyrics = lyrics
- self.currentLyricIndex = -1
- if !lyrics.isEmpty {
- self.currentLyrics = lyrics[0].text
- } else {
- self.currentLyrics = ""
- }
+ let key = lookup.key
+ let lyricsEnabled = Defaults[.enableLyrics]
+ let shouldShowLoading = lyricsEnabled && prioritizeVisibleResult
+ let trackChanged = activeLyricsKey != key
+ activeLyricsKey = key
- // If lyrics are enabled, start syncing them to playback position
- if Defaults[.enableLyrics] && !self.syncedLyrics.isEmpty {
- self.startLyricSync()
- } else if self.syncedLyrics.isEmpty {
- self.stopLyricSync()
- }
+ if trackChanged {
+ syncedLyrics = []
+ currentLyricIndex = -1
+ currentLyrics = shouldShowLoading ? "Loading lyrics..." : ""
+ stopLyricSync()
+ }
+
+ if !forceFetch, let cachedLyrics = lyricsCache[key] {
+ applyLyricsToDisplay(cachedLyrics)
+ return
+ }
+
+ if lyricsFetchKey == key {
+ if shouldShowLoading && syncedLyrics.isEmpty {
+ currentLyrics = "Loading lyrics..."
+ }
+ return
+ }
+
+ lyricsFetchTask?.cancel()
+ lyricsFetchKey = key
+
+ if shouldShowLoading || (lyricsEnabled && syncedLyrics.isEmpty) {
+ currentLyrics = "Loading lyrics..."
+ }
+
+ let requestArtist = lookup.requestArtist
+ let requestTitle = lookup.requestTitle
+ let requestAlbum = lookup.requestAlbum
+
+ lyricsFetchTask = Task { [weak self] in
+ guard let self else { return }
+
+ do {
+ let lyrics = try await self.fetchLyricsFromAPI(
+ artist: requestArtist,
+ title: requestTitle,
+ album: requestAlbum
+ )
+ guard !Task.isCancelled else { return }
+
+ await MainActor.run {
+ guard self.activeLyricsKey == key else { return }
+ self.lyricsCache[key] = lyrics
+ self.lyricsFetchKey = nil
+ self.lyricsFetchTask = nil
+ self.applyLyricsToDisplay(lyrics)
}
} catch {
print("Failed to fetch lyrics: \(error)")
await MainActor.run {
+ guard self.activeLyricsKey == key else { return }
+ self.lyricsFetchKey = nil
+ self.lyricsFetchTask = nil
self.syncedLyrics = []
- self.currentLyrics = ""
self.currentLyricIndex = -1
+ self.currentLyrics = lyricsEnabled ? "No lyrics found" : ""
self.stopLyricSync()
}
}
}
}
- private func fetchLyricsFromAPI(artist: String, title: String) async throws -> [LyricLine] {
+ private func fetchLyricsFromAPI(artist: String, title: String, album: String) async throws -> [LyricLine] {
guard !artist.isEmpty, !title.isEmpty else { return [] }
// Normalize input and percent-encode
let cleanArtist = artist.folding(options: .diacriticInsensitive, locale: .current)
let cleanTitle = title.folding(options: .diacriticInsensitive, locale: .current)
+ let cleanAlbum = album.folding(options: .diacriticInsensitive, locale: .current)
guard let encodedArtist = cleanArtist.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let encodedTitle = cleanTitle.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
return []
@@ -1298,7 +1390,8 @@ class MusicManager: ObservableObject {
if let http = response as? HTTPURLResponse, http.statusCode == 200 {
// Try parse as array JSON (preferred)
if let jsonArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]],
- let first = jsonArray.first {
+ let bestMatch = bestLyricsMatch(in: jsonArray, artist: cleanArtist, title: cleanTitle, album: cleanAlbum) {
+ let first = bestMatch
let plain = (first["plainLyrics"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let synced = (first["syncedLyrics"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
@@ -1340,6 +1433,107 @@ class MusicManager: ObservableObject {
}
}
+ private func currentLyricsLookupContext() -> (key: LyricsLookupKey, requestArtist: String, requestTitle: String, requestAlbum: String)? {
+ let requestArtist = normalizedLyricsRequestComponent(artistName)
+ let requestTitle = normalizedLyricsTitle(songTitle)
+ let requestAlbum = normalizedLyricsRequestComponent(album)
+
+ let key = LyricsLookupKey(
+ title: requestTitle.lowercased(),
+ artist: requestArtist.lowercased(),
+ album: requestAlbum.lowercased()
+ )
+
+ return key.isValid ? (key, requestArtist, requestTitle, requestAlbum) : nil
+ }
+
+ private func normalizedLyricsRequestComponent(_ value: String) -> String {
+ value
+ .trimmingCharacters(in: .whitespacesAndNewlines)
+ .replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression)
+ }
+
+ private func normalizedLyricsTitle(_ value: String) -> String {
+ var normalized = normalizedLyricsRequestComponent(value)
+ let cleanupPatterns = [
+ "\\s*\\((feat\\.?|ft\\.?|featuring)[^\\)]*\\)",
+ "\\s*\\[(feat\\.?|ft\\.?|featuring)[^\\]]*\\]",
+ "\\s*-\\s*(feat\\.?|ft\\.?|featuring)\\s+.*$",
+ "\\s*\\((remaster(ed)?|live|mono|stereo)[^\\)]*\\)$",
+ "\\s*\\[(remaster(ed)?|live|mono|stereo)[^\\]]*\\]$"
+ ]
+
+ for pattern in cleanupPatterns {
+ normalized = normalized.replacingOccurrences(
+ of: pattern,
+ with: "",
+ options: [.regularExpression, .caseInsensitive]
+ )
+ }
+
+ return normalizedLyricsRequestComponent(normalized)
+ }
+
+ private func bestLyricsMatch(in results: [[String: Any]], artist: String, title: String, album: String) -> [String: Any]? {
+ let normalizedArtist = artist.lowercased()
+ let normalizedTitle = title.lowercased()
+ let normalizedAlbum = album.lowercased()
+
+ return results.max { lhs, rhs in
+ lyricsMatchScore(for: lhs, artist: normalizedArtist, title: normalizedTitle, album: normalizedAlbum)
+ < lyricsMatchScore(for: rhs, artist: normalizedArtist, title: normalizedTitle, album: normalizedAlbum)
+ }
+ }
+
+ private func lyricsMatchScore(for result: [String: Any], artist: String, title: String, album: String) -> Int {
+ let resultArtist = ((result["artistName"] as? String) ?? "").lowercased()
+ let resultTitle = ((result["trackName"] as? String) ?? "").lowercased()
+ let resultAlbum = ((result["albumName"] as? String) ?? "").lowercased()
+
+ var score = 0
+
+ if resultTitle == title { score += 8 }
+ else if resultTitle.contains(title) || title.contains(resultTitle) { score += 4 }
+
+ if resultArtist == artist { score += 8 }
+ else if resultArtist.contains(artist) || artist.contains(resultArtist) { score += 4 }
+
+ if !album.isEmpty {
+ if resultAlbum == album { score += 4 }
+ else if resultAlbum.contains(album) || album.contains(resultAlbum) { score += 2 }
+ }
+
+ if !(result["syncedLyrics"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
+ score += 3
+ }
+
+ return score
+ }
+
+ private func applyLyricsToDisplay(_ lyrics: [LyricLine]) {
+ syncedLyrics = lyrics
+ currentLyricIndex = -1
+
+ guard !lyrics.isEmpty else {
+ currentLyrics = Defaults[.enableLyrics] ? "No lyrics found" : ""
+ stopLyricSync()
+ return
+ }
+
+ let playbackPosition = max(estimatedPlaybackPosition(), elapsedTime)
+ updateCurrentLyric(for: playbackPosition)
+
+ if currentLyricIndex == -1, let firstLine = lyrics.first?.text {
+ currentLyrics = firstLine
+ }
+
+ if Defaults[.enableLyrics] {
+ startLyricSync()
+ } else {
+ stopLyricSync()
+ }
+ }
+
private func parseLRC(_ lrc: String) -> [LyricLine] {
let lines = lrc.components(separatedBy: .newlines)
var lyrics: [LyricLine] = []
@@ -1424,6 +1618,32 @@ class MusicManager: ObservableObject {
lyricSyncTask = nil
}
+ // MARK: - Video Artwork
+
+ func fetchVideoArtwork() {
+ guard Defaults[.lockScreenMusicFullscreenVideoArtwork] else {
+ videoArtworkURL = nil
+ return
+ }
+ // Se il player non è Apple Music, non toccare videoArtworkURL:
+ // SpotifyController gestisce il canvas in modo autonomo tramite liveArtworkURL.
+ guard bundleIdentifier == "com.apple.Music" else {
+ return
+ }
+
+ let title = songTitle
+ let artist = artistName
+
+ Task {
+ let url = await AnimatedArtworkManager.shared.fetchAnimatedArtworkURL(
+ title: title, artist: artist
+ )
+ await MainActor.run {
+ self.videoArtworkURL = url
+ }
+ }
+ }
+
func toggleLyrics() {
// Toggle the UI state first so the views can react immediately.
showLyrics.toggle()
@@ -1518,7 +1738,6 @@ private struct AlbumArtFlipModifier: ViewModifier {
// Use a small tolerance to avoid flickering exactly at 90°/270°.
if cos > 0.001 { return 1 }
if cos < -0.001 { return -1 }
- // At the exact edge, prefer the side we're animating toward.
return degrees.truncatingRemainder(dividingBy: 360) >= 0 ? -1 : 1
}
}
diff --git a/DynamicIsland/managers/SpotifyAuthManager.swift b/DynamicIsland/managers/SpotifyAuthManager.swift
new file mode 100644
index 0000000..f733452
--- /dev/null
+++ b/DynamicIsland/managers/SpotifyAuthManager.swift
@@ -0,0 +1,343 @@
+/*
+ * Atoll (DynamicIsland)
+ * Copyright (C) 2024-2026 Atoll Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import CryptoKit
+import Defaults
+import Foundation
+
+extension Notification.Name {
+ static let spotifyCanvasSessionDidChange = Notification.Name("SpotifyCanvasSessionDidChange")
+}
+
+@MainActor
+final class SpotifyAuthManager: ObservableObject {
+ static let shared = SpotifyAuthManager()
+
+ @Published private(set) var isAuthenticated = false
+ @Published private(set) var isAuthorizing = false
+ @Published private(set) var authErrorMessage: String?
+ @Published private(set) var sessionStatusText = "Spotify cookie not configured."
+
+ private let secretsURL = URL(string: "https://raw.githubusercontent.com/xyloflake/spot-secrets-go/main/secrets/secretDict.json")!
+ private let tokenURL = URL(string: "https://open.spotify.com/api/token")!
+ private let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
+
+ private init() {
+ restorePersistedState()
+ }
+
+ var configuredCookie: String {
+ Self.sanitizeCookie(Defaults[.spotifySPDCCookie])
+ }
+
+ var hasConfiguredCookie: Bool {
+ !configuredCookie.isEmpty
+ }
+
+ func validateSession() async {
+ let sanitizedCookie = configuredCookie
+ Defaults[.spotifySPDCCookie] = sanitizedCookie
+ clearCachedToken()
+ Defaults[.spotifyAuthLastValidatedAt] = 0
+ authErrorMessage = nil
+ restorePersistedState()
+
+ guard !sanitizedCookie.isEmpty else {
+ authErrorMessage = "Paste the Spotify sp_dc cookie first."
+ return
+ }
+
+ isAuthorizing = true
+ defer {
+ isAuthorizing = false
+ restorePersistedState()
+ }
+
+ guard await validAccessToken(forceRefresh: true) != nil else {
+ if authErrorMessage == nil {
+ authErrorMessage = "Spotify rejected the sp_dc cookie."
+ }
+ return
+ }
+
+ authErrorMessage = nil
+ NotificationCenter.default.post(name: .spotifyCanvasSessionDidChange, object: nil)
+ }
+
+ func clearSession() {
+ Defaults[.spotifySPDCCookie] = ""
+ clearCachedToken()
+ Defaults[.spotifyAuthLastValidatedAt] = 0
+ authErrorMessage = nil
+ restorePersistedState()
+ NotificationCenter.default.post(name: .spotifyCanvasSessionDidChange, object: nil)
+ }
+
+ func invalidateAccessToken() {
+ clearCachedToken()
+ restorePersistedState()
+ }
+
+ func validAccessToken(forceRefresh: Bool = false) async -> String? {
+ let sanitizedCookie = configuredCookie
+ if sanitizedCookie != Defaults[.spotifySPDCCookie] {
+ Defaults[.spotifySPDCCookie] = sanitizedCookie
+ }
+
+ guard !sanitizedCookie.isEmpty else {
+ restorePersistedState()
+ return nil
+ }
+
+ let cachedToken = Defaults[.spotifyAuthAccessToken]
+ let cachedExpiration = Defaults[.spotifyAuthAccessTokenExpiration]
+ if !forceRefresh, !cachedToken.isEmpty, cachedExpiration > Date().timeIntervalSince1970 + 60 {
+ restorePersistedState()
+ return cachedToken
+ }
+
+ do {
+ let response = try await fetchWebPlayerToken(spDC: sanitizedCookie)
+ Defaults[.spotifyAuthAccessToken] = response.accessToken
+ Defaults[.spotifyAuthAccessTokenExpiration] = response.expirationDate.timeIntervalSince1970
+ Defaults[.spotifyAuthLastValidatedAt] = Date().timeIntervalSince1970
+ authErrorMessage = nil
+ restorePersistedState()
+ return response.accessToken
+ } catch {
+ clearCachedToken()
+ Defaults[.spotifyAuthLastValidatedAt] = 0
+ authErrorMessage = error.localizedDescription
+ restorePersistedState()
+ return nil
+ }
+ }
+
+ private func fetchWebPlayerToken(spDC: String) async throws -> SpotifyWebPlayerTokenResponse {
+ let secrets = try await fetchSecrets()
+ guard let latestSecret = latestSecretEntry(from: secrets) else {
+ throw SpotifyAuthError.missingSecrets
+ }
+
+ let totp = try Self.generateTOTPCode(from: latestSecret.cipher)
+
+ var components = URLComponents(url: tokenURL, resolvingAgainstBaseURL: false)
+ components?.queryItems = [
+ URLQueryItem(name: "reason", value: "transport"),
+ URLQueryItem(name: "productType", value: "web-player"),
+ URLQueryItem(name: "totp", value: totp),
+ URLQueryItem(name: "totpServer", value: totp),
+ URLQueryItem(name: "totpVer", value: latestSecret.version)
+ ]
+
+ guard let url = components?.url else {
+ throw SpotifyAuthError.invalidRequest
+ }
+
+ var request = URLRequest(url: url)
+ request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
+ request.setValue("application/json", forHTTPHeaderField: "Accept")
+ request.setValue("https://open.spotify.com/", forHTTPHeaderField: "Referer")
+ request.setValue("WebPlayer", forHTTPHeaderField: "App-Platform")
+ request.setValue("sp_dc=\(spDC)", forHTTPHeaderField: "Cookie")
+
+ let (data, response) = try await URLSession.shared.data(for: request)
+ try Self.validateHTTPResponse(response, data: data)
+
+ let tokenResponse = try JSONDecoder().decode(SpotifyWebPlayerTokenResponse.self, from: data)
+ if tokenResponse.accessToken.isEmpty {
+ throw SpotifyAuthError.invalidTokenResponse
+ }
+
+ return tokenResponse
+ }
+
+ private func fetchSecrets() async throws -> [String: [Int]] {
+ var request = URLRequest(url: secretsURL)
+ request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
+
+ let (data, response) = try await URLSession.shared.data(for: request)
+ try Self.validateHTTPResponse(response, data: data)
+ return try JSONDecoder().decode([String: [Int]].self, from: data)
+ }
+
+ private func latestSecretEntry(from secrets: [String: [Int]]) -> (version: String, cipher: [Int])? {
+ var latestVersion: String?
+ var latestCipher: [Int]?
+ var highestNumericVersion = Int.min
+
+ for (version, cipher) in secrets {
+ guard let numericVersion = Int(version) else { continue }
+ if numericVersion > highestNumericVersion {
+ highestNumericVersion = numericVersion
+ latestVersion = version
+ latestCipher = cipher
+ }
+ }
+
+ guard let latestVersion, let latestCipher else {
+ return nil
+ }
+
+ return (latestVersion, latestCipher)
+ }
+
+ private func clearCachedToken() {
+ Defaults[.spotifyAuthAccessToken] = ""
+ Defaults[.spotifyAuthAccessTokenExpiration] = 0
+ }
+
+ private func restorePersistedState() {
+ let cookie = configuredCookie
+ let hasToken = !Defaults[.spotifyAuthAccessToken].isEmpty
+ && Defaults[.spotifyAuthAccessTokenExpiration] > Date().timeIntervalSince1970 + 60
+ let hasValidatedCookie = Defaults[.spotifyAuthLastValidatedAt] > 0
+
+ isAuthenticated = !cookie.isEmpty && (hasToken || hasValidatedCookie)
+
+ if cookie.isEmpty {
+ sessionStatusText = "Spotify cookie not configured."
+ } else if hasToken {
+ sessionStatusText = "Spotify Canvas session ready."
+ } else if hasValidatedCookie {
+ sessionStatusText = "Cookie saved. The access token will refresh automatically."
+ } else {
+ sessionStatusText = "Cookie pasted. Validate it once to enable Canvas lookup."
+ }
+ }
+
+ static func sanitizeCookie(_ rawValue: String) -> String {
+ var value = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
+
+ if let range = value.range(of: "sp_dc=") {
+ value = String(value[range.upperBound...])
+ }
+
+ if let firstSemicolon = value.firstIndex(of: ";") {
+ value = String(value[.. String {
+ let transformed = cipher.enumerated().map { index, value in
+ value ^ ((index % 33) + 9)
+ }
+
+ let secretString = transformed.map(String.init).joined()
+ guard let secretData = secretString.data(using: .utf8) else {
+ throw SpotifyAuthError.invalidSecretMaterial
+ }
+
+ let counter = UInt64(Date().timeIntervalSince1970 / 30)
+ var counterBytes = counter.bigEndian
+ let counterData = Data(bytes: &counterBytes, count: MemoryLayout.size)
+
+ let mac = HMAC.authenticationCode(
+ for: counterData,
+ using: SymmetricKey(data: secretData)
+ )
+ let hashData = Data(mac)
+ let offset = Int(hashData.last.map { $0 & 0x0F } ?? 0)
+ let truncatedRange = offset..<(offset + 4)
+
+ guard truncatedRange.upperBound <= hashData.count else {
+ throw SpotifyAuthError.invalidSecretMaterial
+ }
+
+ let truncatedBytes = Array(hashData[truncatedRange])
+ let truncated =
+ ((UInt32(truncatedBytes[0]) << 24)
+ | (UInt32(truncatedBytes[1]) << 16)
+ | (UInt32(truncatedBytes[2]) << 8)
+ | UInt32(truncatedBytes[3]))
+ & 0x7FFF_FFFF
+
+ return String(format: "%06u", truncated % 1_000_000)
+ }
+
+ private static func validateHTTPResponse(_ response: URLResponse, data: Data) throws {
+ guard let httpResponse = response as? HTTPURLResponse else {
+ throw SpotifyAuthError.invalidServerResponse
+ }
+
+ guard (200..<300).contains(httpResponse.statusCode) else {
+ if let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
+ let message = object["error"] as? String {
+ throw SpotifyAuthError.serverError(message)
+ }
+
+ if let body = String(data: data, encoding: .utf8), !body.isEmpty {
+ throw SpotifyAuthError.serverError(body)
+ }
+
+ throw SpotifyAuthError.httpFailure(httpResponse.statusCode)
+ }
+ }
+}
+
+private struct SpotifyWebPlayerTokenResponse: Decodable {
+ let accessToken: String
+ let accessTokenExpirationTimestampMs: Double?
+
+ var expirationDate: Date {
+ if let accessTokenExpirationTimestampMs {
+ return Date(timeIntervalSince1970: accessTokenExpirationTimestampMs / 1000)
+ }
+ return Date().addingTimeInterval(3300)
+ }
+
+ private enum CodingKeys: String, CodingKey {
+ case accessToken
+ case accessTokenExpirationTimestampMs
+ }
+}
+
+private enum SpotifyAuthError: LocalizedError {
+ case invalidRequest
+ case invalidServerResponse
+ case invalidTokenResponse
+ case invalidSecretMaterial
+ case missingSecrets
+ case httpFailure(Int)
+ case serverError(String)
+
+ var errorDescription: String? {
+ switch self {
+ case .invalidRequest:
+ return "Unable to build the Spotify token request."
+ case .invalidServerResponse:
+ return "Spotify returned an invalid response."
+ case .invalidTokenResponse:
+ return "Spotify did not return a usable access token."
+ case .invalidSecretMaterial:
+ return "The Spotify TOTP secret could not be decoded."
+ case .missingSecrets:
+ return "No valid Spotify TOTP secret was found."
+ case let .httpFailure(statusCode):
+ return "Spotify token request failed with HTTP \(statusCode)."
+ case let .serverError(message):
+ return message
+ }
+ }
+}
diff --git a/DynamicIsland/managers/SystemChangesObserver.swift b/DynamicIsland/managers/SystemChangesObserver.swift
index e9d59fc..3525322 100644
--- a/DynamicIsland/managers/SystemChangesObserver.swift
+++ b/DynamicIsland/managers/SystemChangesObserver.swift
@@ -38,9 +38,18 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
"headphones"
]
- private let standardVolumeStep: Float = 1.0 / 16.0
- private let standardBrightnessStep: Float = 1.0 / 16.0
- private let fineStepDivisor: Float = 4.0
+ private var standardVolumeStep: Float {
+ Float(Defaults[.volumeStepPercent]) / 100.0
+ }
+ private var fineVolumeStep: Float {
+ Float(Defaults[.volumeFineStepPercent]) / 100.0
+ }
+ private var standardBrightnessStep: Float {
+ Float(Defaults[.brightnessStepPercent]) / 100.0
+ }
+ private var fineBrightnessStep: Float {
+ Float(Defaults[.brightnessFineStepPercent]) / 100.0
+ }
private var volumeEnabled = false
private var brightnessEnabled = false
@@ -157,7 +166,7 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
}
}
- let baseStep = stepSize(for: step, base: standardVolumeStep)
+ let baseStep = volumeStep(for: step)
let delta = direction == .up ? baseStep : -baseStep
volumeController.adjust(by: delta)
volumeFeedbackPlayer.playIfNeeded()
@@ -185,7 +194,7 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
}
}
- let baseStep = stepSize(for: step, base: standardBrightnessStep)
+ let baseStep = brightnessStep(for: step)
let delta = direction == .up ? baseStep : -baseStep
if modifiers.contains(.command) && keyboardBacklightEnabled {
keyboardBacklightController.adjust(by: delta)
@@ -336,12 +345,21 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
}
private extension SystemChangesObserver {
- func stepSize(for step: MediaKeyStep, base: Float) -> Float {
+ func volumeStep(for step: MediaKeyStep) -> Float {
switch step {
case .standard:
- return base
+ return standardVolumeStep
case .fine:
- return base / fineStepDivisor
+ return fineVolumeStep
+ }
+ }
+
+ func brightnessStep(for step: MediaKeyStep) -> Float {
+ switch step {
+ case .standard:
+ return standardBrightnessStep
+ case .fine:
+ return fineBrightnessStep
}
}
}
diff --git a/DynamicIsland/managers/SystemDisplayManager.swift b/DynamicIsland/managers/SystemDisplayManager.swift
index 94195f3..93fa377 100644
--- a/DynamicIsland/managers/SystemDisplayManager.swift
+++ b/DynamicIsland/managers/SystemDisplayManager.swift
@@ -46,7 +46,7 @@ class SystemDisplayManager {
private static func getStandardDisplayBrightness() throws -> Float {
var brightness: float_t = 1
- let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
+ let service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IODisplayConnect"))
defer {
IOObjectRelease(service)
}
diff --git a/DynamicIsland/managers/SystemMediaControllers.swift b/DynamicIsland/managers/SystemMediaControllers.swift
index d873e04..f61c8d7 100644
--- a/DynamicIsland/managers/SystemMediaControllers.swift
+++ b/DynamicIsland/managers/SystemMediaControllers.swift
@@ -476,6 +476,10 @@ final class SystemBrightnessController {
private let maximumBrightnessAnimationDuration: TimeInterval = 0.3
private let brightnessAnimationDurationScale: TimeInterval = 1.6
private var lastEmittedBrightness: Float = 0.5
+ private let coreBrightnessClient = CoreBrightnessDisplayClient.shared
+ private var pollTimer: Timer?
+ private let pollInterval: TimeInterval = 0.15
+ private let pollChangeThreshold: Float = 0.005
private init() {
registerExternalNotifications()
@@ -484,12 +488,15 @@ final class SystemBrightnessController {
func start() {
notifyCurrentBrightness()
+ startPollingIfNeeded()
}
func stop() {
onBrightnessChange = nil
brightnessAnimationTimer?.invalidate()
brightnessAnimationTimer = nil
+ pollTimer?.invalidate()
+ pollTimer = nil
}
func adjust(by delta: Float) {
@@ -506,6 +513,9 @@ final class SystemBrightnessController {
}
var currentBrightness: Float {
+ if let level = coreBrightnessClient.currentBrightness() {
+ return level
+ }
if let level = getBrightnessViaDisplayServices() {
return level
}
@@ -583,6 +593,9 @@ final class SystemBrightnessController {
private func applyBrightness(_ value: Float) {
let clamped = max(0, min(1, value))
+ if coreBrightnessClient.setBrightness(clamped) {
+ return
+ }
if setBrightnessViaDisplayServices(clamped) {
return
}
@@ -667,7 +680,8 @@ final class SystemBrightnessController {
let names = [
Notification.Name("com.apple.BezelEngine.BrightnessChanged"),
Notification.Name("com.apple.BezelServices.BrightnessChanged"),
- Notification.Name("com.apple.controlcenter.display.brightness")
+ Notification.Name("com.apple.controlcenter.display.brightness"),
+ Notification.Name("com.apple.CoreBrightness.DisplayBrightnessChanged")
]
observers = names.map { name in
DistributedNotificationCenter.default().addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in
@@ -677,8 +691,20 @@ final class SystemBrightnessController {
notificationsInstalled = true
}
+ private func startPollingIfNeeded() {
+ guard pollTimer == nil else { return }
+ pollTimer = Timer.scheduledTimer(withTimeInterval: pollInterval, repeats: true) { [weak self] _ in
+ guard let self else { return }
+ let system = self.currentBrightness
+ if abs(system - self.lastEmittedBrightness) > self.pollChangeThreshold {
+ self.emitBrightnessChange(value: system)
+ }
+ }
+ }
+
deinit {
brightnessAnimationTimer?.invalidate()
+ pollTimer?.invalidate()
observers.forEach { DistributedNotificationCenter.default().removeObserver($0) }
}
}
diff --git a/DynamicIsland/managers/SystemOSDManager.swift b/DynamicIsland/managers/SystemOSDManager.swift
index 52e8fc8..9281fda 100644
--- a/DynamicIsland/managers/SystemOSDManager.swift
+++ b/DynamicIsland/managers/SystemOSDManager.swift
@@ -17,12 +17,23 @@
*/
import Foundation
+import os
class SystemOSDManager {
private init() {}
+ // Tracks the PID we most recently suspended. macOS jetsam-exits OSDUIHelper
+ // when idle and launchd respawns it on the next media-key press as a fresh
+ // process, so we need to re-SIGSTOP every new incarnation.
+ private struct SuppressionState {
+ var task: Task?
+ var lastSuspendedPID: Int32 = -1
+ }
+ private static let suppressionState = OSAllocatedUnfairLock(initialState: SuppressionState())
+
/// Re-enables the system HUD by restarting OSDUIHelper
public static func enableSystemHUD() {
+ stopSuppressionWatcher()
Task.detached(priority: .background) {
await enableSystemHUDAsync()
}
@@ -77,11 +88,14 @@ class SystemOSDManager {
}
}
- /// Disables the system HUD by stopping OSDUIHelper
+ /// Disables the system HUD by stopping OSDUIHelper, and starts a
+ /// background watcher that re-suspends any future incarnation launchd
+ /// spawns (macOS auto-exits OSDUIHelper on idle).
public static func disableSystemHUD() {
Task.detached(priority: .background) {
await disableSystemHUDAsync()
}
+ startSuppressionWatcher()
}
private static func disableSystemHUDAsync() async {
@@ -92,15 +106,35 @@ class SystemOSDManager {
kickstart.arguments = ["kickstart", "gui/\(getuid())/com.apple.OSDUIHelper"]
try kickstart.run()
kickstart.waitUntilExit()
-
- try await Task.sleep(nanoseconds: 500_000_000) // 500ms - async wait instead of usleep
-
- let task = Process()
- task.executableURL = URL(fileURLWithPath: "/usr/bin/killall")
- task.arguments = ["-STOP", "OSDUIHelper"]
- try task.run()
- task.waitUntilExit()
-
+
+ // launchctl kickstart returns once the request is queued, not after
+ // OSDUIHelper has actually forked. At cold boot the helper can take
+ // a while to appear — a fixed sleep races and the SIGSTOP misses,
+ // letting the native OSD render on the first volume/brightness key.
+ // Poll for the PID up to ~5s, then suspend, and retry if launchd
+ // respawned a fresh copy between kickstart and SIGSTOP.
+ var attempts = 0
+ while attempts < 3 {
+ let appeared = await waitForOSDUIHelper(timeoutMillis: 5000)
+ if !appeared {
+ await MainActor.run {
+ NSLog("⚠️ OSDUIHelper did not appear within timeout; retrying SIGSTOP anyway")
+ }
+ }
+
+ suspendOSDUIHelper()
+
+ // Settle, then confirm a process is actually present (and thus
+ // suspended). If none is running, launchd hasn't spawned it yet
+ // or the prior STOP raced — loop and try again.
+ try await Task.sleep(nanoseconds: 250_000_000) // 250ms
+ if let pid = osduiHelperPID() {
+ suppressionState.withLock { $0.lastSuspendedPID = pid }
+ break
+ }
+ attempts += 1
+ }
+
await MainActor.run {
print("✅ System HUD disabled")
}
@@ -110,7 +144,92 @@ class SystemOSDManager {
}
}
}
-
+
+ /// Polls for an OSDUIHelper process, returning true as soon as one appears
+ /// or false if `timeoutMillis` elapses with no match.
+ private static func waitForOSDUIHelper(timeoutMillis: Int) async -> Bool {
+ let pollIntervalNanos: UInt64 = 200_000_000 // 200ms
+ let maxAttempts = max(1, timeoutMillis / 200)
+ for _ in 0.. Task? in
+ let prior = state.task
+ state.task = newTask
+ return prior
+ }
+ previous?.cancel()
+ }
+
+ private static func stopSuppressionWatcher() {
+ let previous = suppressionState.withLock { state -> Task? in
+ let prior = state.task
+ state.task = nil
+ state.lastSuspendedPID = -1
+ return prior
+ }
+ previous?.cancel()
+ }
+
+ /// Returns the newest OSDUIHelper PID, or nil if none.
+ private static func osduiHelperPID() -> Int32? {
+ let task = Process()
+ task.executableURL = URL(fileURLWithPath: "/usr/bin/pgrep")
+ task.arguments = ["-n", "OSDUIHelper"]
+ let pipe = Pipe()
+ task.standardOutput = pipe
+ do {
+ try task.run()
+ task.waitUntilExit()
+ let data = pipe.fileHandleForReading.readDataToEndOfFile()
+ let trimmed = String(data: data, encoding: .utf8)?
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ return Int32(trimmed)
+ } catch {
+ return nil
+ }
+ }
+
+ /// Sends SIGSTOP to all OSDUIHelper processes. Idempotent.
+ private static func suspendOSDUIHelper() {
+ let stop = Process()
+ stop.executableURL = URL(fileURLWithPath: "/usr/bin/killall")
+ stop.arguments = ["-STOP", "OSDUIHelper"]
+ do {
+ try stop.run()
+ stop.waitUntilExit()
+ } catch {
+ NSLog("Suppression watcher: failed to SIGSTOP OSDUIHelper: \(error)")
+ }
+ }
+
/// Check if OSDUIHelper is currently running
public static func isOSDUIHelperRunning() -> Bool {
let task = Process()
diff --git a/DynamicIsland/models/BatteryStatusViewModel.swift b/DynamicIsland/models/BatteryStatusViewModel.swift
index 0b9e792..b83fb42 100644
--- a/DynamicIsland/models/BatteryStatusViewModel.swift
+++ b/DynamicIsland/models/BatteryStatusViewModel.swift
@@ -26,6 +26,12 @@ import Foundation
import IOKit.ps
import SwiftUI
+enum BatteryTemporaryHUDKind: Equatable {
+ case charging
+ case lowBattery
+ case fullBattery
+}
+
/// A view model that manages and monitors the battery status of the device
class BatteryStatusViewModel: ObservableObject {
@@ -34,7 +40,6 @@ class BatteryStatusViewModel: ObservableObject {
private var runLoopSource: Unmanaged?
var animations: DynamicIslandAnimations = DynamicIslandAnimations()
private let lowBatteryAlertSoundPlayer = AudioPlayer()
- private let lowBatteryAlertThresholds: [Float] = [20, 15, 10, 5]
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@@ -46,6 +51,11 @@ class BatteryStatusViewModel: ObservableObject {
@Published private(set) var isInitial: Bool = false
@Published private(set) var timeToFullCharge: Int = 0
@Published private(set) var statusText: String = ""
+ @Published private(set) var activeTemporaryHUDKind: BatteryTemporaryHUDKind?
+ @Published private(set) var activeTemporaryHUDToken: UUID = UUID()
+ @Published private(set) var activeTemporaryHUDTargetScreenName: String?
+ @Published private(set) var activeTemporaryHUDLevelOverride: Int?
+ @Published private(set) var activeTemporaryHUDLowPowerModeOverride: Bool?
private let managerBattery = BatteryActivityManager.shared
private var managerBatteryId: Int?
@@ -79,33 +89,39 @@ class BatteryStatusViewModel: ObservableObject {
switch event {
case .powerSourceChanged(let isPluggedIn):
print("🔌 Power source: \(isPluggedIn ? "Connected" : "Disconnected")")
+ let wasPluggedIn = self.isPluggedIn
withAnimation {
self.isPluggedIn = isPluggedIn
self.statusText = isPluggedIn ? String(localized: "Plugged In") : String(localized: "Unplugged")
- self.notifyImportanChangeStatus()
+ }
+ if !wasPluggedIn && isPluggedIn {
+ presentTemporaryBatteryHUDIfNeeded(kind: .charging)
}
case .batteryLevelChanged(let level):
print("🔋 Battery level: \(Int(level))%")
let previousLevel = self.levelBattery
- self.handleLowBatteryAlertIfNeeded(previousLevel: previousLevel, newLevel: level)
withAnimation {
self.levelBattery = level
}
+ self.handleLowBatteryAlertIfNeeded(previousLevel: previousLevel, newLevel: level)
+ self.handleFullBatteryAlertIfNeeded(previousLevel: previousLevel, newLevel: level)
case .lowPowerModeChanged(let isEnabled):
print("⚡ Low power mode: \(isEnabled ? "Enabled" : "Disabled")")
- self.notifyImportanChangeStatus()
+ let wasEnabled = self.isInLowPowerMode
withAnimation {
self.isInLowPowerMode = isEnabled
self.statusText = String(localized: "Low Power: \(self.isInLowPowerMode ? String(localized: "On") : String(localized: "Off"))")
}
+ if !wasEnabled && isEnabled {
+ presentTemporaryBatteryHUDIfNeeded(kind: .lowBattery)
+ }
case .isChargingChanged(let isCharging):
print("🔌 Charging: \(isCharging ? "Yes" : "No")")
print("maxCapacity: \(self.maxCapacity)")
print("levelBattery: \(self.levelBattery)")
- self.notifyImportanChangeStatus()
withAnimation {
self.isCharging = isCharging
self.statusText =
@@ -145,30 +161,131 @@ class BatteryStatusViewModel: ObservableObject {
}
}
- /// Notifies important changes in the battery status with an optional delay
- /// - Parameter delay: The delay before notifying the change, default is 0.0
- private func notifyImportanChangeStatus(delay: Double = 0.0) {
- Task {
- try? await Task.sleep(for: .seconds(delay))
- self.coordinator.toggleExpandingView(status: true, type: .battery)
+ private func presentTemporaryBatteryHUDIfNeeded(kind: BatteryTemporaryHUDKind) {
+ presentTemporaryBatteryHUDIfNeeded(kind: kind, force: false)
+ }
+
+ func triggerTestHUD(kind: BatteryTemporaryHUDKind) {
+ let previewLevel: Int
+
+ switch kind {
+ case .charging:
+ previewLevel = max(12, min(95, Int(levelBattery.rounded())))
+ case .lowBattery:
+ previewLevel = max(5, min(20, Defaults[.lowBatteryHUDThreshold]))
+ case .fullBattery:
+ previewLevel = 100
}
+
+ presentTemporaryBatteryHUDIfNeeded(
+ kind: kind,
+ force: true,
+ levelOverride: previewLevel,
+ lowPowerModeOverride: kind == .lowBattery ? isInLowPowerMode : nil
+ )
+ }
+
+ private func presentTemporaryBatteryHUDIfNeeded(
+ kind: BatteryTemporaryHUDKind,
+ force: Bool,
+ levelOverride: Int? = nil,
+ lowPowerModeOverride: Bool? = nil
+ ) {
+ guard force || Defaults[.showPowerStatusNotifications] else { return }
+
+ let duration: Int
+ let isEnabled: Bool
+
+ switch kind {
+ case .charging:
+ duration = Defaults[.chargingBatteryHUDDuration]
+ isEnabled = Defaults[.showChargingBatteryHUD]
+ case .lowBattery:
+ duration = Defaults[.lowBatteryHUDDuration]
+ isEnabled = Defaults[.showLowBatteryHUD]
+ case .fullBattery:
+ duration = Defaults[.fullBatteryHUDDuration]
+ isEnabled = Defaults[.showFullBatteryHUD]
+ }
+
+ guard force || isEnabled else { return }
+
+ activeTemporaryHUDKind = kind
+ activeTemporaryHUDToken = UUID()
+ activeTemporaryHUDTargetScreenName = resolvedTemporaryHUDTargetScreenName()
+ activeTemporaryHUDLevelOverride = levelOverride
+ activeTemporaryHUDLowPowerModeOverride = lowPowerModeOverride
+ coordinator.toggleExpandingView(
+ status: true,
+ type: .battery,
+ autoHideDuration: TimeInterval(max(1, duration))
+ )
+ }
+
+ private func resolvedTemporaryHUDTargetScreenName() -> String? {
+ if Defaults[.showOnAllDisplays] {
+ return nil
+ }
+
+ let preferredNames = [
+ coordinator.selectedScreen,
+ coordinator.preferredScreen,
+ NSScreen.main?.localizedName
+ ]
+ .compactMap { $0 }
+
+ for candidate in preferredNames where NSScreen.screens.contains(where: { $0.localizedName == candidate }) {
+ return candidate
+ }
+
+ return NSScreen.screens.first?.localizedName
+ }
+
+ private func preferredDynamicIslandTargetScreenName() -> String? {
+ let mainScreenName = NSScreen.main?.localizedName
+ let preferredNames = [coordinator.selectedScreen, coordinator.preferredScreen]
+
+ for candidate in preferredNames where shouldUseDynamicIslandMode(for: candidate) {
+ return candidate
+ }
+
+ if let externalDynamicIslandScreen = NSScreen.screens.first(where: {
+ $0.localizedName != mainScreenName && shouldUseDynamicIslandMode(for: $0.localizedName)
+ }) {
+ return externalDynamicIslandScreen.localizedName
+ }
+
+ if let anyDynamicIslandScreen = NSScreen.screens.first(where: {
+ shouldUseDynamicIslandMode(for: $0.localizedName)
+ }) {
+ return anyDynamicIslandScreen.localizedName
+ }
+
+ return nil
}
private func handleLowBatteryAlertIfNeeded(previousLevel: Float, newLevel: Float) {
- guard Defaults[.playLowBatteryAlertSound] else { return }
guard !isPluggedIn, !isCharging else { return }
guard newLevel < previousLevel else { return }
+ let threshold = Float(Defaults[.lowBatteryHUDThreshold])
+ guard previousLevel > threshold && newLevel <= threshold else { return }
- for threshold in lowBatteryAlertThresholds {
- if previousLevel >= threshold && newLevel < threshold {
- self.statusText = String(localized: "Low battery")
- notifyImportanChangeStatus()
- playLowBatteryAlertSound()
- break
- }
+ self.statusText = String(localized: "Low battery")
+ presentTemporaryBatteryHUDIfNeeded(kind: .lowBattery)
+ if Defaults[.playLowBatteryAlertSound] {
+ playLowBatteryAlertSound()
}
}
+ private func handleFullBatteryAlertIfNeeded(previousLevel: Float, newLevel: Float) {
+ guard newLevel > previousLevel else { return }
+ let threshold = Float(Defaults[.fullBatteryHUDThreshold])
+ guard previousLevel < threshold && newLevel >= threshold else { return }
+
+ self.statusText = String(localized: "Full charge")
+ presentTemporaryBatteryHUDIfNeeded(kind: .fullBattery)
+ }
+
private func playLowBatteryAlertSound() {
lowBatteryAlertSoundPlayer.play(fileName: "lowbattery", fileExtension: "mp3")
}
diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift
index a440805..6e557b7 100644
--- a/DynamicIsland/models/Constants.swift
+++ b/DynamicIsland/models/Constants.swift
@@ -473,6 +473,22 @@ enum ProgressBarStyle: String, CaseIterable, Identifiable, Defaults.Serializable
var id: String { self.rawValue }
}
+enum BatteryNotificationStyle: String, CaseIterable, Identifiable, Defaults.Serializable {
+ case standard
+ case compact
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .standard:
+ return String(localized: "Standard")
+ case .compact:
+ return String(localized: "Compact")
+ }
+ }
+}
+
enum MusicAuxiliaryControl: String, CaseIterable, Identifiable, Defaults.Serializable {
case shuffle
case repeatMode
@@ -910,6 +926,10 @@ extension Defaults.Keys {
)
static let lockScreenPanelUsesBlur = Key("lockScreenPanelUsesBlur", default: true)
static let lockScreenMusicMergedAirPlayOutput = Key("lockScreenMusicMergedAirPlayOutput", default: true)
+ static let lockScreenMusicFullscreenArtworkEnabled = Key("lockScreenMusicFullscreenArtworkEnabled", default: true)
+ static let lockScreenKeepAlbumArtVisibleDuringFullscreenArtwork = Key("lockScreenKeepAlbumArtVisibleDuringFullscreenArtwork", default: false)
+ static let lockScreenMusicFullscreenVideoArtwork = Key("lockScreenMusicFullscreenVideoArtwork", default: true)
+ static let lockScreenUseArtworkLayoutOverFullscreenCanvas = Key("lockScreenShowCenteredAlbumArtOverFullscreenCanvas", default: true)
static let lockScreenTimerWidgetUsesBlur = Key("lockScreenTimerWidgetUsesBlur", default: false)
static let lockScreenReminderChipStyle = Key("lockScreenReminderChipStyle", default: .eventColor)
static let lockScreenReminderWidgetHorizontalAlignment = Key("lockScreenReminderWidgetHorizontalAlignment", default: "center")
@@ -937,7 +957,17 @@ extension Defaults.Keys {
static let showBatteryPercentage = Key("showBatteryPercentage", default: true)
static let showPowerStatusIcons = Key("showPowerStatusIcons", default: true)
static let playLowBatteryAlertSound = Key("playLowBatteryAlertSound", default: true)
-
+ static let showChargingBatteryHUD = Key("showChargingBatteryHUD", default: true)
+ static let showLowBatteryHUD = Key("showLowBatteryHUD", default: true)
+ static let showFullBatteryHUD = Key("showFullBatteryHUD", default: true)
+ static let chargingBatteryHUDDuration = Key("chargingBatteryHUDDuration", default: 3)
+ static let lowBatteryHUDDuration = Key("lowBatteryHUDDuration", default: 3)
+ static let fullBatteryHUDDuration = Key("fullBatteryHUDDuration", default: 3)
+ static let lowBatteryHUDThreshold = Key("lowBatteryHUDThreshold", default: 20)
+ static let fullBatteryHUDThreshold = Key("fullBatteryHUDThreshold", default: 100)
+ static let lowBatteryHUDStyle = Key("lowBatteryHUDStyle", default: .standard)
+ static let fullBatteryHUDStyle = Key("fullBatteryHUDStyle", default: .standard)
+
static let lockScreenBatteryShowsBatteryGauge = Key(
"lockScreenWeatherShowsBatteryGauge",
default: BatteryActivityManager.shared.hasBattery()
@@ -988,6 +1018,10 @@ extension Defaults.Keys {
// MARK: Media Controller
static let mediaController = Key("mediaController", default: defaultMediaController)
+ static let spotifySPDCCookie = Key("spotifySPDCCookie", default: "")
+ static let spotifyAuthAccessToken = Key("spotifyAuthAccessToken", default: "")
+ static let spotifyAuthAccessTokenExpiration = Key("spotifyAuthAccessTokenExpiration", default: 0)
+ static let spotifyAuthLastValidatedAt = Key("spotifyAuthLastValidatedAt", default: 0)
// MARK: Bluetooth Audio Devices
static let showBluetoothDeviceConnections = Key("showBluetoothDeviceConnections", default: true)
@@ -1099,6 +1133,12 @@ extension Defaults.Keys {
static let enableKeyboardBacklightHUD = Key("enableKeyboardBacklightHUD", default: true)
static let systemHUDSensitivity = Key("systemHUDSensitivity", default: 5)
static let playVolumeChangeFeedback = Key("playVolumeChangeFeedback", default: false)
+
+ // Step sizes for hardware media keys (percent of full range, 1-25)
+ static let volumeStepPercent = Key("volumeStepPercent", default: 6)
+ static let volumeFineStepPercent = Key("volumeFineStepPercent", default: 2)
+ static let brightnessStepPercent = Key("brightnessStepPercent", default: 6)
+ static let brightnessFineStepPercent = Key("brightnessFineStepPercent", default: 2)
// MARK: Custom OSD Window Feature
static let enableCustomOSD = Key("enableCustomOSD", default: false)
@@ -1190,6 +1230,7 @@ extension Defaults.Keys {
// MARK: Lyrics Feature
static let enableLyrics = Key("enableLyrics", default: false)
+ static let showLiveCanvasInDynamicIsland = Key("showLiveCanvasInDynamicIsland", default: false)
// MARK: Notes Feature
static let enableNotes = Key("enableNotes", default: false)
diff --git a/DynamicIsland/models/PlaybackState.swift b/DynamicIsland/models/PlaybackState.swift
index 516e6c2..312a708 100644
--- a/DynamicIsland/models/PlaybackState.swift
+++ b/DynamicIsland/models/PlaybackState.swift
@@ -44,6 +44,7 @@ struct PlaybackState {
var repeatMode: RepeatMode = .off
var lastUpdated: Date = Date.distantPast
var artwork: Data?
+ var liveArtworkURL: URL?
}
extension PlaybackState: Equatable {
@@ -61,5 +62,6 @@ extension PlaybackState: Equatable {
&& lhs.isShuffled == rhs.isShuffled
&& lhs.repeatMode == rhs.repeatMode
&& lhs.artwork == rhs.artwork
+ && lhs.liveArtworkURL == rhs.liveArtworkURL
}
}
diff --git a/DynamicIsland/tr.lproj/InfoPlist.strings b/DynamicIsland/tr.lproj/InfoPlist.strings
new file mode 100644
index 0000000..000e924
--- /dev/null
+++ b/DynamicIsland/tr.lproj/InfoPlist.strings
@@ -0,0 +1,14 @@
+"CFBundleDisplayName" = "Atoll";
+"NSAppleEventsUsageDescription" = "Atoll, Spotify ve Apple Music'i kontrol etmek için AppleScript kullanır.";
+"NSBluetoothAlwaysUsageDescription" = "Atoll, ses aygıtları bağlandığında bunları algılamak ve HUD'da pil durumlarını göstermek için Bluetooth erişimine ihtiyaç duyar.";
+"NSCalendarsFullAccessUsageDescription" = "Takvim etkinliklerini çentik içinde gösterir.";
+"NSCalendarsUsageDescription" = "Takvim modu için gereklidir.";
+"NSCameraUsageDescription" = "Ayna modu için kameranıza ihtiyaç duyuyoruz.";
+"NSDesktopFolderUsageDescription" = "Atoll, ekran görüntülerini masaüstünüze kaydetmek için erişime ihtiyaç duyar.";
+"NSDocumentsFolderUsageDescription" = "Atoll, rafa sürüklenen dosyaları kaydetmek ve okumak için erişime ihtiyaç duyar.";
+"NSDownloadsFolderUsageDescription" = "Atoll, İndirilenler klasöründeki dosyaları okumak için erişime ihtiyaç duyar.";
+"NSLocationUsageDescription" = "Atoll, doğru kilit ekranı hava durumunu göstermek için konumunuzu kullanır.";
+"NSLocationWhenInUseUsageDescription" = "Atoll, doğru kilit ekranı hava durumunu göstermek için konumunuzu kullanır.";
+"NSRemindersUsageDescription" = "Atoll, anımsatıcılarınızı göstermek için Anımsatıcılar erişimine ihtiyaç duyar.";
+"NSAudioCaptureUsageDescription" = "Atoll, gerçek zamanlı müzik dalga biçimi görselleştiricisini göstermek için ses yakalama iznine ihtiyaç duyar.";
+"NSScreenCaptureUsageDescription" = "Atoll, yapay zeka asistanı özelliği için ekran görüntüleri yakalamak üzere ekran kaydı iznine ihtiyaç duyar.";
diff --git a/Localizable.xcstrings b/Localizable.xcstrings
index cdc5a64..dd41f8c 100644
--- a/Localizable.xcstrings
+++ b/Localizable.xcstrings
@@ -11994,8 +11994,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "Amazon Music"
}
}
}
@@ -36021,6 +36021,12 @@
"value": "Включить медиа ",
"state": "translated"
}
+ },
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "启用媒体 "
+ }
}
}
},
@@ -60469,8 +60475,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "仅应用图标"
}
}
}
@@ -60599,8 +60605,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "仅下载图标"
}
}
}
@@ -60729,8 +60735,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "透明度:"
}
}
}
@@ -60865,8 +60871,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "打开 Google AI Studio"
}
}
}
@@ -60996,8 +61002,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "在日历中打开"
}
}
}
@@ -61132,8 +61138,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "打开模型设置"
}
}
}
@@ -61152,6 +61158,12 @@
"value": "Открывать вырез при наведении",
"state": "translated"
}
+ },
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "悬停时打开刘海"
+ }
}
}
},
@@ -61280,8 +61292,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "打开设置"
}
}
}
@@ -61558,8 +61570,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "打开 AI 助手面板,进行文件分析与对话。默认为 Cmd+Shift+A。仅在启用屏幕助手功能时有效。"
}
}
}
@@ -61700,8 +61712,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "打开剪贴板历史记录面板。默认为 Cmd+Shift+V(类似于 PC 上的 Windows+V)。仅在启用剪贴板功能时有效。"
}
}
}
@@ -61842,8 +61854,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "打开取色器面板进行屏幕颜色捕获。默认为 Cmd+Shift+P。仅在启用取色器功能时有效。"
}
}
}
@@ -61973,8 +61985,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "输出设备"
}
}
}
@@ -62104,8 +62116,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "输出音量"
}
}
}
@@ -62246,8 +62258,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "面板模式会在刘海附近的浮动窗口中显示剪贴板。弹出模式会将剪贴板显示为附加在剪贴板按钮上的下拉菜单。"
}
}
}
@@ -62388,8 +62400,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "面板模式会在浮动窗口中显示取色器。弹出模式会将取色器显示为附加在取色器按钮上的下拉菜单。"
}
}
}
@@ -62530,8 +62542,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "面板模式会在刘海附近的浮动窗口中显示助手。弹出模式会将助手显示为附加在 AI 按钮上的下拉菜单。"
}
}
}
@@ -62790,8 +62802,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "已暂停"
}
}
}
@@ -63310,8 +63322,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "已选取 %@"
}
}
}
@@ -63440,8 +63452,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "拾取的颜色"
}
}
}
@@ -63570,8 +63582,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "拾取状态"
}
}
}
@@ -63700,8 +63712,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "从历史记录中固定项目以保存在此处"
}
}
}
@@ -63830,8 +63842,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "固定项目以添加收藏"
}
}
}
@@ -63960,8 +63972,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "固定项目以将它们添加到收藏夹"
}
}
}
@@ -64090,8 +64102,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "已固定项目"
}
}
}
@@ -64226,8 +64238,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "请在模型设置中为所选的 AI 提供商配置您的 API 密钥。"
}
}
}
@@ -64521,8 +64533,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "弹出模式会将剪贴板显示为附加在剪贴板按钮上的下拉菜单。面板模式会在刘海附近的浮动窗口中显示剪贴板。"
}
}
}
@@ -64663,8 +64675,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "弹出模式会将取色器显示为附加在取色器按钮上的下拉菜单。面板模式会在浮动窗口中显示取色器。"
}
}
}
@@ -64805,8 +64817,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "弹出模式会将助手显示为附加在 AI 按钮上的下拉菜单。面板模式会在刘海附近的浮动窗口中显示助手。"
}
}
}
@@ -65340,8 +65352,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "预设将显示在计时器弹出窗口中,包括配置的名称、持续时间和重音颜色。重新排序以更改显示顺序。"
}
}
}
@@ -65471,8 +65483,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "气压"
}
}
}
@@ -65731,8 +65743,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "预览缩放:"
}
}
}
@@ -89120,8 +89132,8 @@
},
"zh-Hans": {
"stringUnit": {
- "state": "new",
- "value": ""
+ "state": "translated",
+ "value": "当 Amazon Music 应用是活动媒体源时使用 macOS 正在播放。播放控制遵循系统正在播放目标。如果 Amazon Music 应用不支持远程拖拽,在时间轴上拖动可能无效。"
}
}
}
@@ -92636,6 +92648,266 @@
}
}
}
+ },
+ "15 mins": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "15 分钟"
+ }
+ }
+ }
+ },
+ "30 mins": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "30 分钟"
+ }
+ }
+ }
+ },
+ "1 hour": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "1 小时"
+ }
+ }
+ }
+ },
+ "3 hours": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "3 小时"
+ }
+ }
+ }
+ },
+ "6 hours": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "6 小时"
+ }
+ }
+ }
+ },
+ "12 hours": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "12 小时"
+ }
+ }
+ }
+ },
+ "Rest of the day": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "今天余下的时间"
+ }
+ }
+ }
+ },
+ "All time": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "所有时间"
+ }
+ }
+ }
+ },
+ "Symbol": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "符号"
+ }
+ }
+ }
+ },
+ "3D": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "3D"
+ }
+ }
+ }
+ },
+ "Left": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "左对齐"
+ }
+ }
+ }
+ },
+ "Right": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "右对齐"
+ }
+ }
+ }
+ },
+ "Enable Minimalistic UI": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "启用极简 UI"
+ }
+ }
+ }
+ },
+ "Hide Dynamic Island during screenshots & recordings": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "截屏和录屏时隐藏灵动岛"
+ }
+ }
+ }
+ },
+ "Close gesture": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "关闭手势"
+ }
+ }
+ }
+ },
+ "Reverse swipe gestures": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "反向滑动"
+ }
+ }
+ }
+ },
+ "Reverse scroll gestures": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "反向滚动"
+ }
+ }
+ }
+ },
+ "Extend hover area": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "扩展悬停区域"
+ }
+ }
+ }
+ },
+ "External display style": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "外接显示器样式"
+ }
+ }
+ }
+ },
+ "Hide until hovered": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "悬停时显示"
+ }
+ }
+ }
+ },
+ "Enable Focus Detection": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "启用专注模式检测"
+ }
+ }
+ }
+ },
+ "Accessibility permission lets Dynamic Island replace the native volume, brightness, and keyboard HUDs.": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "辅助功能权限允许灵动岛替换原生的音量、亮度和键盘提示框。"
+ }
+ }
+ }
+ },
+ "Full Disk Access for global mode": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "全局模式需要完全磁盘访问权限"
+ }
+ }
+ }
+ },
+ "Without Full Disk Access, Shelf can only read files from Documents and Downloads. Grant Full Disk Access to make Shelf work globally.": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "如果没有完全磁盘访问权限,暂存器只能读取文档和下载目录的文件。授予完全磁盘访问权限以使暂存器能在全局范围工作。"
+ }
+ }
+ }
+ },
+ "Request Full Disk Access": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "请求完全磁盘访问权限"
+ }
+ }
+ }
+ },
+ "Open Privacy & Security": {
+ "localizations": {
+ "zh-Hans": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "打开隐私与安全性"
+ }
+ }
+ }
}
},
"version": "1.1"