From e1672ed9a1b968e1df8e7fa0add796e427a5e668 Mon Sep 17 00:00:00 2001 From: lihaoyun6 Date: Sun, 14 Apr 2024 15:43:51 +0800 Subject: [PATCH] added zh_Hans localization; added app-icon for windows list; improved recording indicator on menu bar; deprecate the AppKit entry in main.swift and replace it with native SwiftUI; create preferences window using SwiftUI instead of AppKit --- Azayaka/AppDelegate.swift | 35 ++++++++--- Azayaka/Menu.swift | 71 +++++++++++++++-------- Azayaka/MenuBar.swift | 37 ++++++++++++ Azayaka/Preferences.swift | 53 ++++++++++------- Azayaka/Processing.swift | 10 ++-- Azayaka/Recording.swift | 15 ++--- Azayaka/en.lproj/Localizable.strings | 7 +++ Azayaka/main.swift | 13 ----- Azayaka/zh-Hans.lproj/Localizable.strings | 69 ++++++++++++++++++++++ 9 files changed, 233 insertions(+), 77 deletions(-) create mode 100644 Azayaka/MenuBar.swift create mode 100644 Azayaka/en.lproj/Localizable.strings delete mode 100644 Azayaka/main.swift create mode 100644 Azayaka/zh-Hans.lproj/Localizable.strings diff --git a/Azayaka/AppDelegate.swift b/Azayaka/AppDelegate.swift index 4f39f54..ca2ea19 100644 --- a/Azayaka/AppDelegate.swift +++ b/Azayaka/AppDelegate.swift @@ -9,6 +9,18 @@ import AVFoundation import AVFAudio import Cocoa import ScreenCaptureKit +import SwiftUI + +@main +struct AzayakaApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + Settings { + Preferences() + } + } +} class AppDelegate: NSObject, NSApplicationDelegate, SCStreamDelegate, SCStreamOutput { var vW: AVAssetWriter! @@ -32,8 +44,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCStreamDelegate, SCStreamOu var statusItem: NSStatusItem! var menu = NSMenu() - let info = NSMenuItem(title: "One moment, waiting on update", action: nil, keyEquivalent: "") - let noneAvailable = NSMenuItem(title: "None available", action: nil, keyEquivalent: "") + let info = NSMenuItem(title: "One moment, waiting on update".local, action: nil, keyEquivalent: "") + let noneAvailable = NSMenuItem(title: "None available".local, action: nil, keyEquivalent: "") let preferences = NSWindow() let ud = UserDefaults.standard @@ -70,12 +82,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCStreamDelegate, SCStreamOu if let error = error { switch error { case SCStreamError.userDeclined: self.requestPermissions() - default: print("[err] failed to fetch available content:", error.localizedDescription) + default: print("[err] failed to fetch available content:".local, error.localizedDescription) } return } self.availableContent = content - assert(self.availableContent?.displays.isEmpty != nil, "There needs to be at least one display connected") + assert(self.availableContent?.displays.isEmpty != nil, "There needs to be at least one display connected".local) let frontOnly = UserDefaults.standard.bool(forKey: Preferences.frontAppKey) DispatchQueue.main.async { if buildMenu { @@ -90,10 +102,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCStreamDelegate, SCStreamOu func requestPermissions() { DispatchQueue.main.async { let alert = NSAlert() - alert.messageText = "Azayaka needs permissions!" - alert.informativeText = "Azayaka needs screen recording permissions, even if you only intend on recording audio." - alert.addButton(withTitle: "Open Settings") - alert.addButton(withTitle: "No thanks, quit") + alert.messageText = "Azayaka needs permissions!".local + alert.informativeText = "Azayaka needs screen recording permissions, even if you only intend on recording audio.".local + alert.addButton(withTitle: "Open Settings".local) + alert.addButton(withTitle: "No thanks, quit".local) alert.alertStyle = .informational if alert.runModal() == .alertFirstButtonReturn { NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture")!) @@ -101,7 +113,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCStreamDelegate, SCStreamOu NSApp.terminate(self) } } - + func applicationWillTerminate(_ aNotification: Notification) { if stream != nil { stopRecording() @@ -112,3 +124,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, SCStreamDelegate, SCStreamOu return true } } + +extension String { + var local: String { return NSLocalizedString(self, comment: "") } +} + diff --git a/Azayaka/Menu.swift b/Azayaka/Menu.swift index 010e0bd..d6cff98 100644 --- a/Azayaka/Menu.swift +++ b/Azayaka/Menu.swift @@ -4,7 +4,7 @@ // // Created by Martin Persson on 2022-12-26. // - +import SwiftUI import ScreenCaptureKit extension AppDelegate: NSMenuDelegate { @@ -15,50 +15,52 @@ extension AppDelegate: NSMenuDelegate { if streamType != nil { // recording? var typeText = "" if screen != nil { - typeText = "Display " + String((availableContent?.displays.firstIndex(where: { $0.displayID == screen?.displayID }))!+1) + typeText = "Display ".local + String((availableContent?.displays.firstIndex(where: { $0.displayID == screen?.displayID }))!+1) } else if window != nil { - typeText = window?.owningApplication?.applicationName.uppercased() ?? "A window" + typeText = window?.owningApplication?.applicationName.uppercased() ?? "A window".local } else { - typeText = "System Audio" + typeText = "System Audio".local } - menu.addItem(header("Recording " + typeText, size: 12)) + menu.addItem(header("Recording ".local + typeText, size: 12)) - menu.addItem(NSMenuItem(title: "Stop Recording", action: #selector(stopRecording), keyEquivalent: "")) + menu.addItem(NSMenuItem(title: "Stop Recording".local, action: #selector(stopRecording), keyEquivalent: "")) + menu.addItem(NSMenuItem.separator()) menu.addItem(info) } else { - menu.addItem(header("Audio-only")) + menu.addItem(header("Audio-only".local)) - let audio = NSMenuItem(title: "System Audio", action: #selector(prepRecord), keyEquivalent: "") + let audio = NSMenuItem(title: "System Audio".local, action: #selector(prepRecord), keyEquivalent: "") audio.identifier = NSUserInterfaceItemIdentifier(rawValue: "audio") menu.addItem(audio) - - menu.addItem(header("Displays")) + menu.addItem(NSMenuItem.separator()) + menu.addItem(header("Displays".local)) for (i, display) in availableContent!.displays.enumerated() { - let displayItem = NSMenuItem(title: "Unknown Display", action: #selector(prepRecord), keyEquivalent: "") - let displayName = "Display \(i+1)" + (display.displayID == CGMainDisplayID() ? " (Main)" : "") + let displayItem = NSMenuItem(title: "Unknown Display".local, action: #selector(prepRecord), keyEquivalent: "") + let displayName = "Display ".local + "\(i+1)" + (display.displayID == CGMainDisplayID() ? " (Main)".local : "") displayItem.attributedTitle = NSAttributedString(string: displayName) displayItem.setAccessibilityLabel(displayName) displayItem.title = display.displayID.description displayItem.identifier = NSUserInterfaceItemIdentifier(rawValue: "display") menu.addItem(displayItem) } - - menu.addItem(header("Windows")) + menu.addItem(NSMenuItem.separator()) + menu.addItem(header("Windows".local)) noneAvailable.isHidden = true menu.addItem(noneAvailable) } menu.addItem(NSMenuItem.separator()) - menu.addItem(NSMenuItem(title: "Preferences…", action: #selector(openPreferences), keyEquivalent: ",")) - menu.addItem(NSMenuItem(title: "Quit Azayaka", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) + menu.addItem(NSMenuItem(title: "Preferences…".local, action: #selector(openPreferences), keyEquivalent: ",")) + menu.addItem(NSMenuItem(title: "Quit Azayaka".local, action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) statusItem.menu = menu } func updateMenu() { if streamType != nil { // recording? - info.attributedTitle = NSAttributedString(string: "Duration: \(getRecordingLength())\nFile size: \(getRecordingSize())") + updateIcon() + info.attributedTitle = NSAttributedString(string: String(format: "Duration: %@\nFile size: %@".local, arguments: [getRecordingLength(), getRecordingSize()])) } else { for window in menu.items.filter({ $0.identifier?.rawValue == "window" }) { let matchingWindow = availableContent!.windows.first(where: { window.title == $0.windowID.description }) @@ -106,22 +108,42 @@ extension AppDelegate: NSMenuDelegate { newWindow(window: window) } } + + func getAppIcon(forBundleIdentifier bundleIdentifier: String) -> NSImage? { + if let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleIdentifier) { + let icon = NSWorkspace.shared.icon(forFile: appURL.path) + return icon + } + return nil + } func newWindow(window: SCWindow) { - let win = NSMenuItem(title: "Unknown", action: #selector(prepRecord), keyEquivalent: "") + let win = NSMenuItem(title: "Unknown".local, action: #selector(prepRecord), keyEquivalent: "") win.attributedTitle = getFancyWindowString(window: window) win.title = String(window.windowID) win.identifier = NSUserInterfaceItemIdentifier("window") - win.setAccessibilityLabel("App name: " + (window.owningApplication?.applicationName ?? "Unknown App") + ", window title: " + (window.title ?? "No title")) // VoiceOver will otherwise read the window ID (the item's non-attributed title) + win.setAccessibilityLabel("App name: ".local + (window.owningApplication?.applicationName ?? "Unknown App".local) + ", window title: ".local + (window.title ?? "No title".local)) // VoiceOver will otherwise read the window ID (the item's non-attributed title) menu.insertItem(win, at: menu.numberOfItems - 3) + } func getFancyWindowString(window: SCWindow) -> NSAttributedString { - let str = NSMutableAttributedString(string: (window.owningApplication?.applicationName ?? "Unknown App") + "\n") - str.append(NSAttributedString(string: window.title ?? "No title", + let appID = window.owningApplication?.bundleIdentifier ?? "Unknown App".local + let imageAttachment = NSTextAttachment() + imageAttachment.image = getAppIcon(forBundleIdentifier: appID) + imageAttachment.bounds = CGRectMake(0, -3, 16, 16) + let imageString = NSAttributedString(attachment: imageAttachment) + + let str = NSMutableAttributedString(string: " " + (window.owningApplication?.applicationName ?? "Unknown App".local) + "\n") + str.append(NSAttributedString(string: window.title ?? "No title".local, attributes: [.font: NSFont.systemFont(ofSize: 12, weight: .regular), .foregroundColor: NSColor.secondaryLabelColor])) - return str + + let output = NSMutableAttributedString(string: "") + output.append(imageString) + output.append(str) + + return output } func header(_ title: String, size: CGFloat = 10) -> NSMenuItem { @@ -144,7 +166,10 @@ extension AppDelegate: NSMenuDelegate { func updateIcon() { if let button = statusItem.button { - button.image = NSImage(systemSymbolName: self.streamType != nil ? "record.circle.fill" : "record.circle", accessibilityDescription: "Azayaka") + let iconView = NSHostingView(rootView: MenuBar(recordingStatus: self.streamType != nil, recordingLength: getRecordingLength())) + iconView.frame = NSRect(x: 0, y: 1, width: self.streamType != nil ? 72 : 32, height: 20) + button.subviews = [iconView] + button.frame = iconView.frame } } } diff --git a/Azayaka/MenuBar.swift b/Azayaka/MenuBar.swift new file mode 100644 index 0000000..5851116 --- /dev/null +++ b/Azayaka/MenuBar.swift @@ -0,0 +1,37 @@ +// +// MenuBar.swift +// Azayaka +// +// Created by apple on 2024/4/14. +// + +import SwiftUI +import Foundation + +struct MenuBar: View { + @State var recordingStatus: Bool! + @State var recordingLength = "00:00" + var body: some View { + ZStack { + if recordingStatus { + Rectangle() + .cornerRadius(3) + .opacity(0.1) + } + HStack(spacing: 2.5) { + if recordingStatus { + Image(systemName: "record.circle") + .foregroundStyle(.red) + Text(recordingLength) + .offset(y: -0.5) + } else { + Image(systemName: "record.circle") + } + } + } + } +} + +#Preview { + MenuBar() +} diff --git a/Azayaka/Preferences.swift b/Azayaka/Preferences.swift index f21fb93..20dcc4c 100644 --- a/Azayaka/Preferences.swift +++ b/Azayaka/Preferences.swift @@ -24,7 +24,7 @@ struct Preferences: View { var body: some View { VStack(alignment: .leading) { - GroupBox(label: Text("Video Output".uppercased()).fontWeight(.bold)) { + GroupBox(label: Text("Video Output".local.uppercased()).fontWeight(.bold)) { Form() { Picker("FPS", selection: $frameRate) { Text("60").tag(60) @@ -52,7 +52,7 @@ struct Preferences: View { Text("Show mouse cursor") }.toggleStyle(CheckboxToggleStyle()).padding(.bottom, 10) } - GroupBox(label: Text("Audio Output".uppercased()).fontWeight(.bold)) { + GroupBox(label: Text("Audio Output".local.uppercased()).fontWeight(.bold)) { Form() { Picker("Format", selection: $audioFormat) { Text("AAC").tag(AudioFormat.aac) @@ -94,7 +94,7 @@ struct Preferences: View { Spacer() VStack(spacing: 2) { Button("Select output directory", action: updateOutputDirectory) - Text("Currently set to \"\(URL(fileURLWithPath: saveDirectory!).lastPathComponent)\"").font(.footnote).foregroundColor(Color.gray) + Text(String(format: "Currently set to \"%@\"".local, URL(fileURLWithPath: saveDirectory!).lastPathComponent)).font(.footnote).foregroundColor(Color.gray) }.frame(maxWidth: .infinity) }.frame(width: 260).padding([.leading, .trailing, .top], 10) HStack { @@ -111,10 +111,10 @@ struct Preferences: View { recordMic = false DispatchQueue.main.async { let alert = NSAlert() - alert.messageText = "Azayaka needs permissions!" - alert.informativeText = "Azayaka needs permission to record your microphone to do this." - alert.addButton(withTitle: "Open Settings") - alert.addButton(withTitle: "No thanks") + alert.messageText = "Azayaka needs permissions!".local + alert.informativeText = "Azayaka needs permission to record your microphone to do this.".local + alert.addButton(withTitle: "Open Settings".local) + alert.addButton(withTitle: "No thanks".local) alert.alertStyle = .warning if alert.runModal() == .alertFirstButtonReturn { NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone")!) @@ -134,11 +134,11 @@ struct Preferences: View { } func getVersion() -> String { - return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "Unknown" + return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "Unknown".local } func getBuild() -> String { - return Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "Unknown" + return Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "Unknown".local } struct VisualEffectView: NSViewRepresentable { @@ -147,21 +147,34 @@ struct Preferences: View { } } -struct Preferences_Previews: PreviewProvider { - static var previews: some View { - Preferences() - } +#Preview { + Preferences() } extension AppDelegate { @objc func openPreferences() { - preferences.isReleasedWhenClosed = false // otherwise we crash when opening the window again, WTF? - preferences.title = "Azayaka" - //preferences.subtitle = "Preferences" - preferences.contentView = NSHostingView(rootView: Preferences()) // is this how you SwiftUI help I'm scared - preferences.styleMask = [.titled, .closable] - preferences.center() NSApp.activate(ignoringOtherApps: true) - preferences.makeKeyAndOrderFront(nil) + if #available(macOS 14, *) { + NSApp.mainMenu?.items.first?.submenu?.item(at: 2)?.performAction() + }else if #available(macOS 13, *) { + NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil) + } else { + NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil) + } + for w in NSApplication.shared.windows { + if w.level.rawValue == 0 || w.level.rawValue == 3 { + w.level = .floating + w.styleMask.remove(.resizable) + } + } + } +} + +extension NSMenuItem { + func performAction() { + guard let menu else { + return + } + menu.performActionForItem(at: menu.index(of: self)) } } diff --git a/Azayaka/Processing.swift b/Azayaka/Processing.swift index 3c75c2e..70eae64 100644 --- a/Azayaka/Processing.swift +++ b/Azayaka/Processing.swift @@ -18,7 +18,7 @@ extension AppDelegate { switch fileEnding { case VideoFormat.mov.rawValue: fileType = AVFileType.mov case VideoFormat.mp4.rawValue: fileType = AVFileType.mp4 - default: assertionFailure("loaded unknown video format") + default: assertionFailure("loaded unknown video format".local) } filePath = "\(getFilePath()).\(fileEnding)" @@ -112,20 +112,20 @@ extension AppDelegate { do { try audioFile?.write(from: samples) } - catch { assertionFailure("audio file writing issue") } + catch { assertionFailure("audio file writing issue".local) } } else { // otherwise send the audio data to AVAssetWriter if awInput.isReadyForMoreMediaData { awInput.append(sampleBuffer) } } @unknown default: - assertionFailure("unknown stream type") + assertionFailure("unknown stream type".local) } } func stream(_ stream: SCStream, didStopWithError error: Error) { // stream error - print("closing stream with error:\n", error, - "\nthis might be due to the window closing or the user stopping from the sonoma ui") + print("closing stream with error:\n".local, error, + "\nthis might be due to the window closing or the user stopping from the sonoma ui".local) DispatchQueue.main.async { self.stream = nil self.stopRecording() diff --git a/Azayaka/Recording.swift b/Azayaka/Recording.swift index c6df359..f0dd4cd 100644 --- a/Azayaka/Recording.swift +++ b/Azayaka/Recording.swift @@ -70,7 +70,7 @@ extension AppDelegate { } try await stream.startCapture() } catch { - assertionFailure("capture failed") + assertionFailure("capture failed".local) return } @@ -117,7 +117,7 @@ extension AppDelegate { audioSettings[AVFormatIDKey] = ud.string(forKey: "videoFormat") != VideoFormat.mp4.rawValue ? kAudioFormatOpus : kAudioFormatMPEG4AAC audioSettings[AVEncoderBitRateKey] = ud.integer(forKey: "audioQuality") * 1000 default: - assertionFailure("unknown audio format while setting audio settings: " + (ud.string(forKey: "audioFormat") ?? "[no defaults]")) + assertionFailure("unknown audio format while setting audio settings: ".local + (ud.string(forKey: "audioFormat") ?? "[no defaults]".local)) } } @@ -128,7 +128,7 @@ extension AppDelegate { case AudioFormat.alac.rawValue: fileEnding = "m4a" case AudioFormat.flac.rawValue: fileEnding = "flac" case AudioFormat.opus.rawValue: fileEnding = "ogg" - default: assertionFailure("loaded unknown audio format: " + fileEnding) + default: assertionFailure("loaded unknown audio format: ".local + fileEnding) } filePath = "\(getFilePath()).\(fileEnding)" audioFile = try! AVAudioFile(forWriting: URL(fileURLWithPath: filePath), settings: audioSettings, commonFormat: .pcmFormatFloat32, interleaved: false) @@ -137,7 +137,7 @@ extension AppDelegate { func getFilePath() -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "y-MM-dd HH.mm.ss" - return ud.string(forKey: "saveDirectory")! + "/Recording at " + dateFormatter.string(from: Date()) + return ud.string(forKey: "saveDirectory")! + "/Recording at ".local + dateFormatter.string(from: Date()) } func getRecordingLength() -> String { @@ -145,7 +145,8 @@ extension AppDelegate { formatter.allowedUnits = [.minute, .second] formatter.zeroFormattingBehavior = .pad formatter.unitsStyle = .positional - return formatter.string(from: Date.now.timeIntervalSince(startTime ?? Date.now)) ?? "Unknown" + if self.streamType == nil { self.startTime = nil } + return formatter.string(from: Date.now.timeIntervalSince(startTime ?? Date.now)) ?? "Unknown".local } func getRecordingSize() -> String { @@ -158,9 +159,9 @@ extension AppDelegate { return byteFormat.string(fromByteCount: fileAttr[FileAttributeKey.size] as! Int64) } } catch { - print("failed to fetch file for size indicator: \(error.localizedDescription)") + print(String(format: "failed to fetch file for size indicator: %@".local, error.localizedDescription)) } - return "Unknown" + return "Unknown".local } } diff --git a/Azayaka/en.lproj/Localizable.strings b/Azayaka/en.lproj/Localizable.strings new file mode 100644 index 0000000..fcb8385 --- /dev/null +++ b/Azayaka/en.lproj/Localizable.strings @@ -0,0 +1,7 @@ +/* + Localizable.strings + Azayaka + + Created by apple on 2024/4/14. + +*/ diff --git a/Azayaka/main.swift b/Azayaka/main.swift deleted file mode 100644 index 99e1880..0000000 --- a/Azayaka/main.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// main.swift -// Azayaka -// -// Created by Martin Persson on 2023-01-01. -// - -import AppKit - -let delegate = AppDelegate() -NSApplication.shared.delegate = delegate -NSApplication.shared.run() -_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) diff --git a/Azayaka/zh-Hans.lproj/Localizable.strings b/Azayaka/zh-Hans.lproj/Localizable.strings new file mode 100644 index 0000000..844a202 --- /dev/null +++ b/Azayaka/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,69 @@ +/* + Localizable.strings + Azayaka + + Created by apple on 2024/4/14. + +*/ +"One moment, waiting on update" = "正在更新, 请稍等"; +"None available" = "不可用"; +"Azayaka needs permissions!" = "请为 Azayaka 授权"; +"Azayaka needs screen recording permissions, even if you only intend on recording audio." = "Azayaka 需要使用录屏权限\n(即使只是用来录制音频)"; +"Open Settings" = "打开系统设置"; +"No thanks, quit" = "不了谢谢, 再见"; +"Display " = "显示器 "; +"System Audio" = "系统声音"; +"Recording " = "正在录制: "; +"A window" = "某个窗口"; +"Stop Recording" = "停止录制"; +"Audio-only" = "仅录音"; +"Displays" = "显示器"; +"Unknown Display" = "未知显示器"; +" (Main)" = " (主显示器)"; +"Windows" = "应用窗口"; +"Preferences…" = "设置…"; +"Quit Azayaka" = "退出Azayaka"; +"Duration: %@\nFile size: %@" = "录像时长: %@\n文件大小: %@"; +"Unknown" = "未知"; +"App name: " = "应用名称: "; +"Unknown App" = "未知应用"; +", window title: " = ", 窗口标题: "; +"No title" = "无标题"; +"Video Output" = "视频设置"; +"FPS" = "帧速率"; +"Format" = "输出格式"; +"Encoder" = "编码方式"; +"Exclude Azayaka itself" = "从画面中排除 Azayaka 本身"; +"Only list focused app's windows" = "只列出当前聚焦的应用的窗口"; +"Show mouse cursor" = "录制鼠标光标"; +"Audio Output" = "音频设置"; +"ALAC (Lossless)" = "ALAC (无损)"; +"FLAC (Lossless)" = "FLAC (无损)"; +"Quality" = "质量"; +"Normal - 128Kbps" = "普通 - 128Kbps"; +"Good - 192Kbps" = "优质 - 192Kbps"; +"High - 256Kbps" = "高清 - 256Kbps"; +"Extreme - 320Kbps" = "极致 - 320Kbps"; +"These settings are also used when recording video. If set to Opus, MP4 will fall back to AAC." = "录制视频时也会遵从这些设置, 但如果音频编码被设为 Opus ,则录制 MP4 时会回退至 AAC 编码."; +"Record microphone" = "录制麦克风声音"; +"Doesn't apply to system audio-only recordings. The currently set input device will be used, and will be written as a separate audio track." = "录制视频时, 来自系统默认音频输入设备的声音将被写入单独的音轨 (此功能不适用于\"仅录音\")"; +"Select output directory" = "选择保存位置"; +"Azayaka needs permission to record your microphone to do this." = "Azayaka 需要使用麦克风权限来录制麦克风声音."; +"Currently set to \"%@\"" = "当前保存位置为\"%@\""; +"/Recording at " = "/录制于 "; + +// Warning text +/* + "failed to fetch file for size indicator: %@" = "获取文件大小失败: "; +"[err] failed to fetch available content:" = "[错误] 无法获取可用的上下文:"; +"There needs to be at least one display connected" = "至少需要连接一台显示器"; +"capture failed" = "捕获失败"; +"unknown audio format while setting audio settings: " = "更改音频设置时设置了未知的音频格式"; +"[no defaults]" = "[无默认设置]"; +"loaded unknown audio format: " = "加载了未知的音频格式: "; +"loaded unknown video format" = "加载了未知的视频格式"; +"unknown stream type" = "未知的流类型"; +"audio file writing issue" = "音频文件写入出错"; +"closing stream with error:\n" = "关闭流时发生错误:\n"; +"\nthis might be due to the window closing or the user stopping from the sonoma ui" = "\n这可能是有由于用户关闭了正在录制中的窗口, 或使用窗口上的附加按钮停止了录制."; +*/