Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Temporary Items
*.swp
*.lock
*.log
dist/

# Snippets
snippets/
40 changes: 40 additions & 0 deletions BetterCapture/Model/AppLanguage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// AppLanguage.swift
// BetterCapture
//
// Created by Codex on 30.05.26.
//

import Foundation

enum AppLanguage: String, CaseIterable, Identifiable {
case english
case simplifiedChinese

var id: String { rawValue }

static var current: AppLanguage {
let rawValue = UserDefaults.standard.string(forKey: "appLanguage") ?? AppLanguage.english.rawValue
return AppLanguage(rawValue: rawValue) ?? .english
}
Comment on lines +16 to +19

var displayName: String {
switch self {
case .english:
return "English"
case .simplifiedChinese:
return "简体中文"
}
}
}

enum AppText {
static func value(_ english: String, _ simplifiedChinese: String, language: AppLanguage) -> String {
switch language {
case .english:
return english
case .simplifiedChinese:
return simplifiedChinese
}
}
}
10 changes: 8 additions & 2 deletions BetterCapture/Model/ContentSelectionMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ enum ContentSelectionMode: String {
case selectArea

var label: String {
label(language: .english)
}

func label(language: AppLanguage) -> String {
switch self {
case .pickContent: "Pick Content"
case .selectArea: "Select Area"
case .pickContent:
AppText.value("Pick Content", "选择内容", language: language)
case .selectArea:
AppText.value("Select Area", "选择区域", language: language)
}
}

Expand Down
36 changes: 35 additions & 1 deletion BetterCapture/Model/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,13 @@ enum FrameRate: Int, CaseIterable, Identifiable {
var id: Int { rawValue }

var displayName: String {
displayName(language: .english)
}

func displayName(language: AppLanguage) -> String {
switch self {
case .native:
return "Native"
return AppText.value("Native", "原生", language: language)
default:
return "\(rawValue) fps"
}
Expand Down Expand Up @@ -187,6 +191,21 @@ enum VideoQuality: String, CaseIterable, Identifiable {

var id: String { rawValue }

var displayName: String {
displayName(language: .english)
}

func displayName(language: AppLanguage) -> String {
switch self {
case .low:
return AppText.value("Low", "低", language: language)
case .medium:
return AppText.value("Medium", "中", language: language)
case .high:
return AppText.value("High", "高", language: language)
}
}

/// Bits-per-pixel multiplier for H.264
var h264BitsPerPixel: Double {
switch self {
Expand Down Expand Up @@ -249,6 +268,21 @@ final class SettingsStore {
self.defaults = defaults
}

// MARK: - General Settings

var appLanguage: AppLanguage {
get {
access(keyPath: \.appLanguage)
let rawValue = defaults.string(forKey: "appLanguage") ?? AppLanguage.english.rawValue
return AppLanguage(rawValue: rawValue) ?? .english
}
set {
withMutation(keyPath: \.appLanguage) {
defaults.set(newValue.rawValue, forKey: "appLanguage")
}
}
}

// MARK: - Video Settings

var frameRate: FrameRate {
Expand Down
24 changes: 18 additions & 6 deletions BetterCapture/Service/AssetWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -576,17 +576,29 @@ enum AssetWriterError: LocalizedError {
var errorDescription: String? {
switch self {
case .failedToCreateWriter:
return "Failed to create the asset writer."
return AppText.value("Failed to create the asset writer.", "创建媒体写入器失败。", language: .current)
case .writerNotReady:
return "The asset writer is not ready for writing."
return AppText.value("The asset writer is not ready for writing.", "媒体写入器尚未准备好写入。", language: .current)
case .failedToStartWriting(let error):
return "Failed to start writing: \(error?.localizedDescription ?? "Unknown error")"
return AppText.value(
"Failed to start writing: \(error?.localizedDescription ?? "Unknown error")",
"开始写入失败:\(error?.localizedDescription ?? "未知错误")",
language: .current
)
case .writingFailed(let error):
return "Writing failed: \(error?.localizedDescription ?? "Unknown error")"
return AppText.value(
"Writing failed: \(error?.localizedDescription ?? "Unknown error")",
"写入失败:\(error?.localizedDescription ?? "未知错误")",
language: .current
)
case .noOutputURL:
return "No output URL was configured."
return AppText.value("No output URL was configured.", "未配置输出位置。", language: .current)
case .noFramesWritten:
return "No video frames were captured. Check screen recording permissions."
return AppText.value(
"No video frames were captured. Check screen recording permissions.",
"没有捕捉到视频帧。请检查屏幕录制权限。",
language: .current
)
}
}
}
22 changes: 17 additions & 5 deletions BetterCapture/Service/CaptureEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -376,15 +376,27 @@ enum CaptureError: LocalizedError {
var errorDescription: String? {
switch self {
case .noContentFilterSelected:
return "No content has been selected for capture. Please use the picker to select a window or display."
return AppText.value(
"No content has been selected for capture. Please use the picker to select a window or display.",
"尚未选择录制内容。请使用选择器选择窗口或显示器。",
language: .current
)
case .failedToCreateStream:
return "Failed to create the capture stream."
return AppText.value("Failed to create the capture stream.", "创建录制流失败。", language: .current)
case .captureAlreadyRunning:
return "A capture session is already in progress."
return AppText.value("A capture session is already in progress.", "录制会话已在进行中。", language: .current)
case .screenRecordingPermissionDenied:
return "Screen recording permission is required. Please grant permission in System Settings → Privacy & Security → Screen Recording."
return AppText.value(
"Screen recording permission is required. Please grant permission in System Settings > Privacy & Security > Screen Recording.",
"需要屏幕录制权限。请在“系统设置”>“隐私与安全性”>“屏幕录制”中授予权限。",
language: .current
)
case .microphonePermissionDenied:
return "Microphone permission is required. Please grant permission in System Settings → Privacy & Security → Microphone."
return AppText.value(
"Microphone permission is required. Please grant permission in System Settings > Privacy & Security > Microphone.",
"需要麦克风权限。请在“系统设置”>“隐私与安全性”>“麦克风”中授予权限。",
language: .current
)
}
}
}
34 changes: 26 additions & 8 deletions BetterCapture/Service/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ final class NotificationService: NSObject {
// Action to show recording in Finder
let showInFinderAction = UNNotificationAction(
identifier: NotificationIdentifier.actionShowInFinder,
title: "Show in Finder",
title: AppText.value("Show in Finder", "在访达中显示", language: .current),
options: [.foreground]
)

Expand Down Expand Up @@ -100,9 +100,15 @@ final class NotificationService: NSObject {
/// Sends a notification for a successfully saved recording
/// - Parameter fileURL: The URL of the saved recording file
func sendRecordingSavedNotification(fileURL: URL) {
registerNotificationCategories()
let language = settings.appLanguage
let content = UNMutableNotificationContent()
Comment on lines 100 to 105
content.title = "Recording Saved"
content.body = "Your recording has been saved to \(fileURL.lastPathComponent)"
content.title = AppText.value("Recording Saved", "录制已保存", language: language)
content.body = AppText.value(
"Your recording has been saved to \(fileURL.lastPathComponent)",
"录制文件已保存到 \(fileURL.lastPathComponent)",
language: language
)
content.sound = .default
content.categoryIdentifier = NotificationIdentifier.categoryRecordingSaved

Expand All @@ -129,9 +135,15 @@ final class NotificationService: NSObject {
/// Sends a notification for a failed recording
/// - Parameter error: The error that caused the recording to fail
func sendRecordingFailedNotification(error: Error) {
registerNotificationCategories()
let language = settings.appLanguage
let content = UNMutableNotificationContent()
content.title = "Recording Failed"
content.body = "Your recording could not be saved: \(error.localizedDescription)"
content.title = AppText.value("Recording Failed", "录制失败", language: language)
content.body = AppText.value(
"Your recording could not be saved: \(error.localizedDescription)",
"无法保存录制文件:\(error.localizedDescription)",
language: language
)
content.sound = .default
content.categoryIdentifier = NotificationIdentifier.categoryRecordingFailed

Expand All @@ -154,13 +166,19 @@ final class NotificationService: NSObject {
/// Sends a notification when recording stopped unexpectedly
/// - Parameter error: Optional error that caused the stop
func sendRecordingStoppedNotification(error: Error?) {
registerNotificationCategories()
let language = settings.appLanguage
let content = UNMutableNotificationContent()
content.title = "Recording Stopped"
content.title = AppText.value("Recording Stopped", "录制已停止", language: language)

if let error {
content.body = "Recording stopped unexpectedly: \(error.localizedDescription)"
content.body = AppText.value(
"Recording stopped unexpectedly: \(error.localizedDescription)",
"录制意外停止:\(error.localizedDescription)",
language: language
)
} else {
content.body = "Recording stopped unexpectedly"
content.body = AppText.value("Recording stopped unexpectedly", "录制意外停止", language: language)
}

content.sound = .default
Expand Down
4 changes: 2 additions & 2 deletions BetterCapture/View/AreaSelectionOverlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -484,13 +484,13 @@ final class AreaSelectionView: NSView {
let container = NSView()

let confirm = makeActionButton(
title: "Confirm",
title: AppText.value("Confirm", "确认", language: .current),
textColor: .systemGreen,
action: #selector(confirmButtonClicked)
)

let cancel = makeActionButton(
title: "Cancel",
title: AppText.value("Cancel", "取消", language: .current),
textColor: .systemRed,
action: #selector(cancelButtonClicked)
)
Expand Down
Loading