diff --git a/apps/chrome-extension/camera-permission.html b/apps/chrome-extension/camera-permission.html index 5451ff7bf26..d79c651602d 100644 --- a/apps/chrome-extension/camera-permission.html +++ b/apps/chrome-extension/camera-permission.html @@ -1,9 +1,9 @@ - + - Camera access - Cap + 摄像头权限 - Cap diff --git a/apps/chrome-extension/camera-preview.html b/apps/chrome-extension/camera-preview.html index ca9d94312f6..826c580577a 100644 --- a/apps/chrome-extension/camera-preview.html +++ b/apps/chrome-extension/camera-preview.html @@ -1,9 +1,9 @@ - + - Cap Camera Preview + Cap 摄像头预览 diff --git a/apps/chrome-extension/e2e/overlay-ui.spec.ts b/apps/chrome-extension/e2e/overlay-ui.spec.ts index dc4a287ee86..e6ea13702ea 100644 --- a/apps/chrome-extension/e2e/overlay-ui.spec.ts +++ b/apps/chrome-extension/e2e/overlay-ui.spec.ts @@ -347,7 +347,7 @@ test("clicking X in the panel closes the panel and the camera preview", async () const panelFrame = frameWithUrl(targetPage, "popup.html"); if (!panelFrame) throw new Error("panel frame missing"); await panelFrame - .locator('button[aria-label="Close Cap and hide all recorder UI"]') + .locator('button[aria-label="关闭 Cap 并隐藏所有录制界面"]') .click(); // The panel and the camera preview should both tear down. @@ -419,7 +419,7 @@ test("a failed recording start reopens the panel with the error", async () => { .toBe(true); const panelFrame = frameWithUrl(targetPage, "popup.html"); if (!panelFrame) throw new Error("panel frame missing"); - await expect(panelFrame.getByText("Recording failed.")).toBeVisible({ + await expect(panelFrame.getByText("录制失败。")).toBeVisible({ timeout: 10_000, }); await targetPage.screenshot({ diff --git a/apps/chrome-extension/how-it-works.html b/apps/chrome-extension/how-it-works.html index 3734907d4d1..3cfba0f84e1 100644 --- a/apps/chrome-extension/how-it-works.html +++ b/apps/chrome-extension/how-it-works.html @@ -1,9 +1,9 @@ - + - How it works - Cap + 使用说明 - Cap
@@ -81,10 +81,9 @@ > -

How Cap works

+

Cap 如何工作

- Instant Mode uploads while you record, so your share link is ready the - moment you stop. + 即时模式会在录制过程中同步上传,因此停止录制时分享链接已准备就绪。

  1. @@ -92,10 +91,9 @@

    How Cap works

    - Step 1 + 第 1 步

    - Click the Cap icon, pick a tab, window, screen or camera, and press - start. + 点击 Cap 图标,选择标签页、窗口、屏幕或摄像头,然后开始录制。

  2. @@ -110,10 +108,9 @@

    How Cap works

    d="M 18 29 L 24 23 L 30 29" > - Step 2 + 第 2 步

    - Your video streams to the cloud while you record. No exports, no - waiting at the end. + 视频会在录制过程中传输到云端,无需导出,结束后也不用等待。

  3. @@ -128,36 +125,32 @@

    How Cap works

    > - Step 3 + 第 3 步

    - Stop the recording and the share link is live instantly. Paste it - anywhere. + 停止录制后,分享链接会立即生效,可粘贴到任意位置。

-

Good to know

+

实用提示

- Click the Cap icon in your toolbar whenever you're ready to record. + 准备好后,随时点击工具栏中的 Cap 图标开始录制。

diff --git a/apps/chrome-extension/offscreen.html b/apps/chrome-extension/offscreen.html index 07811d502c2..a7821aeee06 100644 --- a/apps/chrome-extension/offscreen.html +++ b/apps/chrome-extension/offscreen.html @@ -1,9 +1,9 @@ - + - Cap Recorder Offscreen + Cap 后台录制器 diff --git a/apps/chrome-extension/options.html b/apps/chrome-extension/options.html index 583f37990f5..39056f29e95 100644 --- a/apps/chrome-extension/options.html +++ b/apps/chrome-extension/options.html @@ -1,9 +1,9 @@ - + - Options - Cap + 设置 - Cap
diff --git a/apps/chrome-extension/popup-window.html b/apps/chrome-extension/popup-window.html index 9573af9a38b..fa4a80d84ba 100644 --- a/apps/chrome-extension/popup-window.html +++ b/apps/chrome-extension/popup-window.html @@ -1,9 +1,9 @@ - + - Cap Recorder + Cap 录制器
diff --git a/apps/chrome-extension/popup.html b/apps/chrome-extension/popup.html index 9573af9a38b..fa4a80d84ba 100644 --- a/apps/chrome-extension/popup.html +++ b/apps/chrome-extension/popup.html @@ -1,9 +1,9 @@ - + - Cap Recorder + Cap 录制器
diff --git a/apps/chrome-extension/public/manifest.json b/apps/chrome-extension/public/manifest.json index eeb058f457c..b8369c1cbae 100644 --- a/apps/chrome-extension/public/manifest.json +++ b/apps/chrome-extension/public/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, - "name": "Cap - Screen Recorder & Screen Capture", + "name": "Cap - 屏幕录制与截图", "short_name": "Cap", - "description": "Free, open source screen recorder. Capture your screen, tab, camera & mic in Chrome and share a video link the moment you stop.", + "description": "免费开源的屏幕录制工具。在 Chrome 中录制屏幕、标签页、摄像头和麦克风,停止录制后即可分享视频链接。", "version": "1.0.2", "homepage_url": "https://cap.so", "minimum_chrome_version": "116", @@ -13,7 +13,7 @@ "128": "icons/icon-128.png" }, "action": { - "default_title": "Record your screen with Cap", + "default_title": "使用 Cap 录制屏幕", "default_icon": { "16": "icons/icon-16.png", "32": "icons/icon-32.png", diff --git a/apps/chrome-extension/src/background/service-worker.ts b/apps/chrome-extension/src/background/service-worker.ts index 43eccb06c54..43ef67e41fe 100644 --- a/apps/chrome-extension/src/background/service-worker.ts +++ b/apps/chrome-extension/src/background/service-worker.ts @@ -146,9 +146,7 @@ const createTab = (url: string) => new Promise((resolve, reject) => { chrome.tabs.create({ url, active: true }, (tab) => { if (chrome.runtime.lastError) { - reject( - new Error(chrome.runtime.lastError.message ?? "Failed to open tab"), - ); + reject(new Error(chrome.runtime.lastError.message ?? "打开标签页失败")); return; } resolve(tab); @@ -160,9 +158,7 @@ const updateTab = (tabId: number, url: string) => chrome.tabs.update(tabId, { url, active: true }, (tab) => { if (chrome.runtime.lastError || !tab) { reject( - new Error( - chrome.runtime.lastError?.message ?? "Failed to update tab", - ), + new Error(chrome.runtime.lastError?.message ?? "更新标签页失败"), ); return; } @@ -219,7 +215,7 @@ const createOffscreenDocument = () => { url: OFFSCREEN_URL, reasons: ["USER_MEDIA", "DISPLAY_MEDIA", "BLOBS", "AUDIO_PLAYBACK"], - justification: "Record and upload Cap videos from an extension page.", + justification: "从扩展页面录制并上传 Cap 视频。", }, () => { const error = chrome.runtime.lastError; @@ -228,7 +224,7 @@ const createOffscreenDocument = () => return; } - const message = error.message ?? "Failed to create offscreen document"; + const message = error.message ?? "创建后台录制文档失败"; if (message.toLowerCase().includes("single offscreen document")) { resolve(); return; @@ -267,7 +263,7 @@ const sendOffscreenRuntimeMessage = (message: OffscreenRequest) => new Promise((resolve, reject) => { chrome.runtime.sendMessage(message, (response) => { if (chrome.runtime.lastError) { - reject(new Error(chrome.runtime.lastError.message ?? "Message failed")); + reject(new Error(chrome.runtime.lastError.message ?? "消息发送失败")); return; } resolve(response as OffscreenResponse); @@ -312,11 +308,7 @@ const getTabStreamId = (tabId: number) => new Promise((resolve, reject) => { chrome.tabCapture.getMediaStreamId({ targetTabId: tabId }, (streamId) => { if (chrome.runtime.lastError) { - reject( - new Error( - chrome.runtime.lastError.message ?? "Failed to capture tab", - ), - ); + reject(new Error(chrome.runtime.lastError.message ?? "录制标签页失败")); return; } resolve(streamId); @@ -596,9 +588,7 @@ const updateActionForStatus = (nextStatus: RecordingStatus) => { // The recorder renders inside the page, so the action never opens a popup. setActionPopup(""), setActionBadgeText(isCapturing ? "REC" : ""), - setActionTitle( - isCapturing ? "Stop Cap recording" : "Record your screen with Cap", - ), + setActionTitle(isCapturing ? "停止 Cap 录制" : "使用 Cap 录制屏幕"), ]).then(() => undefined); }; @@ -849,7 +839,7 @@ const waitForWebcamPreviewReady = (tabId: number) => { previewReadyWaiters, tabId, START_PREVIEW_READY_TIMEOUT_MS, - "Camera preview did not become ready before recording started.", + "开始录制前摄像头预览未准备就绪。", ); }; @@ -1133,7 +1123,7 @@ const loadSignedInState = async () => { const requireSignedInState = async () => { const state = await loadSignedInState(); if (!state.auth || !state.bootstrap) { - throw new Error("Sign in to Cap first"); + throw new Error("请先登录 Cap"); } return state as { settings: ExtensionSettings; @@ -1230,7 +1220,7 @@ const startRecording = async (mode: RecordingMode) => { await saveSettings(recordingSettings); } if (mode === "camera" && !recordingSettings.webcam.deviceId) { - throw new Error("Select a camera before recording."); + throw new Error("请先选择摄像头再开始录制。"); } if (isWebcamPreviewEnabled(recordingSettings)) { await saveWebcamPreviewDismissed(false); @@ -1249,7 +1239,7 @@ const startRecording = async (mode: RecordingMode) => { return { ok: false, canceled: true, - error: "Recording canceled", + error: "录制已取消", } satisfies OffscreenResponse; } } @@ -1396,10 +1386,7 @@ const launchWebAuthFlow = (url: string) => (responseUrl) => { if (chrome.runtime.lastError || !responseUrl) { reject( - new Error( - chrome.runtime.lastError?.message ?? - "The sign-in window was closed", - ), + new Error(chrome.runtime.lastError?.message ?? "登录窗口已关闭"), ); return; } @@ -1492,7 +1479,7 @@ const handlePreviewError = async ( settleTabWaiters( previewReadyWaiters, tabId, - new Error("Camera preview did not become ready."), + new Error("摄像头预览未准备就绪。"), ); const fallbackTabId = @@ -1552,7 +1539,7 @@ const handleRequest = async ( const status = error instanceof ApiRequestError ? error.status : null; if (status === null || status >= 500) { throw new Error( - "Could not reach Cap to revoke this sign-in. Check your connection and try again.", + "无法连接 Cap 以撤销本次登录,请检查网络连接后重试。", ); } } @@ -1741,7 +1728,7 @@ const handleRequest = async ( // excluded so a web-accessible page embedded by a hostile site cannot // authorise itself. if (!isWebPageSender(sender)) { - return { ok: false, error: "Unauthorized" }; + return { ok: false, error: "未授权" }; } await registerOverlayToken(message.token); return { ok: true }; @@ -1757,7 +1744,7 @@ const handleRequest = async ( message.sessionId, ); if (!allowed) { - return { ok: false, error: "Camera preview is not authorized." }; + return { ok: false, error: "摄像头预览未获授权。" }; } const response = await sendOffscreen({ target: "offscreen", @@ -1789,7 +1776,7 @@ const handleRequest = async ( tabId === undefined || !(await isCameraPreviewEventAllowed(sender, message.token)) ) { - return { ok: false, error: "Unauthorized" }; + return { ok: false, error: "未授权" }; } chrome.tabs.sendMessage( tabId, @@ -1840,7 +1827,7 @@ const handleRequest = async ( return { ok: true }; } - return { ok: false, error: "Unknown request" }; + return { ok: false, error: "未知请求" }; }; chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { diff --git a/apps/chrome-extension/src/content/confirm-overlay.tsx b/apps/chrome-extension/src/content/confirm-overlay.tsx index 08eb8da28dd..314c21c4cee 100644 --- a/apps/chrome-extension/src/content/confirm-overlay.tsx +++ b/apps/chrome-extension/src/content/confirm-overlay.tsx @@ -14,14 +14,12 @@ const COPY: Record< { title: string; message: string } > = { "no-mic": { - title: "Record without a microphone?", - message: - "No microphone is selected, so this recording won't capture your voice.", + title: "不使用麦克风录制?", + message: "尚未选择麦克风,本次录制将无法采集你的声音。", }, "no-sound": { - title: "No sound from your microphone", - message: - "We're not detecting any audio from the selected microphone. It may be muted or unplugged.", + title: "麦克风没有声音", + message: "未检测到所选麦克风的声音,它可能已静音或未连接。", }, }; @@ -98,7 +96,7 @@ export function ConfirmOverlay() { diff --git a/apps/chrome-extension/src/content/countdown-overlay.tsx b/apps/chrome-extension/src/content/countdown-overlay.tsx index 11cd3bb5180..2bcea6dde06 100644 --- a/apps/chrome-extension/src/content/countdown-overlay.tsx +++ b/apps/chrome-extension/src/content/countdown-overlay.tsx @@ -128,7 +128,7 @@ export function CountdownOverlay() { onPointerDown={(event) => event.stopPropagation()} > - {`Recording starts in ${value}`} + {`将在 ${value} 秒后开始录制`}
- Press Esc to cancel + 按 Esc 取消 ); } diff --git a/apps/chrome-extension/src/content/drawing-overlay.tsx b/apps/chrome-extension/src/content/drawing-overlay.tsx index aac1bae30ae..e5791706d59 100644 --- a/apps/chrome-extension/src/content/drawing-overlay.tsx +++ b/apps/chrome-extension/src/content/drawing-overlay.tsx @@ -10,19 +10,19 @@ import { // A bright, presentation-friendly palette: the warm/cool brights read well on // light pages and white covers dark slides. Red is the default ink. const DRAW_COLORS = [ - { name: "Red", value: "#ef4444" }, - { name: "Yellow", value: "#facc15" }, - { name: "Green", value: "#22c55e" }, - { name: "Blue", value: "#3b82f6" }, - { name: "White", value: "#ffffff" }, + { name: "红色", value: "#ef4444" }, + { name: "黄色", value: "#facc15" }, + { name: "绿色", value: "#22c55e" }, + { name: "蓝色", value: "#3b82f6" }, + { name: "白色", value: "#ffffff" }, ] as const; // Stroke width in CSS pixels paired with the diameter of the dot shown in the // toolbar, so the picker previews roughly what the brush draws. const BRUSH_SIZES = [ - { name: "Small", value: 4, dot: 6 }, - { name: "Medium", value: 9, dot: 10 }, - { name: "Large", value: 16, dot: 15 }, + { name: "细", value: 4, dot: 6 }, + { name: "中", value: 9, dot: 10 }, + { name: "粗", value: 16, dot: 15 }, ] as const; const DEFAULT_COLOR = DRAW_COLORS[0].value; @@ -259,7 +259,7 @@ export function DrawingOverlay({ active, onClose }: DrawingOverlayProps) {
{DRAW_COLORS.map((swatch) => ( @@ -290,7 +290,7 @@ export function DrawingOverlay({ active, onClose }: DrawingOverlayProps) { )} aria-label={brush.name} aria-pressed={size === brush.value} - title={`${brush.name} brush`} + title={`${brush.name}画笔`} onClick={() => setSize(brush.value)} > @@ -322,8 +322,8 @@ export function DrawingOverlay({ active, onClose }: DrawingOverlayProps) {