From 5e5838dcd25847f0baf5fb1fef2b1e56a55469d5 Mon Sep 17 00:00:00 2001 From: hrw <258592834+131122syy-cloud@users.noreply.github.com> Date: Mon, 1 Jun 2026 09:53:43 +0900 Subject: [PATCH] =?UTF-8?q?feat(localization):=20add=20Simplified=20Chines?= =?UTF-8?q?e=20support=20=C2=B7=20zh-Hans?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Reframed.xcodeproj/project.pbxproj | 9 + Reframed/App/WindowController.swift | 4 +- .../CaptureArea/CaptureAreaView.swift | 2 +- .../CaptureScreen/StartRecordingOverlay.swift | 2 +- .../CaptureWindow/ResizePopover.swift | 4 +- Reframed/Compositor/ExportSheet+Phases.swift | 2 +- Reframed/Compositor/ExportSheet.swift | 12 +- Reframed/Editor/EditorState+Export.swift | 4 +- Reframed/Editor/EditorView+Sidebar.swift | 2 +- Reframed/Editor/EditorView.swift | 4 +- Reframed/Editor/EditorWindow.swift | 2 +- Reframed/Editor/History+ChangeRules.swift | 271 ++++---- Reframed/Editor/HistoryPopover.swift | 2 +- .../Editor/PropertiesPanel+CaptionsTab.swift | 16 +- .../PropertiesPanel+CursorZoomTab.swift | 8 +- .../Editor/PropertiesPanel+EffectsTab.swift | 6 +- .../Editor/PropertiesPanel+GeneralTab.swift | 20 +- Reframed/Editor/RegionEditPopover.swift | 4 +- .../Editor/SpotlightRegionEditPopover.swift | 15 +- Reframed/Editor/TimelineView+AudioTrack.swift | 2 +- .../Editor/TimelineView+CameraTrack.swift | 2 +- Reframed/Editor/TimelineView+Shared.swift | 2 +- .../ZoomKeyframeEditor+RegionView.swift | 4 +- Reframed/Recording/WebcamPreviewWindow.swift | 2 +- Reframed/State/CaptureState.swift | 59 +- Reframed/State/SessionState+Camera.swift | 2 +- Reframed/State/SessionState+Project.swift | 2 +- Reframed/State/SessionState+UI.swift | 4 +- Reframed/UI/AreaSizePresetsPopover.swift | 2 +- Reframed/UI/CaptureToolbarComponents.swift | 6 +- Reframed/UI/CheckmarkRow.swift | 2 +- Reframed/UI/DevicePickerPopover.swift | 2 +- Reframed/UI/Dropdown.swift | 2 +- Reframed/UI/ImageDropSection.swift | 2 +- Reframed/UI/LanguagePicker.swift | 2 +- Reframed/UI/MenuBarComponents.swift | 2 +- Reframed/UI/MenuBarView.swift | 2 +- Reframed/UI/ModeButton.swift | 2 +- Reframed/UI/PermissionsView.swift | 6 +- Reframed/UI/SectionHeader.swift | 4 +- Reframed/UI/SegmentPicker.swift | 2 +- Reframed/UI/SelectButton.swift | 2 +- Reframed/UI/SettingsAboutTab.swift | 6 +- Reframed/UI/SettingsRecordingTab.swift | 2 +- Reframed/UI/SettingsView.swift | 10 +- Reframed/UI/ShortcutRow.swift | 2 +- Reframed/UI/SliderRow.swift | 4 +- Reframed/UI/StartRecordingButton.swift | 2 +- Reframed/UI/TailwindColorPicker.swift | 2 +- Reframed/UI/ToggleRow.swift | 2 +- Reframed/UI/TransitionControlsSection.swift | 4 +- Reframed/Utilities/TimeFormatting.swift | 12 +- Reframed/zh-Hans.lproj/InfoPlist.strings | 7 + Reframed/zh-Hans.lproj/Localizable.strings | 603 ++++++++++++++++++ 54 files changed, 939 insertions(+), 223 deletions(-) create mode 100644 Reframed/zh-Hans.lproj/InfoPlist.strings create mode 100644 Reframed/zh-Hans.lproj/Localizable.strings diff --git a/Reframed.xcodeproj/project.pbxproj b/Reframed.xcodeproj/project.pbxproj index 9940220..4b01997 100644 --- a/Reframed.xcodeproj/project.pbxproj +++ b/Reframed.xcodeproj/project.pbxproj @@ -218,6 +218,8 @@ E1000000000000000000000C /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E1000000000000000000000B /* Sparkle */; }; E1000000000000000000000D /* SparkleUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1000000000000000000000E /* SparkleUpdater.swift */; }; E30000000000000000000001 /* VideoCompositor+ManualExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20000000000000000000001 /* VideoCompositor+ManualExport.swift */; }; + ZH0000000000000000000001 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = ZH0000000000000000000002 /* Localizable.strings */; }; + ZH0000000000000000000003 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = ZH0000000000000000000004 /* InfoPlist.strings */; }; E30000000000000000000002 /* VideoCompositor+ParallelExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20000000000000000000002 /* VideoCompositor+ParallelExport.swift */; }; E30000000000000000000003 /* VideoCompositor+GIFExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20000000000000000000003 /* VideoCompositor+GIFExport.swift */; }; E30000000000000000000004 /* VideoCompositor+Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20000000000000000000004 /* VideoCompositor+Audio.swift */; }; @@ -362,6 +364,8 @@ B1000000000000000000004D /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; B1000000000000000000004E /* SelectButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectButton.swift; sourceTree = ""; }; B1000000000000000000004F /* LanguagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguagePicker.swift; sourceTree = ""; }; + ZH0000000000000000000002 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = "Reframed/zh-Hans.lproj/Localizable.strings"; sourceTree = SOURCE_ROOT; }; + ZH0000000000000000000004 /* InfoPlist.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = "Reframed/zh-Hans.lproj/InfoPlist.strings"; sourceTree = SOURCE_ROOT; }; B17A303B9C60827322EAE0A9 /* VideoPreviewContainer+Cursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VideoPreviewContainer+Cursor.swift"; sourceTree = ""; }; B1F00000000000000000001 /* WindowPositionObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowPositionObserver.swift; sourceTree = ""; }; B20000000000000000000001 /* WebcamCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebcamCapture.swift; sourceTree = ""; }; @@ -533,6 +537,8 @@ children = ( B10000000000000000000001 /* ReframedApp.swift */, B10000000000000000000019 /* Info.plist */, + ZH0000000000000000000004 /* InfoPlist.strings */, + ZH0000000000000000000002 /* Localizable.strings */, B1000000000000000000001A /* Reframed.entitlements */, D10000000000000000000003 /* App */, D10000000000000000000004 /* Logging */, @@ -951,6 +957,7 @@ knownRegions = ( en, Base, + "zh-Hans", ); mainGroup = D10000000000000000000001; packageReferences = ( @@ -976,6 +983,8 @@ files = ( 3DCA03E12F3B511C003FE309 /* Assets.xcassets in Resources */, AA0000000000000000000001 /* Credits.html in Resources */, + ZH0000000000000000000001 /* Localizable.strings in Resources */, + ZH0000000000000000000003 /* InfoPlist.strings in Resources */, ); }; /* End PBXResourcesBuildPhase section */ diff --git a/Reframed/App/WindowController.swift b/Reframed/App/WindowController.swift index 5a41362..3533b8b 100644 --- a/Reframed/App/WindowController.swift +++ b/Reframed/App/WindowController.swift @@ -58,7 +58,7 @@ final class WindowController: ObservableObject { let windowNumber = windowDict[kCGWindowNumber as String] as? Int ?? 0 let title = windowDict[kCGWindowName as String] as? String ?? "" let app = NSRunningApplication(processIdentifier: pid) - let appName = app?.localizedName ?? "Unknown" + let appName = app?.localizedName ?? "未知" let axApp = AXUIElementCreateApplication(pid) let axWindow = findAXWindow(for: axApp, matching: bounds) ?? axApp @@ -137,7 +137,7 @@ final class WindowController: ObservableObject { let windowNumber = windowDict[kCGWindowNumber as String] as? Int ?? 0 let title = windowDict[kCGWindowName as String] as? String ?? "" let app = NSRunningApplication(processIdentifier: pid) - let appName = app?.localizedName ?? "Unknown" + let appName = app?.localizedName ?? "未知" let axApp = AXUIElementCreateApplication(pid) let axWindow = findAXWindow(for: axApp, matching: bounds) ?? axApp diff --git a/Reframed/CaptureModes/CaptureArea/CaptureAreaView.swift b/Reframed/CaptureModes/CaptureArea/CaptureAreaView.swift index b3854e6..86ebfd1 100644 --- a/Reframed/CaptureModes/CaptureArea/CaptureAreaView.swift +++ b/Reframed/CaptureModes/CaptureArea/CaptureAreaView.swift @@ -105,7 +105,7 @@ struct CaptureAreaView: View { private func fieldGroup(_ title: String, @ViewBuilder content: () -> Content) -> some View { VStack(spacing: 6) { - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xxs, weight: .medium)) .foregroundStyle(secondaryTextColor) content() diff --git a/Reframed/CaptureModes/CaptureScreen/StartRecordingOverlay.swift b/Reframed/CaptureModes/CaptureScreen/StartRecordingOverlay.swift index 96e8f8e..f33c314 100644 --- a/Reframed/CaptureModes/CaptureScreen/StartRecordingOverlay.swift +++ b/Reframed/CaptureModes/CaptureScreen/StartRecordingOverlay.swift @@ -35,7 +35,7 @@ struct StartRecordingOverlayView: View { VStack(spacing: 12) { if totalScreens > 1 { - Text("Display \(screenIndex)") + Text("显示器 \(screenIndex)") .font(.system(size: FontSize.xxl, weight: .bold, design: .rounded)) .foregroundStyle(Color.black) } diff --git a/Reframed/CaptureModes/CaptureWindow/ResizePopover.swift b/Reframed/CaptureModes/CaptureWindow/ResizePopover.swift index a5cd6f8..af1be49 100644 --- a/Reframed/CaptureModes/CaptureWindow/ResizePopover.swift +++ b/Reframed/CaptureModes/CaptureWindow/ResizePopover.swift @@ -152,7 +152,7 @@ struct ResizePopover: View { } private func resizeSectionHeader(_ title: String) -> some View { - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(secondaryTextColor) .frame(maxWidth: .infinity, alignment: .leading) @@ -170,7 +170,7 @@ private struct ResizePopoverRow: View { var body: some View { Button(action: action) { HStack { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs)) Spacer() } diff --git a/Reframed/Compositor/ExportSheet+Phases.swift b/Reframed/Compositor/ExportSheet+Phases.swift index 55b1d26..e368521 100644 --- a/Reframed/Compositor/ExportSheet+Phases.swift +++ b/Reframed/Compositor/ExportSheet+Phases.swift @@ -28,7 +28,7 @@ extension ExportSheet { .foregroundStyle(ReframedColors.secondaryText) if let eta = editorState.exportETA, eta > 0 { - Text("ETA \(formatDuration(seconds: Int(ceil(eta))))") + Text("预计剩余 \(formatDuration(seconds: Int(ceil(eta))))") .font(.system(size: FontSize.xs).monospacedDigit()) .foregroundStyle(ReframedColors.secondaryText) } diff --git a/Reframed/Compositor/ExportSheet.swift b/Reframed/Compositor/ExportSheet.swift index 93deb80..28e5252 100644 --- a/Reframed/Compositor/ExportSheet.swift +++ b/Reframed/Compositor/ExportSheet.swift @@ -93,7 +93,7 @@ struct ExportSheet: View { ) } - Text(settings.gifQuality.description) + Text(LocalizedStringKey(settings.gifQuality.description)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .padding(.top, -10) @@ -106,7 +106,7 @@ struct ExportSheet: View { ) } - Text(settings.codec.description) + Text(LocalizedStringKey(settings.codec.description)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .padding(.top, -10) @@ -126,7 +126,7 @@ struct ExportSheet: View { } if sourceFPS < 60 { - Text("Source recorded at \(sourceFPS) fps. Higher frame rates are not available.") + Text("源录制帧率为 \(sourceFPS) fps,不能导出更高帧率。") .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .padding(.top, -10) @@ -159,7 +159,7 @@ struct ExportSheet: View { ) } - Text(settings.mode.description) + Text(LocalizedStringKey(settings.mode.description)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .padding(.top, -10) @@ -174,7 +174,7 @@ struct ExportSheet: View { ) } - Text(settings.captionExportMode.description) + Text(LocalizedStringKey(settings.captionExportMode.description)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .padding(.top, -10) @@ -244,7 +244,7 @@ struct ExportSheet: View { func settingsRow(label: String, @ViewBuilder content: () -> Content) -> some View { VStack(alignment: .leading, spacing: 6) { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(ReframedColors.secondaryText) content() diff --git a/Reframed/Editor/EditorState+Export.swift b/Reframed/Editor/EditorState+Export.swift index bddc433..691f50e 100644 --- a/Reframed/Editor/EditorState+Export.swift +++ b/Reframed/Editor/EditorState+Export.swift @@ -16,11 +16,11 @@ extension EditorState { if isMicProcessing { exportStatusMessage = - "Waiting for noise reduction… \(Int(micProcessingProgress * 100))%" + "正在等待降噪完成… \(Int(micProcessingProgress * 100))%" while isMicProcessing { try await Task.sleep(for: .milliseconds(100)) exportStatusMessage = - "Waiting for noise reduction… \(Int(micProcessingProgress * 100))%" + "正在等待降噪完成… \(Int(micProcessingProgress * 100))%" } exportStatusMessage = nil } diff --git a/Reframed/Editor/EditorView+Sidebar.swift b/Reframed/Editor/EditorView+Sidebar.swift index 76c78a9..1252046 100644 --- a/Reframed/Editor/EditorView+Sidebar.swift +++ b/Reframed/Editor/EditorView+Sidebar.swift @@ -19,7 +19,7 @@ extension EditorView { Image(systemName: tab.icon) .font(.system(size: FontSize.base)) .foregroundStyle(ReframedColors.primaryText) - Text(tab.label) + Text(LocalizedStringKey(tab.label)) .font(.system(size: FontSize.xxs, weight: .semibold)) .foregroundStyle(selectedTab == tab ? ReframedColors.primaryText : ReframedColors.secondaryText) } diff --git a/Reframed/Editor/EditorView.swift b/Reframed/Editor/EditorView.swift index 5766761..0888acd 100644 --- a/Reframed/Editor/EditorView.swift +++ b/Reframed/Editor/EditorView.swift @@ -141,9 +141,9 @@ struct EditorView: View { ? editorState.micProcessingProgress * 0.5 : (micWaveformGenerator.isGenerating ? 0.5 + micWaveformGenerator.progress * 0.5 : nil), micAudioMessage: editorState.isMicProcessing - ? "Denoising… \(Int(editorState.micProcessingProgress * 100))%" + ? "正在降噪… \(Int(editorState.micProcessingProgress * 100))%" : (micWaveformGenerator.isGenerating - ? "Generating waveform… \(Int(micWaveformGenerator.progress * 100))%" + ? "正在生成波形… \(Int(micWaveformGenerator.progress * 100))%" : nil), onScrub: { time in editorState.pause() diff --git a/Reframed/Editor/EditorWindow.swift b/Reframed/Editor/EditorWindow.swift index 2328307..d6cb028 100644 --- a/Reframed/Editor/EditorWindow.swift +++ b/Reframed/Editor/EditorWindow.swift @@ -60,7 +60,7 @@ final class EditorWindow: NSObject, NSWindowDelegate { } window.isReleasedWhenClosed = false window.delegate = self - window.title = "Reframed Editor" + window.title = NSLocalizedString("Reframed Editor", comment: "") window.level = .floating window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) diff --git a/Reframed/Editor/History+ChangeRules.swift b/Reframed/Editor/History+ChangeRules.swift index 658f05f..fac7b32 100644 --- a/Reframed/Editor/History+ChangeRules.swift +++ b/Reframed/Editor/History+ChangeRules.swift @@ -6,62 +6,62 @@ extension History { guard old.trimStartSeconds != new.trimStartSeconds || old.trimEndSeconds != new.trimEndSeconds else { return [] } return [ - "Trim range \(formatCompactTime(seconds: old.trimStartSeconds))–\(formatCompactTime(seconds: old.trimEndSeconds)) → \(formatCompactTime(seconds: new.trimStartSeconds))–\(formatCompactTime(seconds: new.trimEndSeconds))" + "裁剪范围 \(formatCompactTime(seconds: old.trimStartSeconds))–\(formatCompactTime(seconds: old.trimEndSeconds)) → \(formatCompactTime(seconds: new.trimStartSeconds))–\(formatCompactTime(seconds: new.trimEndSeconds))" ] }, - prop(\.backgroundStyle) { "Background set to \(describeBackground($0))" }, + prop(\.backgroundStyle) { "背景设为 \(describeBackground($0))" }, prop(\.backgroundImageFillMode) { - "Background image fill mode set to \(($0 ?? .fill).label.lowercased())" + "背景图片填充方式设为 \(localizedLabel(($0 ?? .fill).label))" }, - prop(\.canvasAspect) { "Canvas aspect ratio set to \(($0 ?? .original).label)" }, - prop(\.padding) { "Padding set to \(Int($0 * 100))%" }, + prop(\.canvasAspect) { "画布比例设为 \(localizedLabel(($0 ?? .original).label))" }, + prop(\.padding) { "画布留白设为 \(Int($0 * 100))%" }, - prop(\.videoCornerRadius) { "Video corner radius set to \(Int($0))px" }, + prop(\.videoCornerRadius) { "视频圆角设为 \(Int($0))px" }, prop(\.videoShadow) { let val = Int($0 ?? 0) - return val == 0 ? "Video shadow removed" : "Video shadow set to \(val)" + return val == 0 ? "已移除视频阴影" : "视频阴影设为 \(val)" }, - prop(\.cameraAspect) { "Camera aspect ratio set to \(($0 ?? .original).label)" }, - prop(\.cameraCornerRadius) { "Camera corner radius set to \(Int($0))px" }, - prop(\.cameraBorderWidth) { "Camera border width set to \(String(format: "%.1f", $0))px" }, - prop(\.cameraBorderColor) { _ in "Camera border color updated" }, + prop(\.cameraAspect) { "摄像头比例设为 \(localizedLabel(($0 ?? .original).label))" }, + prop(\.cameraCornerRadius) { "摄像头圆角设为 \(Int($0))px" }, + prop(\.cameraBorderWidth) { "摄像头边框宽度设为 \(String(format: "%.1f", $0))px" }, + prop(\.cameraBorderColor) { _ in "摄像头边框颜色已更新" }, prop(\.cameraShadow) { let val = Int($0 ?? 0) - return val == 0 ? "Camera shadow removed" : "Camera shadow set to \(val)" + return val == 0 ? "已移除摄像头阴影" : "摄像头阴影设为 \(val)" }, - toggle(\.cameraMirrored, default: false, on: "Camera mirror enabled", off: "Camera mirror disabled"), + toggle(\.cameraMirrored, default: false, on: "已开启摄像头镜像", off: "已关闭摄像头镜像"), prop(\.cameraFullscreenFillMode) { - "Camera fullscreen fill mode set to \(($0 ?? .fit).label.lowercased())" + "摄像头全屏填充方式设为 \(localizedLabel(($0 ?? .fit).label))" }, prop(\.cameraFullscreenAspect) { - "Camera fullscreen aspect ratio set to \(($0 ?? .original).label)" + "摄像头全屏比例设为 \(localizedLabel(($0 ?? .original).label))" }, - prop(\.cameraLayout) { _ in "Camera repositioned" }, - toggle(\.webcamEnabled, default: true, on: "Webcam enabled", off: "Webcam disabled"), - prop(\.cameraBackgroundStyle) { "Camera background set to \(describeCameraBackground($0))" }, + prop(\.cameraLayout) { _ in "摄像头位置已调整" }, + toggle(\.webcamEnabled, default: true, on: "已启用摄像头", off: "已停用摄像头"), + prop(\.cameraBackgroundStyle) { "摄像头背景设为 \(describeCameraBackground($0))" }, { old, new in guard old.cursorSettings != new.cursorSettings else { return [] } let oldShow = old.cursorSettings?.showCursor ?? true let newShow = new.cursorSettings?.showCursor ?? true if oldShow != newShow { - return [newShow ? "Cursor enabled" : "Cursor disabled"] + return [newShow ? "已显示光标" : "已隐藏光标"] } let subRules: [ChangeRule] = [ sub(\.cursorSettings, \.cursorStyleRaw, default: 0) { let style = CursorStyle(rawValue: $0) ?? .centerDefault - return "Cursor style set to \(style.label)" + return "光标样式设为 \(localizedLabel(style.label))" }, - sub(\.cursorSettings, \.cursorSize, default: CGFloat(24)) { "Cursor size set to \(Int($0))px" }, + sub(\.cursorSettings, \.cursorSize, default: CGFloat(24)) { "光标大小设为 \(Int($0))px" }, subToggle( \.cursorSettings, \.showClickHighlights, default: true, - on: "Click highlights enabled", - off: "Click highlights disabled" + on: "已开启点击高亮", + off: "已关闭点击高亮" ), { old, new in guard @@ -70,50 +70,50 @@ extension History { else { return [] } var results: [String] = [] if old.cursorSettings?.clickHighlightColor != new.cursorSettings?.clickHighlightColor { - results.append("Click highlight color updated") + results.append("点击高亮颜色已更新") } if old.cursorSettings?.clickHighlightSize != new.cursorSettings?.clickHighlightSize { results.append( - "Click highlight size set to \(Int(new.cursorSettings?.clickHighlightSize ?? 36))px" + "点击高亮大小设为 \(Int(new.cursorSettings?.clickHighlightSize ?? 36))px" ) } return results }, sub(\.cursorSettings, \.cursorFillColor, default: nil as CodableColor?) { _ in - "Cursor fill color updated" + "光标填充色已更新" }, sub(\.cursorSettings, \.cursorStrokeColor, default: nil as CodableColor?) { _ in - "Cursor stroke color updated" + "光标描边色已更新" }, subToggle( \.cursorSettings, \.spotlightEnabled, default: false, - on: "Spotlight enabled", - off: "Spotlight disabled" + on: "已开启聚光灯", + off: "已关闭聚光灯" ), sub(\.cursorSettings, \.spotlightRadius, default: CGFloat(200)) { - "Spotlight radius set to \(Int($0))px" + "聚光灯半径设为 \(Int($0))px" }, sub(\.cursorSettings, \.spotlightDimOpacity, default: CGFloat(0.6)) { - "Spotlight dim opacity set to \(Int($0 * 100))%" + "聚光灯暗化强度设为 \(Int($0 * 100))%" }, sub(\.cursorSettings, \.spotlightEdgeSoftness, default: CGFloat(50)) { - "Spotlight edge softness set to \(Int($0))px" + "聚光灯边缘柔和度设为 \(Int($0))px" }, subToggle( \.cursorSettings, \.clickSoundEnabled, default: false, - on: "Click sound enabled", - off: "Click sound disabled" + on: "已开启点击音效", + off: "已关闭点击音效" ), sub(\.cursorSettings, \.clickSoundVolume, default: Float(0.5)) { - "Click sound volume set to \(Int($0 * 100))%" + "点击音效音量设为 \(Int($0 * 100))%" }, sub(\.cursorSettings, \.clickSoundStyleRaw, default: 0) { let style = ClickSoundStyle(rawValue: $0) ?? .click001 - return "Click sound style set to \(style.label)" + return "点击音效设为 \(localizedLabel(style.label))" }, ] return subRules.flatMap { $0(old, new) } @@ -122,29 +122,29 @@ extension History { { old, new in guard old.zoomSettings != new.zoomSettings else { return [] } let subRules: [ChangeRule] = [ - subToggle(\.zoomSettings, \.zoomEnabled, default: false, on: "Zoom enabled", off: "Zoom disabled"), + subToggle(\.zoomSettings, \.zoomEnabled, default: false, on: "已启用缩放", off: "已停用缩放"), subToggle( \.zoomSettings, \.autoZoomEnabled, default: false, - on: "Auto zoom enabled", - off: "Auto zoom disabled" + on: "已开启自动缩放", + off: "已关闭自动缩放" ), subToggle( \.zoomSettings, \.zoomFollowCursor, default: true, - on: "Zoom follow cursor enabled", - off: "Zoom follow cursor disabled" + on: "已开启缩放跟随光标", + off: "已关闭缩放跟随光标" ), sub(\.zoomSettings, \.zoomLevel, default: 2.0) { - "Zoom level set to \(String(format: "%.1f", $0))x" + "缩放倍数设为 \(String(format: "%.1f", $0))x" }, sub(\.zoomSettings, \.transitionDuration, default: 0.3) { - "Zoom transition speed set to \(String(format: "%.1f", $0))s" + "缩放转场速度设为 \(String(format: "%.1f", $0))s" }, sub(\.zoomSettings, \.dwellThreshold, default: 0.5) { - "Zoom dwell threshold set to \(String(format: "%.1f", $0))s" + "缩放停留阈值设为 \(String(format: "%.1f", $0))s" }, { old, new in let o = old.zoomSettings?.keyframes @@ -152,9 +152,9 @@ extension History { guard o != n else { return [] } let oldCount = o?.count ?? 0 let newCount = n?.count ?? 0 - if newCount > oldCount { return ["Zoom keyframe added"] } - if newCount < oldCount { return ["Zoom keyframe removed"] } - return ["Zoom keyframe adjusted"] + if newCount > oldCount { return ["已添加缩放关键帧"] } + if newCount < oldCount { return ["已移除缩放关键帧"] } + return ["缩放关键帧已调整"] }, ] return subRules.flatMap { $0(old, new) } @@ -167,27 +167,27 @@ extension History { \.animationSettings, \.cursorMovementEnabled, default: false, - on: "Cursor smoothing enabled", - off: "Cursor smoothing disabled" + on: "已开启光标平滑", + off: "已关闭光标平滑" ), sub(\.animationSettings, \.cursorMovementSpeed, default: .medium) { - "Cursor smoothing speed set to \($0.label)" + "光标平滑速度设为 \(localizedLabel($0.label))" }, subToggle( \.animationSettings, \.useSystemCursor, default: true, - on: "System cursor enabled", - off: "System cursor disabled" + on: "已启用系统光标", + off: "已停用系统光标" ), sub(\.animationSettings, \.clickBounce, default: CGFloat(0)) { - "Click bounce set to \(String(format: "%.1f", $0))" + "点击弹跳设为 \(String(format: "%.1f", $0))" }, sub(\.animationSettings, \.cursorSway, default: CGFloat(0)) { - "Cursor sway set to \(String(format: "%.2f", $0))" + "光标摆动设为 \(String(format: "%.2f", $0))" }, sub(\.animationSettings, \.cursorMotionBlur, default: CGFloat(0)) { - "Cursor motion blur set to \(String(format: "%.1f", $0))" + "光标运动模糊设为 \(String(format: "%.1f", $0))" }, ] return subRules.flatMap { $0(old, new) } @@ -200,20 +200,20 @@ extension History { let n = new.audioSettings if o?.systemAudioVolume != n?.systemAudioVolume || o?.systemAudioMuted != n?.systemAudioMuted { if n?.systemAudioMuted == true && o?.systemAudioMuted != true { - results.append("System audio muted") + results.append("系统声音已静音") } else if n?.systemAudioMuted != true && o?.systemAudioMuted == true { - results.append("System audio unmuted") + results.append("系统声音已取消静音") } else { - results.append("System audio volume set to \(Int((n?.systemAudioVolume ?? 1.0) * 100))%") + results.append("系统声音音量设为 \(Int((n?.systemAudioVolume ?? 1.0) * 100))%") } } if o?.micAudioVolume != n?.micAudioVolume || o?.micAudioMuted != n?.micAudioMuted { if n?.micAudioMuted == true && o?.micAudioMuted != true { - results.append("Mic audio muted") + results.append("麦克风已静音") } else if n?.micAudioMuted != true && o?.micAudioMuted == true { - results.append("Mic audio unmuted") + results.append("麦克风已取消静音") } else { - results.append("Mic audio volume set to \(Int((n?.micAudioVolume ?? 1.0) * 100))%") + results.append("麦克风音量设为 \(Int((n?.micAudioVolume ?? 1.0) * 100))%") } } let noiseRules: [ChangeRule] = [ @@ -221,11 +221,11 @@ extension History { \.audioSettings, \.micNoiseReductionEnabled, default: false, - on: "Noise reduction enabled", - off: "Noise reduction disabled" + on: "已开启降噪", + off: "已关闭降噪" ), sub(\.audioSettings, \.micNoiseReductionIntensity, default: Float(0.5)) { - "Noise reduction intensity set to \(Int($0 * 100))%" + "降噪强度设为 \(Int($0 * 100))%" }, ] results.append(contentsOf: noiseRules.flatMap { $0(old, new) }) @@ -234,39 +234,39 @@ extension History { regions( \.systemAudioRegions, - added: "System audio region added", - removed: "System audio region removed", - adjusted: "System audio region adjusted" + added: "已添加系统声音片段", + removed: "已移除系统声音片段", + adjusted: "系统声音片段已调整" ), regions( \.micAudioRegions, - added: "Mic audio region added", - removed: "Mic audio region removed", - adjusted: "Mic audio region adjusted" + added: "已添加麦克风片段", + removed: "已移除麦克风片段", + adjusted: "麦克风片段已调整" ), regions( \.cameraFullscreenRegions, - added: "Camera fullscreen region added", - removed: "Camera fullscreen region removed", - adjusted: "Camera fullscreen region adjusted" + added: "已添加摄像头全屏片段", + removed: "已移除摄像头全屏片段", + adjusted: "摄像头全屏片段已调整" ), regions( \.cameraRegions, - added: "Camera region added", - removed: "Camera region removed", - adjusted: "Camera region adjusted" + added: "已添加摄像头片段", + removed: "已移除摄像头片段", + adjusted: "摄像头片段已调整" ), regions( \.videoRegions, - added: "Video region added", - removed: "Video region removed", - adjusted: "Video region adjusted" + added: "已添加视频片段", + removed: "已移除视频片段", + adjusted: "视频片段已调整" ), regions( \.spotlightRegions, - added: "Spotlight region added", - removed: "Spotlight region removed", - adjusted: "Spotlight region adjusted" + added: "已添加聚光灯片段", + removed: "已移除聚光灯片段", + adjusted: "聚光灯片段已调整" ), { old, new in @@ -276,16 +276,16 @@ extension History { let oldSegs = old.captionSegments ?? [] let newSegs = new.captionSegments ?? [] if oldSegs.isEmpty && !newSegs.isEmpty { - return ["Captions generated (\(newSegs.count) segments)"] + return ["已生成字幕(\(newSegs.count) 个片段)"] } if !oldSegs.isEmpty && newSegs.isEmpty { - return ["Captions cleared"] + return ["字幕已清空"] } if oldSegs != newSegs && !oldSegs.isEmpty && !newSegs.isEmpty { if oldSegs.count != newSegs.count { - results.append("Caption segments updated (\(newSegs.count) segments)") + results.append("字幕片段已更新(\(newSegs.count) 个片段)") } else { - results.append("Caption segments edited") + results.append("字幕片段已编辑") } } let subRules: [ChangeRule] = [ @@ -293,48 +293,48 @@ extension History { \.captionSettings, \.enabled, default: true, - on: "Captions enabled", - off: "Captions disabled" + on: "已启用字幕", + off: "已停用字幕" ), sub(\.captionSettings, \.fontSize, default: CGFloat(48)) { - "Caption font size set to \(Int($0))px" + "字幕字号设为 \(Int($0))px" }, sub(\.captionSettings, \.fontWeight, default: .bold) { - "Caption font weight set to \($0.label)" + "字幕字重设为 \(localizedLabel($0.label))" }, sub(\.captionSettings, \.position, default: .bottom) { _ in - "Caption position updated" + "字幕位置已更新" }, sub(\.captionSettings, \.textColor, default: CodableColor(r: 1, g: 1, b: 1)) { _ in - "Caption text color updated" + "字幕文字颜色已更新" }, sub( \.captionSettings, \.backgroundColor, default: CodableColor(r: 0, g: 0, b: 0, a: 1.0) - ) { _ in "Caption background color updated" }, + ) { _ in "字幕背景颜色已更新" }, sub(\.captionSettings, \.backgroundOpacity, default: CGFloat(0.6)) { - "Caption background opacity set to \(Int($0 * 100))%" + "字幕背景不透明度设为 \(Int($0 * 100))%" }, subToggle( \.captionSettings, \.showBackground, default: true, - on: "Caption background enabled", - off: "Caption background disabled" + on: "已启用字幕背景", + off: "已停用字幕背景" ), sub(\.captionSettings, \.maxWordsPerLine, default: 6) { - "Caption words per line set to \($0)" + "字幕每行词数设为 \($0)" }, sub(\.captionSettings, \.model, default: "openai_whisper-base") { - let modelName = WhisperModel(rawValue: $0)?.shortLabel ?? "unknown" - return "Caption model set to \(modelName)" + let modelName = WhisperModel(rawValue: $0)?.shortLabel ?? "未知" + return "字幕模型设为 \(localizedLabel(modelName))" }, sub(\.captionSettings, \.language, default: .auto) { - "Caption language set to \($0.label)" + "字幕语言设为 \(localizedLabel($0.label))" }, sub(\.captionSettings, \.audioSource, default: .microphone) { - "Caption audio source set to \($0.label)" + "字幕音频来源设为 \(localizedLabel($0.label))" }, ] results.append(contentsOf: subRules.flatMap { $0(old, new) }) @@ -344,42 +344,85 @@ extension History { static func describeChanges(from old: EditorStateData, to new: EditorStateData) -> [String] { var changes = rules.flatMap { $0(old, new) } - if changes.isEmpty { changes.append("Editor settings updated") } + if changes.isEmpty { changes.append("编辑设置已更新") } return changes } static func describeBackground(_ style: BackgroundStyle) -> String { switch style { case .none: - return "none" + return "无" case .gradient(let id): if let preset = GradientPresets.preset(for: id) { - return "\(preset.name) gradient" + return "\(localizedLabel(preset.name)) 渐变" } - return "gradient" + return "渐变" case .solidColor: - return "solid color" + return "纯色" case .image: - return "image" + return "图片" } } static func describeCameraBackground(_ style: CameraBackgroundStyle?) -> String { - guard let style else { return "none" } + guard let style else { return "无" } switch style { case .none: - return "none" + return "无" case .blur: - return "blur" + return "模糊" case .solidColor: - return "solid color" + return "纯色" case .gradient(let id): if let preset = GradientPresets.preset(for: id) { - return "\(preset.name) gradient" + return "\(localizedLabel(preset.name)) 渐变" } - return "gradient" + return "渐变" case .image: - return "image" + return "图片" + } + } + + static func localizedLabel(_ label: String) -> String { + switch label { + case "Original": return "原始" + case "Fill": return "填满" + case "Fit": return "适应" + case "Default": return "默认" + case "Secondary": return "次要" + case "Pointer": return "指针" + case "Dot": return "圆点" + case "Circle": return "圆形" + case "Ring Dot": return "环形点" + case "Bullseye": return "靶心" + case "Diamond": return "菱形" + case "Plus": return "加号" + case "Crosshair": return "准星" + case "Cross Dot": return "十字点" + case "Cross Gap": return "间隔十字" + case "Spotlight": return "聚光灯" + case "Brackets": return "括号" + case "Corners": return "角标" + case "Square": return "正方形" + case "Star": return "星形" + case "Triangle": return "三角形" + case "Pen": return "钢笔" + case "Marker": return "马克笔" + case "Slow": return "慢速" + case "Medium": return "中等" + case "Fast": return "快速" + case "Rapid": return "极快" + case "Regular": return "常规" + case "Semibold": return "半粗体" + case "Bold": return "粗体" + case "Base": return "基础" + case "Turbo": return "极速" + case "Microphone": return "麦克风" + case "System Audio": return "系统声音" + case "Auto-detect": return "自动检测" + case "Chinese": return "中文" + case "English": return "英语" + default: return NSLocalizedString(label, comment: "") } } } diff --git a/Reframed/Editor/HistoryPopover.swift b/Reframed/Editor/HistoryPopover.swift index 56b7ae2..c2862b4 100644 --- a/Reframed/Editor/HistoryPopover.swift +++ b/Reframed/Editor/HistoryPopover.swift @@ -75,7 +75,7 @@ struct HistoryPopover: View { : isCurrent ? ReframedColors.primaryText : ReframedColors.secondaryText ) } else { - Text(diffs.first ?? "Editor settings updated") + Text(diffs.first ?? "编辑设置已更新") .font(.system(size: FontSize.xs, weight: isCurrent ? .semibold : .regular)) .foregroundStyle( isFuture diff --git a/Reframed/Editor/PropertiesPanel+CaptionsTab.swift b/Reframed/Editor/PropertiesPanel+CaptionsTab.swift index d27a491..69ce67b 100644 --- a/Reframed/Editor/PropertiesPanel+CaptionsTab.swift +++ b/Reframed/Editor/PropertiesPanel+CaptionsTab.swift @@ -33,7 +33,7 @@ extension PropertiesPanel { } if let model = WhisperModel(rawValue: editorState.captionModel) { - Text(model.description) + Text(LocalizedStringKey(model.description)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .lineLimit(2) @@ -102,7 +102,7 @@ extension PropertiesPanel { .frame(width: 32, alignment: .trailing) } HStack { - Text(transcriptionStatusText) + Text(LocalizedStringKey(transcriptionStatusText)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) Spacer() @@ -116,7 +116,7 @@ extension PropertiesPanel { HStack(spacing: 8) { Button( editorState.captionSegments.isEmpty && !editorState.transcriptionDidFinishEmpty - ? "Generate Captions" : "Regenerate" + ? "生成字幕" : "重新生成" ) { handleGenerateAction() } @@ -147,9 +147,9 @@ extension PropertiesPanel { private var transcriptionStatusText: String { let pct = Int(editorState.transcriptionProgress * 100) if editorState.transcriptionProgress < 0.15 { - return "Loading model… \(pct)%" + return "正在加载模型… \(pct)%" } - return "Transcribing… \(pct)%" + return "正在转写… \(pct)%" } private func handleGenerateAction() { @@ -201,8 +201,10 @@ extension PropertiesPanel { .foregroundStyle(ReframedColors.secondaryText) HStack(spacing: 4) { ForEach(Array(CaptionPosition.presets.enumerated()), id: \.offset) { _, preset in - Button(preset.label) { + Button { editorState.captionPosition = preset.position + } label: { + Text(LocalizedStringKey(preset.label)) } .buttonStyle(OutlineButtonStyle(size: .small, fullWidth: true)) } @@ -288,7 +290,7 @@ extension PropertiesPanel { Image(systemName: "list.bullet") .font(.system(size: FontSize.xs, weight: .semibold)) .foregroundStyle(ReframedColors.accent) - Text("Segments (\(editorState.captionSegments.count))") + Text("片段(\(editorState.captionSegments.count))") .font(.system(size: FontSize.xs, weight: .semibold)) .foregroundStyle(ReframedColors.primaryText) Spacer() diff --git a/Reframed/Editor/PropertiesPanel+CursorZoomTab.swift b/Reframed/Editor/PropertiesPanel+CursorZoomTab.swift index dd03a19..313e626 100644 --- a/Reframed/Editor/PropertiesPanel+CursorZoomTab.swift +++ b/Reframed/Editor/PropertiesPanel+CursorZoomTab.swift @@ -24,7 +24,7 @@ extension PropertiesPanel { RoundedRectangle(cornerRadius: Radius.md) .stroke(isSelected ? ReframedColors.ring : Color.clear, lineWidth: 2) ) - Text(style.label) + Text(LocalizedStringKey(style.label)) .font(.system(size: FontSize.xs, weight: isSelected ? .semibold : .regular)) .foregroundStyle(isSelected ? ReframedColors.primaryText : ReframedColors.secondaryText) .lineLimit(1) @@ -208,7 +208,7 @@ extension PropertiesPanel { private func infoParam(_ label: String, value: String) -> some View { HStack { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) Spacer() @@ -262,7 +262,7 @@ extension PropertiesPanel { value: $editorState.zoomTransitionSpeed, range: 0.1...4.0, step: 0.05, - formattedValue: String(format: "%.2fs", editorState.zoomTransitionSpeed), + formattedValue: String(format: "%.2f秒", editorState.zoomTransitionSpeed), valueWidth: 40 ) .onChange(of: editorState.zoomTransitionSpeed) { _, _ in @@ -275,7 +275,7 @@ extension PropertiesPanel { value: $editorState.zoomDwellThreshold, range: 0.5...10.0, step: 0.1, - formattedValue: String(format: "%.1fs", editorState.zoomDwellThreshold), + formattedValue: String(format: "%.1f秒", editorState.zoomDwellThreshold), valueWidth: 40 ) .onChange(of: editorState.zoomDwellThreshold) { _, _ in diff --git a/Reframed/Editor/PropertiesPanel+EffectsTab.swift b/Reframed/Editor/PropertiesPanel+EffectsTab.swift index 623e890..f9cccc3 100644 --- a/Reframed/Editor/PropertiesPanel+EffectsTab.swift +++ b/Reframed/Editor/PropertiesPanel+EffectsTab.swift @@ -73,10 +73,12 @@ extension PropertiesPanel { selection: $editorState.clickSoundStyle ) { ForEach(ClickSoundCategory.allCases, id: \.self) { category in - Section(category.label) { + Section(LocalizedStringKey(category.label)) { ForEach(ClickSoundStyle.styles(for: category)) { style in - Button(style.label) { + Button { editorState.clickSoundStyle = style + } label: { + Text(LocalizedStringKey(style.label)) } } } diff --git a/Reframed/Editor/PropertiesPanel+GeneralTab.swift b/Reframed/Editor/PropertiesPanel+GeneralTab.swift index f995abb..2f226be 100644 --- a/Reframed/Editor/PropertiesPanel+GeneralTab.swift +++ b/Reframed/Editor/PropertiesPanel+GeneralTab.swift @@ -31,7 +31,7 @@ extension PropertiesPanel { } infoRow("Project Size", value: formattedProjectSize()) - infoRow("Cursor Data", value: editorState.cursorMetadataProvider != nil ? "Yes" : "No") + infoRow("Cursor Data", value: yesNo(editorState.cursorMetadataProvider != nil)) if let date = editorState.project?.metadata.createdAt { infoRow("Recorded", value: formattedDate(date)) @@ -54,7 +54,7 @@ extension PropertiesPanel { infoRow("Resolution", value: "\(Int(editorState.result.screenSize.width))x\(Int(editorState.result.screenSize.height))") infoRow("FPS", value: "\(editorState.result.fps)") infoRow("Codec", value: codecLabel(editorState.result.captureQuality)) - infoRow("HDR", value: editorState.result.isHDR ? "Yes" : "No") + infoRow("HDR", value: yesNo(editorState.result.isHDR)) if let info = screenInfo { infoRow("Size", value: info.fileSize) if let bitrate = info.bitrate { @@ -149,14 +149,18 @@ extension PropertiesPanel { func captureModeLabel(_ mode: CaptureMode) -> String { switch mode { - case .none: "None" - case .entireScreen: "Entire Screen" - case .selectedWindow: "Window" - case .selectedArea: "Area" - case .device: "iOS Device" + case .none: "无" + case .entireScreen: "整个屏幕" + case .selectedWindow: "窗口" + case .selectedArea: "区域" + case .device: "iOS 设备" } } + func yesNo(_ value: Bool) -> String { + value ? "是" : "否" + } + func formattedProjectSize() -> String { guard let bundleURL = editorState.project?.bundleURL else { return "—" } let fm = FileManager.default @@ -181,7 +185,7 @@ extension PropertiesPanel { func infoRow(_ label: String, value: String) -> some View { HStack { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) Spacer() diff --git a/Reframed/Editor/RegionEditPopover.swift b/Reframed/Editor/RegionEditPopover.swift index 3c1cb75..33f3b7f 100644 --- a/Reframed/Editor/RegionEditPopover.swift +++ b/Reframed/Editor/RegionEditPopover.swift @@ -67,7 +67,7 @@ struct RegionEditPopover: View { value: $easeIn, range: 0.05...2.0, step: 0.05, - formattedValue: String(format: "%.2fs", easeIn), + formattedValue: String(format: "%.2f秒", easeIn), valueWidth: 42 ) .padding(.horizontal, 12) @@ -79,7 +79,7 @@ struct RegionEditPopover: View { value: $easeOut, range: 0.05...2.0, step: 0.05, - formattedValue: String(format: "%.2fs", easeOut), + formattedValue: String(format: "%.2f秒", easeOut), valueWidth: 42 ) .padding(.horizontal, 12) diff --git a/Reframed/Editor/SpotlightRegionEditPopover.swift b/Reframed/Editor/SpotlightRegionEditPopover.swift index 0c6ed1e..85a8b25 100644 --- a/Reframed/Editor/SpotlightRegionEditPopover.swift +++ b/Reframed/Editor/SpotlightRegionEditPopover.swift @@ -71,8 +71,8 @@ struct SpotlightRegionEditPopover: View { range: 0...1.0, step: 0.05, formattedValue: localFadeDuration == 0 - ? "Off" - : String(format: "%.2fs", localFadeDuration), + ? "关闭" + : String(format: "%.2f秒", localFadeDuration), valueWidth: 40 ) .onChange(of: localFadeDuration) { _, newValue in @@ -119,7 +119,7 @@ struct SpotlightRegionEditPopover: View { onReset: @escaping () -> Void ) -> some View { VStack(alignment: .leading, spacing: 4) { - ToggleRow(label: "Custom \(label)", isOn: useCustom) + ToggleRow(label: "自定义\(localizedLabel(label))", isOn: useCustom) .onChange(of: useCustom.wrappedValue) { _, newValue in if newValue { localValue.wrappedValue = globalValue @@ -145,4 +145,13 @@ struct SpotlightRegionEditPopover: View { } } } + + private func localizedLabel(_ label: String) -> String { + switch label { + case "Radius": return "半径" + case "Dim": return "暗化" + case "Softness": return "柔和度" + default: return label + } + } } diff --git a/Reframed/Editor/TimelineView+AudioTrack.swift b/Reframed/Editor/TimelineView+AudioTrack.swift index 15ed66b..323f282 100644 --- a/Reframed/Editor/TimelineView+AudioTrack.swift +++ b/Reframed/Editor/TimelineView+AudioTrack.swift @@ -80,7 +80,7 @@ extension TimelineView { } .fixedSize() - Text(message ?? "Generating waveform… \(Int(progress * 100))%") + Text(message ?? "正在生成波形… \(Int(progress * 100))%") .font(.system(size: FontSize.xs).monospacedDigit()) .foregroundStyle(ReframedColors.primaryText) .frame(width: 160, alignment: .leading) diff --git a/Reframed/Editor/TimelineView+CameraTrack.swift b/Reframed/Editor/TimelineView+CameraTrack.swift index e2d0a57..12cbf03 100644 --- a/Reframed/Editor/TimelineView+CameraTrack.swift +++ b/Reframed/Editor/TimelineView+CameraTrack.swift @@ -64,7 +64,7 @@ extension TimelineView { Image(systemName: region.type.icon) .font(.system(size: Track.fontSize)) if regionWidth > 50 { - Text(region.type.label) + Text(LocalizedStringKey(region.type.label)) .font(.system(size: Track.fontSize, weight: Track.fontWeight)) .lineLimit(1) } diff --git a/Reframed/Editor/TimelineView+Shared.swift b/Reframed/Editor/TimelineView+Shared.swift index e04539a..07d431b 100644 --- a/Reframed/Editor/TimelineView+Shared.swift +++ b/Reframed/Editor/TimelineView+Shared.swift @@ -6,7 +6,7 @@ extension TimelineView { VStack(spacing: 4) { Image(systemName: icon) .font(.system(size: FontSize.sm)) - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xxs, weight: .semibold)) } .foregroundStyle(ReframedColors.primaryText) diff --git a/Reframed/Editor/ZoomKeyframeEditor+RegionView.swift b/Reframed/Editor/ZoomKeyframeEditor+RegionView.swift index 82a0771..2be6203 100644 --- a/Reframed/Editor/ZoomKeyframeEditor+RegionView.swift +++ b/Reframed/Editor/ZoomKeyframeEditor+RegionView.swift @@ -26,12 +26,12 @@ extension ZoomKeyframeEditor { .lineLimit(1) } if regionWidth > 90 { - Text(String(format: "%.1fs", totalDur)) + Text(String(format: "%.1f秒", totalDur)) .font(.system(size: Track.fontSize)) .lineLimit(1) } if regionWidth > 160, easeIn > 0.01 || easeOut > 0.01 { - Text(String(format: "↗%.1fs ↘%.1fs", easeIn, easeOut)) + Text(String(format: "↗%.1f秒 ↘%.1f秒", easeIn, easeOut)) .font(.system(size: Track.fontSize - 1)) .lineLimit(1) } diff --git a/Reframed/Recording/WebcamPreviewWindow.swift b/Reframed/Recording/WebcamPreviewWindow.swift index c0a2e19..4315bf0 100644 --- a/Reframed/Recording/WebcamPreviewWindow.swift +++ b/Reframed/Recording/WebcamPreviewWindow.swift @@ -40,7 +40,7 @@ final class WebcamPreviewWindow { spinner.startAnimation(nil) container.addSubview(spinner) - let label = NSTextField(labelWithString: "Camera is starting...") + let label = NSTextField(labelWithString: NSLocalizedString("Camera is starting...", comment: "")) label.font = NSFont.systemFont(ofSize: FontSize.xs, weight: .medium) label.textColor = ReframedColors.secondaryTextNS label.alignment = .center diff --git a/Reframed/State/CaptureState.swift b/Reframed/State/CaptureState.swift index 624ea6c..8ccb0ac 100644 --- a/Reframed/State/CaptureState.swift +++ b/Reframed/State/CaptureState.swift @@ -26,27 +26,64 @@ enum CaptureError: LocalizedError { var errorDescription: String? { switch self { case .invalidTransition(let from, let to): - return "Invalid state transition from \(from) to \(to)" + return "状态切换无效:从 \(from) 切换到 \(to)" case .noSelectionStored: - return "No screen region has been selected" + return "还没有选择屏幕区域" case .displayNotFound: - return "Could not find the target display" + return "找不到目标显示器" case .permissionDenied: - return "Screen recording permission is required" + return "需要屏幕录制权限" case .recordingFailed(let reason): - return "Recording failed: \(reason)" + return "录制失败:\(localizedFailureReason(reason))" case .microphoneNotFound: - return "Could not find the selected microphone" + return "找不到已选择的麦克风" case .cameraNotFound: - return "Could not find the selected camera" + return "找不到已选择的摄像头" case .cameraStreamFailed: - return "Camera failed to start streaming. Make sure no other app is using the camera." + return "摄像头无法开始采集。请确认没有其他 App 正在使用摄像头。" case .microphoneStreamFailed: - return "Microphone failed to start streaming. Make sure no other app is using the microphone." + return "麦克风无法开始采集。请确认没有其他 App 正在使用麦克风。" case .deviceNotFound: - return "No iOS device found. Connect an iPhone or iPad via USB and trust this Mac." + return "未找到 iOS 设备。请用 USB 连接 iPhone 或 iPad,并在设备上信任这台 Mac。" case .deviceStreamFailed: - return "Device failed to start streaming. Make sure the device is unlocked and trusted." + return "设备无法开始采集。请确认设备已解锁并信任这台 Mac。" + } + } + + private func localizedFailureReason(_ reason: String) -> String { + if reason.hasPrefix("gifski_add_frame failed") { + return reason.replacingOccurrences(of: "gifski_add_frame failed", with: "添加 GIF 帧失败") + } + if reason.hasPrefix("gifski_finish failed") { + return reason.replacingOccurrences(of: "gifski_finish failed", with: "结束 GIF 编码失败") + } + + return switch reason { + case "Failed to create mono audio format": "无法创建单声道音频格式" + case "No audio data after conversion": "音频转换后没有可用数据" + case "No output from noise reduction": "降噪没有产生输出" + case "Failed to allocate output audio buffer": "无法分配输出音频缓冲区" + case "Failed to allocate stereo audio buffer": "无法分配立体声音频缓冲区" + case "Failed to allocate audio write buffer": "无法分配音频写入缓冲区" + case "Unsupported audio format for noise reduction": "当前音频格式不支持降噪" + case "Failed to allocate audio read buffer": "无法分配音频读取缓冲区" + case "Failed to allocate audio conversion buffer": "无法分配音频转换缓冲区" + case "Failed to convert audio to 48kHz mono": "无法将音频转换为 48kHz 单声道" + case "No video track found": "找不到视频轨道" + case "No screen track found": "找不到屏幕录制轨道" + case "No video track in screen recording": "屏幕录制中没有视频轨道" + case "Failed to create export session": "无法创建导出会话" + case "Failed to create audio mix session": "无法创建音频混合会话" + case "Failed to start video reader": "无法启动视频读取器" + case "Failed to start audio reader": "无法启动音频读取器" + case "Failed to start writing": "无法开始写入文件" + case "Failed to create pixel buffer pool": "无法创建像素缓冲池" + case "Failed to create pixel buffer pool for GIF": "无法为 GIF 创建像素缓冲池" + case "Export writing failed": "导出写入失败" + case "Failed to create gifski encoder": "无法创建 GIF 编码器" + case "Failed to set gifski output file": "无法设置 GIF 输出文件" + case "Screen recording file missing from project bundle": "项目包中缺少屏幕录制文件" + default: reason } } } diff --git a/Reframed/State/SessionState+Camera.swift b/Reframed/State/SessionState+Camera.swift index b196fe7..a78cea1 100644 --- a/Reframed/State/SessionState+Camera.swift +++ b/Reframed/State/SessionState+Camera.swift @@ -49,7 +49,7 @@ extension SessionState { } catch { guard isCameraOn, options.selectedCamera?.id == cam.id else { return } cameraPreviewState = .failed(error.localizedDescription) - previewWindow.showError("Camera failed to start") + previewWindow.showError(NSLocalizedString("Camera failed to start", comment: "")) logger.error("Camera preview failed: \(error)") } } diff --git a/Reframed/State/SessionState+Project.swift b/Reframed/State/SessionState+Project.swift index 6cd4f17..328f3cd 100644 --- a/Reframed/State/SessionState+Project.swift +++ b/Reframed/State/SessionState+Project.swift @@ -62,7 +62,7 @@ extension SessionState { openEditor(project: project) } catch { logger.error("Failed to open project: \(error)") - showError("Failed to open project: \(error.localizedDescription)") + showError("打开项目失败:\(error.localizedDescription)") } } diff --git a/Reframed/State/SessionState+UI.swift b/Reframed/State/SessionState+UI.swift index 4d0aa7c..965a41e 100644 --- a/Reframed/State/SessionState+UI.swift +++ b/Reframed/State/SessionState+UI.swift @@ -141,10 +141,10 @@ extension SessionState { func showError(_ message: String) { let alert = NSAlert() - alert.messageText = "Recording Error" + alert.messageText = NSLocalizedString("Recording Error", comment: "") alert.informativeText = message alert.alertStyle = .critical - alert.addButton(withTitle: "OK") + alert.addButton(withTitle: NSLocalizedString("OK", comment: "")) alert.runModal() } diff --git a/Reframed/UI/AreaSizePresetsPopover.swift b/Reframed/UI/AreaSizePresetsPopover.swift index 3e57b7a..ad25d15 100644 --- a/Reframed/UI/AreaSizePresetsPopover.swift +++ b/Reframed/UI/AreaSizePresetsPopover.swift @@ -52,7 +52,7 @@ private struct AreaPresetRow: View { var body: some View { Button(action: action) { HStack(spacing: 8) { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs)) Spacer() } diff --git a/Reframed/UI/CaptureToolbarComponents.swift b/Reframed/UI/CaptureToolbarComponents.swift index f07cc2a..5da123d 100644 --- a/Reframed/UI/CaptureToolbarComponents.swift +++ b/Reframed/UI/CaptureToolbarComponents.swift @@ -41,7 +41,7 @@ struct ToolbarActionButton: View { } .buttonStyle(PlainCustomButtonStyle()) .onHover { isHovered = $0 } - .help(tooltip) + .help(NSLocalizedString(tooltip, comment: "")) } } @@ -60,7 +60,7 @@ struct ToolbarToggleButton: View { Image(systemName: isOn ? activeIcon : icon) .font(.system(size: Layout.toolbarIconSize)) .foregroundStyle(iconColor) - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xxs, weight: .semibold)) .foregroundStyle(labelColor) } @@ -71,7 +71,7 @@ struct ToolbarToggleButton: View { } .buttonStyle(PlainCustomButtonStyle()) .disabled(!isAvailable) - .help(tooltip) + .help(NSLocalizedString(tooltip, comment: "")) } private var iconColor: Color { diff --git a/Reframed/UI/CheckmarkRow.swift b/Reframed/UI/CheckmarkRow.swift index 41fedba..af776ca 100644 --- a/Reframed/UI/CheckmarkRow.swift +++ b/Reframed/UI/CheckmarkRow.swift @@ -14,7 +14,7 @@ struct CheckmarkRow: View { .font(.system(size: FontSize.xs, weight: .bold)) .frame(width: 14) .opacity(isSelected ? 1 : 0) - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xs)) Spacer() } diff --git a/Reframed/UI/DevicePickerPopover.swift b/Reframed/UI/DevicePickerPopover.swift index 0356599..ac85687 100644 --- a/Reframed/UI/DevicePickerPopover.swift +++ b/Reframed/UI/DevicePickerPopover.swift @@ -15,7 +15,7 @@ struct DevicePickerPopover: View { SectionHeader(title: title) if devices.isEmpty { - Text(emptyMessage) + Text(LocalizedStringKey(emptyMessage)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.tertiaryText) .padding(.horizontal, 12) diff --git a/Reframed/UI/Dropdown.swift b/Reframed/UI/Dropdown.swift index 2610205..393a22f 100644 --- a/Reframed/UI/Dropdown.swift +++ b/Reframed/UI/Dropdown.swift @@ -13,7 +13,7 @@ struct Dropdown: View { menuContent() } label: { HStack { - Text(selectedLabel) + Text(LocalizedStringKey(selectedLabel)) .font(.system(size: ButtonSize.small.fontSize, weight: ButtonSize.small.fontWeight)) .foregroundStyle(ReframedColors.primaryText) Spacer() diff --git a/Reframed/UI/ImageDropSection.swift b/Reframed/UI/ImageDropSection.swift index e2f0b90..6a0fb72 100644 --- a/Reframed/UI/ImageDropSection.swift +++ b/Reframed/UI/ImageDropSection.swift @@ -22,7 +22,7 @@ struct ImageDropSection: View { } label: { HStack { Image(systemName: "photo.on.rectangle") - Text(image != nil ? "Change Image" : "Choose Image") + Text(LocalizedStringKey(image != nil ? "Change Image" : "Choose Image")) } .font(.system(size: FontSize.xs)) .frame(maxWidth: .infinity) diff --git a/Reframed/UI/LanguagePicker.swift b/Reframed/UI/LanguagePicker.swift index dcf9091..fa29432 100644 --- a/Reframed/UI/LanguagePicker.swift +++ b/Reframed/UI/LanguagePicker.swift @@ -41,7 +41,7 @@ struct LanguagePicker: View { onSelect?() } label: { HStack { - Text(lang.label) + Text(LocalizedStringKey(lang.label)) .font(.system(size: FontSize.xs)) Spacer() if selection == lang { diff --git a/Reframed/UI/MenuBarComponents.swift b/Reframed/UI/MenuBarComponents.swift index af8606c..6adca5c 100644 --- a/Reframed/UI/MenuBarComponents.swift +++ b/Reframed/UI/MenuBarComponents.swift @@ -31,7 +31,7 @@ struct ActionGridItem: View { .foregroundStyle(ReframedColors.primaryText) .frame(height: 22) - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xxs, weight: .semibold)) .foregroundStyle(ReframedColors.primaryText) } diff --git a/Reframed/UI/MenuBarView.swift b/Reframed/UI/MenuBarView.swift index 2e0d212..29f387f 100644 --- a/Reframed/UI/MenuBarView.swift +++ b/Reframed/UI/MenuBarView.swift @@ -98,7 +98,7 @@ struct MenuBarView: View { MenuBarDivider() - Text(totalProjectCount > 0 ? "Projects (\(totalProjectCount))" : "Projects") + Text(totalProjectCount > 0 ? "项目 (\(totalProjectCount))" : "项目") .font(.system(size: FontSize.xxs, weight: .semibold)) .foregroundStyle(ReframedColors.secondaryText) .padding(.horizontal, 12) diff --git a/Reframed/UI/ModeButton.swift b/Reframed/UI/ModeButton.swift index 8173b73..662fb2d 100644 --- a/Reframed/UI/ModeButton.swift +++ b/Reframed/UI/ModeButton.swift @@ -14,7 +14,7 @@ struct ModeButton: View { Image(systemName: icon) .font(.system(size: Layout.toolbarIconSize)) .foregroundStyle(ReframedColors.primaryText) - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xxs, weight: .semibold)) .foregroundStyle(ReframedColors.primaryText) } diff --git a/Reframed/UI/PermissionsView.swift b/Reframed/UI/PermissionsView.swift index 46aed9f..a358de2 100644 --- a/Reframed/UI/PermissionsView.swift +++ b/Reframed/UI/PermissionsView.swift @@ -62,11 +62,11 @@ private struct PermissionRow: View { var body: some View { HStack(alignment: .center, spacing: 16) { VStack(alignment: .leading, spacing: 4) { - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(ReframedColors.primaryText) - Text(description) + Text(LocalizedStringKey(description)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .lineLimit(3) @@ -86,7 +86,7 @@ private struct PermissionRow: View { Image(systemName: "checkmark") .font(.system(size: FontSize.xs, weight: .semibold)) } - Text(granted ? grantedLabel : requestLabel) + Text(LocalizedStringKey(granted ? grantedLabel : requestLabel)) .font(.system(size: FontSize.xs, weight: .medium)) } .frame(width: 260) diff --git a/Reframed/UI/SectionHeader.swift b/Reframed/UI/SectionHeader.swift index 2234887..226a0ea 100644 --- a/Reframed/UI/SectionHeader.swift +++ b/Reframed/UI/SectionHeader.swift @@ -13,12 +13,12 @@ struct SectionHeader: View { Image(systemName: icon) .font(.system(size: FontSize.sm)) .foregroundStyle(ReframedColors.secondaryText) - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xs, weight: .semibold)) .foregroundStyle(ReframedColors.primaryText) } } else { - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xxs, weight: .semibold)) .foregroundStyle(ReframedColors.secondaryText) .frame(maxWidth: .infinity, alignment: .leading) diff --git a/Reframed/UI/SegmentPicker.swift b/Reframed/UI/SegmentPicker.swift index 344087c..57ee220 100644 --- a/Reframed/UI/SegmentPicker.swift +++ b/Reframed/UI/SegmentPicker.swift @@ -31,7 +31,7 @@ private struct ToggleGroupItem: View { var body: some View { Button(action: action) { - Text(text) + Text(LocalizedStringKey(text)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(ReframedColors.primaryText) .frame(maxWidth: .infinity) diff --git a/Reframed/UI/SelectButton.swift b/Reframed/UI/SelectButton.swift index fe7d8fb..56ed5e4 100644 --- a/Reframed/UI/SelectButton.swift +++ b/Reframed/UI/SelectButton.swift @@ -43,7 +43,7 @@ struct SelectButton: View { if let leadingContent { leadingContent } - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(ReframedColors.primaryText) .lineLimit(1) diff --git a/Reframed/UI/SettingsAboutTab.swift b/Reframed/UI/SettingsAboutTab.swift index e7bb648..c39f3b4 100644 --- a/Reframed/UI/SettingsAboutTab.swift +++ b/Reframed/UI/SettingsAboutTab.swift @@ -26,7 +26,7 @@ extension SettingsView { .foregroundStyle(ReframedColors.primaryText) VStack(spacing: 4) { - Text("Version \(UpdateChecker.currentVersion)") + Text("版本 \(UpdateChecker.currentVersion)") .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) @@ -73,7 +73,7 @@ extension SettingsView { HStack(spacing: 4) { Image(systemName: icon) .font(.system(size: FontSize.xs)) - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xs)) } .foregroundStyle(ReframedColors.secondaryText) @@ -97,7 +97,7 @@ private struct ChangelogView: View { Group { if let version, let changelog, !changelog.isEmpty { VStack(alignment: .leading, spacing: 8) { - Text("Latest Release — v\(version)") + Text("最新版本 - v\(version)") .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(ReframedColors.primaryText) diff --git a/Reframed/UI/SettingsRecordingTab.swift b/Reframed/UI/SettingsRecordingTab.swift index 89cfaba..324e9f6 100644 --- a/Reframed/UI/SettingsRecordingTab.swift +++ b/Reframed/UI/SettingsRecordingTab.swift @@ -14,7 +14,7 @@ extension SettingsView { ) } - Text(captureQualityDescription) + Text(LocalizedStringKey(captureQualityDescription)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .padding(.top, -10) diff --git a/Reframed/UI/SettingsView.swift b/Reframed/UI/SettingsView.swift index 96a0937..6d79a4f 100644 --- a/Reframed/UI/SettingsView.swift +++ b/Reframed/UI/SettingsView.swift @@ -75,7 +75,7 @@ struct SettingsView: View { Button { selectedTab = tab } label: { - Text(tab.rawValue) + Text(LocalizedStringKey(tab.rawValue)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(selectedTab == tab ? ReframedColors.primaryText : ReframedColors.secondaryText) .frame(maxWidth: .infinity) @@ -95,7 +95,7 @@ struct SettingsView: View { func settingsRow(label: String, @ViewBuilder content: () -> Content) -> some View { VStack(alignment: .leading, spacing: 6) { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(ReframedColors.primaryText) content() @@ -104,7 +104,7 @@ struct SettingsView: View { func settingsToggle(_ title: String, isOn: Binding) -> some View { HStack { - Text(title) + Text(LocalizedStringKey(title)) .font(.system(size: FontSize.xs, weight: .medium)) .foregroundStyle(ReframedColors.primaryText) Spacer() @@ -127,7 +127,7 @@ struct SettingsView: View { panel.canChooseFiles = false panel.canCreateDirectories = true panel.allowsMultipleSelection = false - panel.prompt = "Select" + panel.prompt = NSLocalizedString("Select", comment: "") if panel.runModal() == .OK, let url = panel.url { let path = url.path.replacingOccurrences( @@ -145,7 +145,7 @@ struct SettingsView: View { panel.canChooseFiles = false panel.canCreateDirectories = true panel.allowsMultipleSelection = false - panel.prompt = "Select" + panel.prompt = NSLocalizedString("Select", comment: "") if panel.runModal() == .OK, let url = panel.url { let path = url.path.replacingOccurrences( diff --git a/Reframed/UI/ShortcutRow.swift b/Reframed/UI/ShortcutRow.swift index a679f62..b9f6b58 100644 --- a/Reframed/UI/ShortcutRow.swift +++ b/Reframed/UI/ShortcutRow.swift @@ -18,7 +18,7 @@ struct ShortcutRow: View { var body: some View { let _ = colorScheme HStack { - Text(action.label) + Text(LocalizedStringKey(action.label)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.primaryText) Spacer() diff --git a/Reframed/UI/SliderRow.swift b/Reframed/UI/SliderRow.swift index 32b9e15..e8f4506 100644 --- a/Reframed/UI/SliderRow.swift +++ b/Reframed/UI/SliderRow.swift @@ -15,12 +15,12 @@ struct SliderRow: View where V.Stride: BinaryFloatingPoi HStack(spacing: 8) { if let label { if let labelWidth { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) .frame(width: labelWidth, alignment: .leading) } else { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.secondaryText) } diff --git a/Reframed/UI/StartRecordingButton.swift b/Reframed/UI/StartRecordingButton.swift index 47a3db2..399eb65 100644 --- a/Reframed/UI/StartRecordingButton.swift +++ b/Reframed/UI/StartRecordingButton.swift @@ -17,7 +17,7 @@ struct StartRecordingButton: View { HStack(spacing: 6) { if let remaining { Image(systemName: "timer") - Text("Recording in \(remaining)...") + Text("\(remaining) 秒后开始录制...") } else { Image(systemName: "record.circle") Text("Start recording") diff --git a/Reframed/UI/TailwindColorPicker.swift b/Reframed/UI/TailwindColorPicker.swift index a829d8c..f9c4c8e 100644 --- a/Reframed/UI/TailwindColorPicker.swift +++ b/Reframed/UI/TailwindColorPicker.swift @@ -74,7 +74,7 @@ private struct ColorPickerRow: View { .fill(preset.swiftUIColor) .overlay(Circle().stroke(ReframedColors.border, lineWidth: 1)) .frame(width: 18, height: 18) - Text(preset.name) + Text(LocalizedStringKey(preset.name)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.primaryText) Spacer() diff --git a/Reframed/UI/ToggleRow.swift b/Reframed/UI/ToggleRow.swift index 560b664..58dc53b 100644 --- a/Reframed/UI/ToggleRow.swift +++ b/Reframed/UI/ToggleRow.swift @@ -8,7 +8,7 @@ struct ToggleRow: View { var body: some View { let _ = colorScheme HStack { - Text(label) + Text(LocalizedStringKey(label)) .font(.system(size: FontSize.xs)) .foregroundStyle(ReframedColors.primaryText) Spacer() diff --git a/Reframed/UI/TransitionControlsSection.swift b/Reframed/UI/TransitionControlsSection.swift index 4860ce7..a4a4f9e 100644 --- a/Reframed/UI/TransitionControlsSection.swift +++ b/Reframed/UI/TransitionControlsSection.swift @@ -22,7 +22,7 @@ struct TransitionControlsSection: View { value: $entryDuration, range: 0.05...1.0, step: 0.05, - formattedValue: String(format: "%.2fs", entryDuration) + formattedValue: String(format: "%.2f秒", entryDuration) ) } @@ -42,7 +42,7 @@ struct TransitionControlsSection: View { value: $exitDuration, range: 0.05...1.0, step: 0.05, - formattedValue: String(format: "%.2fs", exitDuration) + formattedValue: String(format: "%.2f秒", exitDuration) ) } } diff --git a/Reframed/Utilities/TimeFormatting.swift b/Reframed/Utilities/TimeFormatting.swift index 1ca5812..ba1112a 100644 --- a/Reframed/Utilities/TimeFormatting.swift +++ b/Reframed/Utilities/TimeFormatting.swift @@ -33,7 +33,7 @@ func formatCompactTime(seconds: Double) -> String { if mins > 0 { return String(format: "%d:%04.1f", mins, secs) } - return String(format: "%.1fs", secs) + return String(format: "%.1f秒", secs) } func formatTimeRange(start: Double, end: Double) -> String { @@ -57,12 +57,12 @@ func formatTimestamp(_ date: Date = Date()) -> String { func formatRelativeTime(_ date: Date) -> String { let seconds = Int(-date.timeIntervalSinceNow) - if seconds < 5 { return "just now" } - if seconds < 60 { return "\(seconds)s ago" } + if seconds < 5 { return "刚刚" } + if seconds < 60 { return "\(seconds) 秒前" } let minutes = seconds / 60 - if minutes < 60 { return "\(minutes)m ago" } + if minutes < 60 { return "\(minutes) 分钟前" } let hours = minutes / 60 - if hours < 24 { return "\(hours)h ago" } + if hours < 24 { return "\(hours) 小时前" } let days = hours / 24 - return "\(days)d ago" + return "\(days) 天前" } diff --git a/Reframed/zh-Hans.lproj/InfoPlist.strings b/Reframed/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 0000000..100bb28 --- /dev/null +++ b/Reframed/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,7 @@ +/* Simplified Chinese Info.plist localization for Reframed. */ + +"NSScreenCaptureUsageDescription" = "Reframed 需要屏幕录制权限来录制你的屏幕。"; +"NSMicrophoneUsageDescription" = "Reframed 需要麦克风权限,才能在屏幕录制时录入麦克风声音。"; +"NSCameraUsageDescription" = "Reframed 需要摄像头权限,才能在屏幕录制时同时录入摄像头画面。"; +"CFBundleTypeName" = "Reframed 项目"; +"UTTypeDescription" = "Reframed 项目"; diff --git a/Reframed/zh-Hans.lproj/Localizable.strings b/Reframed/zh-Hans.lproj/Localizable.strings new file mode 100644 index 0000000..bd077f5 --- /dev/null +++ b/Reframed/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,603 @@ +/* Simplified Chinese localization for Reframed. */ + +"About" = "关于"; +"Accessibility" = "辅助功能"; +"Accessibility allowed" = "已允许辅助功能"; +"Allow Accessibility" = "允许辅助功能"; +"Allow Screen Recording" = "允许屏幕录制"; +"Appearance" = "外观"; +"Apply" = "应用"; +"Area" = "区域"; +"Aspect Ratio" = "画面比例"; +"Audio" = "音频"; +"Audio Bitrate (kbps)" = "音频码率 (kbps)"; +"Auto Zoom" = "自动缩放"; +"Back" = "返回"; +"Background" = "背景"; +"Blur" = "模糊"; +"Border" = "边框"; +"Browse" = "浏览"; +"Bullseye" = "靶心"; +"Camera" = "摄像头"; +"Camera Device" = "摄像头设备"; +"Camera Region" = "摄像头片段"; +"Cancel" = "取消"; +"Canvas" = "画布"; +"Captions" = "字幕"; +"Center" = "居中"; +"Check for Updates" = "检查更新"; +"Circle" = "圆形"; +"Clear" = "清空"; +"Click Bounce" = "点击弹跳"; +"Click Highlights" = "点击高亮"; +"Click Sound" = "点击音效"; +"Close" = "关闭"; +"Codec" = "编码器"; +"Color" = "颜色"; +"Connect via USB and unlock" = "通过 USB 连接并解锁"; +"Copy to Clipboard" = "复制到剪贴板"; +"Corner Radius" = "圆角"; +"Cross Dot" = "十字点"; +"Cross Gap" = "间隔十字"; +"Crosshair" = "准星"; +"Cursor" = "光标"; +"Cursor Effects" = "光标效果"; +"Cursor Movement" = "光标移动"; +"Custom" = "自定义"; +"Default" = "默认"; +"Delete" = "删除"; +"Device" = "设备"; +"Devices" = "设备"; +"Diamond" = "菱形"; +"Dim" = "暗化"; +"Dim Outer Area While Recording" = "录制时暗化外部区域"; +"Display" = "显示器"; +"Done" = "完成"; +"Dot" = "圆点"; +"Download" = "下载"; +"Downloading model…" = "正在下载模型…"; +"Drag captions in preview to reposition" = "在预览中拖动字幕来调整位置"; +"Duration" = "时长"; +"Ease In" = "淡入"; +"Ease Out" = "淡出"; +"Editor" = "编辑器"; +"Effects" = "效果"; +"Enable Click Sound" = "启用点击音效"; +"Enable Spotlight" = "启用聚光灯"; +"Enable Zoom" = "启用缩放"; +"Enabled" = "启用"; +"Entry Transition" = "进入转场"; +"Exit Transition" = "退出转场"; +"Export" = "导出"; +"Export Failed" = "导出失败"; +"Export Settings" = "导出设置"; +"Export Successful" = "导出成功"; +"Exporting…" = "正在导出…"; +"Fade" = "淡化"; +"Fast" = "快速"; +"Fill" = "填满"; +"Fill Mode" = "填充方式"; +"Foff" = "Foff"; +"Follow Cursor" = "跟随光标"; +"Format" = "格式"; +"Frame Rate" = "帧率"; +"Full Screen" = "全屏"; +"Fullscreen" = "全屏"; +"General" = "通用"; +"Generate" = "生成"; +"Grant Permissions" = "授予权限"; +"Gradient" = "渐变"; +"HDR Capture" = "HDR 录制"; +"Hide Camera Preview While Recording" = "录制时隐藏摄像头预览"; +"History" = "历史"; +"Hold" = "停留"; +"Home" = "首页"; +"Image" = "图片"; +"Initial state" = "初始状态"; +"Intensity" = "强度"; +"Issues" = "问题"; +"Language" = "语言"; +"Latest Release" = "最新版本"; +"Level" = "级别"; +"Marker" = "马克笔"; +"Medium" = "中等"; +"Mic" = "麦克风"; +"Microphone" = "麦克风"; +"Mirror" = "镜像"; +"Mode Selection" = "模式选择"; +"Model" = "模型"; +"Motion Blur" = "运动模糊"; +"Mute" = "静音"; +"No devices found" = "未找到设备"; +"No history" = "暂无历史"; +"No recent projects" = "暂无最近项目"; +"No speech detected in the audio." = "没有在音频中检测到语音。"; +"Noise Reduction" = "降噪"; +"None" = "无"; +"Opacity" = "不透明度"; +"Open Projects" = "打开项目"; +"Options" = "选项"; +"Original" = "原始"; +"Output Folder" = "输出文件夹"; +"Padding" = "留白"; +"Pen" = "钢笔"; +"Pointer" = "指针"; +"Position" = "位置"; +"Preset" = "预设"; +"Presets" = "预设"; +"Press shortcut..." = "按下快捷键..."; +"Primary" = "主屏幕"; +"Processing..." = "处理中..."; +"Project" = "项目"; +"Project Folder" = "项目文件夹"; +"Quality" = "质量"; +"Quick Actions" = "快捷操作"; +"Quit" = "退出"; +"Radius" = "半径"; +"Rapid" = "极快"; +"Recording" = "录制"; +"Recording Controls" = "录制控制"; +"Recording Info" = "录制信息"; +"Reframed needs Screen Recording and Accessibility permissions to work." = "Reframed 需要屏幕录制和辅助功能权限才能工作。"; +"Releases" = "发布版本"; +"Remember Last Selection" = "记住上次选择"; +"Requires cursor data to be recorded." = "需要录制光标数据。"; +"Reset" = "重置"; +"Reset All to Defaults" = "全部恢复默认"; +"Resolution" = "分辨率"; +"Restart" = "重新开始"; +"Retina Capture (Supersample)" = "Retina 录制(超采样)"; +"Ring Dot" = "环形点"; +"Screen" = "屏幕"; +"Screen Capture" = "屏幕录制"; +"Screen Recording" = "屏幕录制"; +"Screen recording & editing for macOS" = "macOS 屏幕录制与编辑"; +"Screen recording allowed" = "已允许屏幕录制"; +"Search…" = "搜索…"; +"Secondary" = "次要"; +"Select Camera" = "选择摄像头"; +"Select Microphone" = "选择麦克风"; +"Settings" = "设置"; +"Shadow" = "阴影"; +"Shift to lock aspect ratio · Esc to cancel · Enter to start" = "按住 Shift 锁定比例 · Esc 取消 · Enter 开始"; +"Shortcut" = "快捷键"; +"Shortcuts" = "快捷键"; +"Show Cursor" = "显示光标"; +"Show Highlights" = "显示高亮"; +"Show Recording Preview" = "显示录制预览"; +"Show in Finder" = "在 Finder 中显示"; +"Size" = "大小"; +"Size Presets" = "尺寸预设"; +"Slow" = "慢速"; +"Smooth Movement" = "平滑移动"; +"Softness" = "柔和度"; +"Sound" = "声音"; +"Source" = "来源"; +"Speed" = "速度"; +"Spotlight" = "聚光灯"; +"Spotlight Region" = "聚光灯片段"; +"Square" = "正方形"; +"Start recording" = "开始录制"; +"Stroke" = "描边"; +"Style" = "样式"; +"Sway" = "摆动"; +"System" = "系统"; +"System Audio" = "系统声音"; +"System Cursor" = "系统光标"; +"Tab to cycle windows · Esc to cancel · Enter to start" = "Tab 切换窗口 · Esc 取消 · Enter 开始"; +"Tab to cycle · Esc to cancel" = "Tab 切换 · Esc 取消"; +"Text" = "文字"; +"This will discard the current recording and return to mode selection." = "这会丢弃当前录制,并返回模式选择。"; +"This will permanently delete the source recording files." = "这会永久删除源录制文件。"; +"Timer" = "倒计时"; +"Timer Delay" = "倒计时延迟"; +"Transition" = "转场"; +"Video" = "视频"; +"Video Region" = "视频片段"; +"Volume" = "音量"; +"Weight" = "字重"; +"Window" = "窗口"; +"Words" = "词数"; +"Zoom" = "缩放"; + +"Capture Quality" = "录制质量"; +"H.265 (HEVC) 10-bit — great quality, smaller files" = "H.265 (HEVC) 10-bit — 画质好,文件更小"; +"ProRes 422 — near-lossless, larger files" = "ProRes 422 — 接近无损,文件更大"; +"ProRes 4444 — lossless quality, massive files" = "ProRes 4444 — 无损画质,文件很大"; +"Maximum Camera Resolution" = "最高摄像头分辨率"; + +"MP4" = "MP4"; +"MOV" = "MOV"; +"GIF" = "GIF"; +"H.264" = "H.264"; +"H.265" = "H.265"; +"ProRes 422" = "ProRes 422"; +"ProRes 4444" = "ProRes 4444"; +"Renderer" = "渲染器"; +"Standard export pipeline." = "标准导出流程。"; +"Burned In" = "嵌入画面"; +"Sidecar" = "字幕文件"; +"Disabled" = "关闭"; + +"Entire Screen" = "整个屏幕"; +"iOS Device" = "iOS 设备"; +"Selected Window" = "所选窗口"; +"Selected Area" = "所选区域"; + +"Automatic" = "自动"; +"Light" = "浅色"; +"Dark" = "深色"; + +"High" = "高"; +"Very High" = "很高"; +"Low" = "低"; +"Large" = "大"; +"Small" = "小"; + +"None (0s)" = "无 (0 秒)"; +"3 seconds" = "3 秒"; +"5 seconds" = "5 秒"; +"10 seconds" = "10 秒"; + +"Auto" = "自动"; +"Manual" = "手动"; +"YouTube" = "YouTube"; +"Twitter/X" = "Twitter/X"; +"TikTok" = "TikTok"; +"Instagram" = "Instagram"; +"Discord" = "Discord"; + +"Tiny" = "极小"; +"Base" = "基础"; +"Both" = "两者"; + +"Regular" = "常规"; +"Bold" = "粗体"; +"Black" = "黑体"; +"Top" = "顶部"; +"Bottom" = "底部"; + +"Double-click to add audio region" = "双击添加音频片段"; +"Double-click to add camera region" = "双击添加摄像头片段"; +"Double-click to add spotlight region" = "双击添加聚光灯片段"; +"Double-click to add zoom region" = "双击添加缩放片段"; +"Hover over a window to select it" = "将鼠标悬停在窗口上即可选择"; +"Make sure your device is unlocked before recording" = "录制前请确认设备已解锁"; +"Open Device" = "打开设备"; +"Press Esc to cancel · Enter to start" = "按 Esc 取消 · 按 Enter 开始"; +"Remove" = "移除"; +"Resize" = "调整大小"; +"Shift to lock aspect ratio \u{00b7} Esc to cancel \u{00b7} Enter to start" = "按住 Shift 锁定比例 · Esc 取消 · Enter 开始"; +"GitHub" = "GitHub"; +"Reframed" = "Reframed"; + +"Recording Error" = "录制错误"; +"OK" = "好"; +"Reframed Editor" = "Reframed 编辑器"; +"Delete Recording?" = "删除录制?"; +"Restart Recording?" = "重新开始录制?"; + +"Doubles capture resolution for better zoom quality. Only enable this on retina displays, otherwise it will result in blurry video." = "将录制分辨率加倍,以获得更好的缩放画质。只建议在 Retina 显示器上开启,否则视频可能会变模糊。"; +"Records in HDR with wider color range and higher brightness. Requires Apple Silicon. Exported videos preserve HDR when played on compatible displays." = "以 HDR 录制,保留更宽的色域和更高亮度。需要 Apple Silicon。导出视频在兼容显示器上播放时会保留 HDR。"; + +"Standard" = "标准"; +"Semibold" = "半粗体"; +"Hidden" = "隐藏"; +"Scale" = "缩放"; +"Slide" = "滑入"; +"Normal" = "普通"; +"Parallel" = "并行"; +"Maximum" = "最高"; +"Burn In" = "嵌入画面"; +"Render captions directly into the video." = "将字幕直接渲染进视频画面。"; +"Export SubRip (.srt) subtitle file alongside the video." = "在视频旁边同时导出 SubRip (.srt) 字幕文件。"; +"Export WebVTT (.vtt) subtitle file alongside the video." = "在视频旁边同时导出 WebVTT (.vtt) 字幕文件。"; +"Smallest file size. Noticeable quality loss." = "文件最小,但画质损失明显。"; +"Good balance of size and quality." = "文件大小和画质比较均衡。"; +"High quality. Recommended for most use cases." = "高画质,适合大多数场景。"; +"Best quality. Larger file size." = "最佳画质,文件更大。"; +"Multi-core parallel rendering. Faster export." = "多核心并行渲染,导出更快。"; +"Widely compatible. Larger file size, works everywhere." = "兼容性最好,文件较大,基本哪里都能播放。"; +"Better compression. Smaller file size, same quality." = "压缩效率更高,同等画质下文件更小。"; +"Professional editing codec. Large files, excellent quality." = "专业剪辑编码,文件较大,画质优秀。"; +"Highest quality with alpha channel. Very large files." = "最高画质并支持透明通道,文件非常大。"; + +"Display Mode" = "显示器模式"; +"Window Mode" = "窗口模式"; +"Area Mode" = "区域模式"; +"Stop Recording" = "停止录制"; +"Pause / Resume" = "暂停 / 继续"; +"Restart Recording" = "重新开始录制"; +"Undo" = "撤销"; +"Redo" = "重做"; + +"Click" = "点击"; +"Drop" = "落下"; +"Select" = "选择"; +"Switch" = "切换"; +"Toggle" = "开关"; +"Click 1" = "点击 1"; +"Click 2" = "点击 2"; +"Click 3" = "点击 3"; +"Click 4" = "点击 4"; +"Click 5" = "点击 5"; +"Click 8-bit" = "8-bit 点击"; +"Click Soft" = "轻柔点击"; +"Drop 1" = "落下 1"; +"Drop 2" = "落下 2"; +"Drop 3" = "落下 3"; +"Drop 4" = "落下 4"; +"Select 1" = "选择 1"; +"Select 2" = "选择 2"; +"Select 3" = "选择 3"; +"Select 4" = "选择 4"; +"Select 5" = "选择 5"; +"Select 6" = "选择 6"; +"Select 7" = "选择 7"; +"Select 8" = "选择 8"; +"Switch 1" = "切换 1"; +"Switch 2" = "切换 2"; +"Switch 3" = "切换 3"; +"Switch 4" = "切换 4"; +"Switch 5" = "切换 5"; +"Switch 6" = "切换 6"; +"Switch 7" = "切换 7"; +"Toggle 1" = "开关 1"; +"Toggle 2" = "开关 2"; +"Toggle 3" = "开关 3"; +"Toggle 4" = "开关 4"; + +"~150 MB. Fast, lower accuracy. Good for clear English audio." = "约 150 MB。速度快,准确率较低,适合清晰的英语音频。"; +"~1.5 GB. High accuracy, slower. Good for multilingual." = "约 1.5 GB。准确率高,速度较慢,适合多语言。"; +"~3 GB. Fast and accurate. Recommended for most use cases." = "约 3 GB。速度快且准确,推荐大多数场景使用。"; +"~3 GB. Best accuracy, slowest. Best for difficult audio." = "约 3 GB。准确率最高,速度最慢,适合复杂音频。"; +"Turbo" = "极速"; +"Base (~150 MB)" = "基础(约 150 MB)"; +"Medium (~1.5 GB)" = "中等(约 1.5 GB)"; +"Turbo (~3 GB)" = "极速(约 3 GB)"; +"Large (~3 GB)" = "大型(约 3 GB)"; + +"Auto-detect" = "自动检测"; +"English" = "英语"; +"Chinese" = "中文"; +"German" = "德语"; +"Spanish" = "西班牙语"; +"Russian" = "俄语"; +"Korean" = "韩语"; +"French" = "法语"; +"Japanese" = "日语"; +"Portuguese" = "葡萄牙语"; +"Turkish" = "土耳其语"; +"Polish" = "波兰语"; +"Catalan" = "加泰罗尼亚语"; +"Dutch" = "荷兰语"; +"Arabic" = "阿拉伯语"; +"Swedish" = "瑞典语"; +"Italian" = "意大利语"; +"Indonesian" = "印尼语"; +"Hindi" = "印地语"; +"Finnish" = "芬兰语"; +"Vietnamese" = "越南语"; +"Hebrew" = "希伯来语"; +"Ukrainian" = "乌克兰语"; +"Greek" = "希腊语"; +"Malay" = "马来语"; +"Czech" = "捷克语"; +"Romanian" = "罗马尼亚语"; +"Danish" = "丹麦语"; +"Hungarian" = "匈牙利语"; +"Tamil" = "泰米尔语"; +"Norwegian" = "挪威语"; +"Thai" = "泰语"; +"Urdu" = "乌尔都语"; +"Croatian" = "克罗地亚语"; +"Bulgarian" = "保加利亚语"; +"Lithuanian" = "立陶宛语"; +"Latin" = "拉丁语"; +"Maori" = "毛利语"; +"Malayalam" = "马拉雅拉姆语"; +"Welsh" = "威尔士语"; +"Slovak" = "斯洛伐克语"; +"Telugu" = "泰卢固语"; +"Persian" = "波斯语"; +"Latvian" = "拉脱维亚语"; +"Bengali" = "孟加拉语"; +"Serbian" = "塞尔维亚语"; +"Azerbaijani" = "阿塞拜疆语"; +"Slovenian" = "斯洛文尼亚语"; +"Kannada" = "卡纳达语"; +"Estonian" = "爱沙尼亚语"; +"Macedonian" = "马其顿语"; +"Breton" = "布列塔尼语"; +"Basque" = "巴斯克语"; +"Icelandic" = "冰岛语"; +"Armenian" = "亚美尼亚语"; +"Nepali" = "尼泊尔语"; +"Mongolian" = "蒙古语"; +"Bosnian" = "波斯尼亚语"; +"Kazakh" = "哈萨克语"; +"Albanian" = "阿尔巴尼亚语"; +"Swahili" = "斯瓦希里语"; +"Galician" = "加利西亚语"; +"Marathi" = "马拉地语"; +"Punjabi" = "旁遮普语"; +"Sinhala" = "僧伽罗语"; +"Khmer" = "高棉语"; +"Shona" = "绍纳语"; +"Yoruba" = "约鲁巴语"; +"Somali" = "索马里语"; +"Afrikaans" = "南非荷兰语"; +"Occitan" = "奥克语"; +"Georgian" = "格鲁吉亚语"; +"Belarusian" = "白俄罗斯语"; +"Tajik" = "塔吉克语"; +"Sindhi" = "信德语"; +"Gujarati" = "古吉拉特语"; +"Amharic" = "阿姆哈拉语"; +"Yiddish" = "意第绪语"; +"Lao" = "老挝语"; +"Uzbek" = "乌兹别克语"; +"Faroese" = "法罗语"; +"Haitian Creole" = "海地克里奥尔语"; +"Pashto" = "普什图语"; +"Turkmen" = "土库曼语"; +"Nynorsk" = "新挪威语"; +"Maltese" = "马耳他语"; +"Sanskrit" = "梵语"; +"Luxembourgish" = "卢森堡语"; +"Myanmar" = "缅甸语"; +"Tibetan" = "藏语"; +"Tagalog" = "他加禄语"; +"Malagasy" = "马达加斯加语"; +"Assamese" = "阿萨姆语"; +"Tatar" = "鞑靼语"; +"Hawaiian" = "夏威夷语"; +"Lingala" = "林加拉语"; +"Hausa" = "豪萨语"; +"Bashkir" = "巴什基尔语"; +"Javanese" = "爪哇语"; +"Sundanese" = "巽他语"; +"Cantonese" = "粤语"; + +"Amber" = "琥珀"; +"Blue" = "蓝色"; +"Cyan" = "青色"; +"Emerald" = "祖母绿"; +"Fuchsia" = "紫红"; +"Gray" = "灰色"; +"Green" = "绿色"; +"Indigo" = "靛蓝"; +"Lime" = "酸橙绿"; +"Orange" = "橙色"; +"Pink" = "粉色"; +"Purple" = "紫色"; +"Red" = "红色"; +"Rose" = "玫瑰"; +"Slate" = "石板灰"; +"Stone" = "石灰"; +"Teal" = "蓝绿色"; +"Violet" = "紫罗兰"; +"Yellow" = "黄色"; +"Zinc" = "锌灰"; + +"Required to capture your screen. A restart may be needed after granting this." = "用于录制你的屏幕。授予权限后可能需要重新启动 App。"; +"Used to track your cursor and listen for keyboard shortcuts during recording." = "用于在录制时追踪光标,并监听录制快捷键。"; +"No cameras found" = "未找到摄像头"; +"No microphones found" = "未找到麦克风"; +"Capture System Audio" = "录制系统声音"; +"Camera is starting..." = "摄像头正在启动..."; +"Camera failed to start" = "摄像头启动失败"; +"Change Image" = "更换图片"; +"Choose Image" = "选择图片"; +"Capture Mode" = "录制模式"; +"Project Size" = "项目大小"; +"Cursor Data" = "光标数据"; +"Recorded" = "录制时间"; +"FPS" = "帧率"; +"Bitrate" = "码率"; +"HDR" = "HDR"; +"Yes" = "是"; +"No" = "否"; +"3 Seconds" = "3 秒"; +"5 Seconds" = "5 秒"; +"10 Seconds" = "10 秒"; +"H.265 (HEVC)" = "H.265 (HEVC)"; +"ProRes" = "ProRes"; +"SRT" = "SRT"; +"VTT" = "VTT"; +"Fit" = "适应"; +"Plus" = "加号"; +"Brackets" = "括号"; +"Corners" = "角标"; +"Star" = "星形"; +"Triangle" = "三角形"; +"X" = "X"; +"Resume" = "继续"; +"Pause" = "暂停"; +"Stop" = "停止"; + +"Hyper" = "高亮"; +"Oceanic" = "海洋"; +"Cotton Candy" = "棉花糖"; +"Gotham" = "哥谭"; +"Sunset" = "日落"; +"Mojave" = "莫哈韦"; +"Beachside" = "海滩"; +"Gunmetal" = "枪灰"; +"Peachy" = "蜜桃"; +"Seafoam" = "海沫"; +"Pumpkin" = "南瓜"; +"Pandora" = "潘多拉"; +"Valentine" = "情人节"; +"Hawaii" = "夏威夷"; +"Lavender" = "薰衣草"; +"Wintergreen" = "冬青"; +"Huckleberry" = "越橘"; +"Blue Steel" = "钢蓝"; +"Arendelle" = "阿伦黛尔"; +"Spearmint" = "留兰香"; +"Minnesota" = "明尼苏达"; +"Bombpop" = "冰棒"; +"Acadia" = "阿卡迪亚"; +"Sonora" = "索诺拉"; +"Paradise" = "天堂"; +"Sierra Mist" = "山雾"; +"Creamsicle" = "奶油冰棒"; +"Midnight" = "午夜"; +"Borealis" = "极光"; +"Strawberry" = "草莓"; +"Flamingo" = "火烈鸟粉"; +"Burning Sunrise" = "燃烧日出"; +"Apple" = "苹果"; +"Watermelon" = "西瓜"; +"Flare" = "耀斑"; +"Rasta" = "拉斯塔"; +"Lust" = "浓烈红"; +"Sublime" = "清新绿"; +"Witch" = "女巫"; +"Powerpuff" = "泡泡糖"; +"Solid Blue" = "纯蓝"; +"Ice" = "冰蓝"; +"Sky" = "天空"; +"Horizon" = "地平线"; +"Morning" = "清晨"; +"Space" = "太空"; +"Earth" = "大地"; +"Picture" = "画面"; +"Messenger" = "信使"; +"Sea" = "海洋"; +"Payment" = "支付"; +"Passion" = "热情"; +"Flower" = "花朵"; +"Cool Sunset" = "冷调日落"; +"Pink Neon" = "粉色霓虹"; +"Blue Sand" = "蓝沙"; +"Relaxed Rose" = "柔和玫瑰"; +"Purple Haze" = "紫雾"; +"Silver" = "银色"; +"Orange Coral" = "橙珊瑚"; +"Blue Coral" = "蓝珊瑚"; +"Beam of Light" = "光束"; +"Safari Sunset" = "旷野日落"; +"High Tide" = "高潮"; +"Hunniepop" = "蜂蜜糖"; +"Soft Metal" = "柔金属"; +"Coral Sun" = "珊瑚太阳"; +"Power Pink" = "能量粉"; +"Powder Blue" = "粉蓝"; +"Moody Sunset" = "暮色日落"; +"Burnt Sand" = "焦沙"; +"Blue White Split" = "蓝白分割"; +"Purple Beam" = "紫色光束"; +"Sand Beam" = "沙色光束"; +"Island Waves" = "岛屿海浪"; +"Big Sur" = "大苏尔"; +"Oahu" = "欧胡岛"; +"Peach Pie" = "桃子派"; +"Salem" = "塞勒姆"; +"Purple Burst" = "紫色爆发"; +"Amber Sunrise" = "琥珀日出"; +"Sky Sea" = "天空海"; +"Rocket Power" = "火箭能量"; +"Blue Flame" = "蓝焰"; +"Warm Glow" = "暖光";