From 2c330efb8aa99f805dc676bd870bf81fd9ecd2cb Mon Sep 17 00:00:00 2001 From: atani Date: Thu, 5 Mar 2026 07:58:28 +0900 Subject: [PATCH] feat: add Japanese localization with String Catalog (#819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add i18n infrastructure with String Catalog and Japanese translations Introduce String Catalog (.xcstrings) for localization support: - Localizable.xcstrings: 195 UI string entries with en and ja translations - InfoPlist.xcstrings: Info.plist strings (microphone usage, Finder menu items) - project.pbxproj: add xcstrings to build phase and ja to knownRegions * Replace hardcoded UI strings with String(localized:defaultValue:) Migrate all user-facing strings across 11 source files to use String(localized:defaultValue:) API (macOS 13+). Each string references a key in Localizable.xcstrings, with the English text preserved as defaultValue for fallback. Files modified: - KeyboardShortcutSettings: 28 shortcut labels - SocketControlSettings: mode names and descriptions - TabManager: placement labels, color names, close dialogs - BrowserPanel/BrowserPanelView: error pages, context menus, tooltips - UpdateViewModel/UpdatePopoverView/UpdatePill: update UI states - NotificationsPage: notification panel labels - SurfaceSearchOverlay: search bar placeholder and tooltips - AppDelegate: menus, dialogs, command palette items * Fix localization gaps from review feedback Address review comments from CodeRabbit, Greptile, and Cubic Dev AI: - Use interpolated String(localized:) instead of concatenation for version/progress strings in UpdateViewModel - Localize remaining hardcoded strings in AppDelegate: window labels, rename dialog, status menu items, unread notification count - Localize insecure HTTP alert body in BrowserPanel - Add 12 new entries to Localizable.xcstrings with Japanese translations * Fix String(localized:defaultValue:) keys to use StaticString The localized: parameter requires StaticString when defaultValue: is used. Move string interpolation from the key to defaultValue only, and revert maxWidthText to plain strings since they are only used for layout width calculation. * Localize remaining UI strings across all source files Add String(localized:defaultValue:) to all user-facing strings in: - cmuxApp.swift: settings screen, menus, about panel, dialogs (~180 strings) - ContentView.swift: command palette, sidebar context menu, dialogs (~200 strings) - Workspace.swift: rename/move/close tab dialogs, tooltips (~20 strings) - UpdateTitlebarAccessory.swift: titlebar tooltips, notifications popover (~10 strings) - TerminalNotificationStore.swift: notification permission dialog (4 strings) - CmuxWebView.swift: browser context menu items (2 strings) - AppDelegate.swift: CLI install/uninstall alerts (6 strings) Add 418 new entries to Localizable.xcstrings with Japanese translations. Extract sidebar context menu into separate @ViewBuilder to fix Swift type-checker timeout in large body. Fix xcstrings format specifiers for interpolated strings (%lld, %@). Total: 624 localization entries covering the full UI. * Address review feedback: fix missing localizations and terminology - Localize javaScriptDialogTitle URL branch in BrowserPanel - Localize cantReach error message in BrowserPanel - Localize close other tabs dialog message in TabManager - Localize workspace accessibility label in ContentView - Fix unread notification singular/plural (split into two keys) - Fix insecure connection apostrophe inconsistency (unify to U+2019) - Rename socketControl.fullOpen.description to socketControl.allowAll.description - Remove dead code: renameTargetNoun function - Fix terminology inconsistencies in xcstrings: - Unify "Developer Tools" to デベロッパツール - Unify "Jump to Latest Unread" phrasing - Unify "Flash Focused Panel" terminology - Fix dialog.enableNotifications.notNow translation * fix: address remaining PR 819 review feedback * fix: use a single localized key for close-other-tabs * fix: avoid inflection markup in close-other-tabs message * Address review feedback: localize tooltip, fix subtitle concat, unify keys - Localize menubar tooltip unread count (hardcoded English -> localized) - Replace subtitle string concatenation anti-pattern with single localized keys containing interpolation placeholders - Unify workspace fallback key to workspace.displayName.fallback - Remove unused workspace.defaultName key from xcstrings - Add Japanese translations for new tooltip and subtitle keys --- GhosttyTabs.xcodeproj/project.pbxproj | 11 +- Resources/InfoPlist.xcstrings | 57 + Resources/Localizable.xcstrings | 10733 +++++++++++++++++ Sources/AppDelegate.swift | 110 +- Sources/ContentView.swift | 684 +- Sources/Find/SurfaceSearchOverlay.swift | 8 +- Sources/KeyboardShortcutSettings.swift | 60 +- Sources/NotificationsPage.swift | 16 +- Sources/Panels/BrowserPanel.swift | 79 +- Sources/Panels/BrowserPanelView.swift | 18 +- Sources/Panels/CmuxWebView.swift | 6 +- Sources/SocketControlSettings.swift | 22 +- Sources/TabManager.swift | 48 +- Sources/TerminalNotificationStore.swift | 8 +- Sources/Update/UpdatePill.swift | 2 +- Sources/Update/UpdatePopoverView.swift | 54 +- Sources/Update/UpdateTitlebarAccessory.swift | 20 +- Sources/Update/UpdateViewModel.swift | 90 +- Sources/Workspace.swift | 44 +- Sources/cmuxApp.swift | 362 +- 20 files changed, 11643 insertions(+), 789 deletions(-) create mode 100644 Resources/InfoPlist.xcstrings create mode 100644 Resources/Localizable.xcstrings diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index aa0221e6..532ab71b 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -84,6 +84,8 @@ F6000000A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */; }; F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */; }; F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */; }; + DA7A10CA710E000000000003 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000001 /* Localizable.xcstrings */; }; + DA7A10CA710E000000000004 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000002 /* InfoPlist.xcstrings */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -215,7 +217,9 @@ F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateShortcutRoutingTests.swift; sourceTree = ""; }; F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceContentViewVisibilityTests.swift; sourceTree = ""; }; F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketControlPasswordStoreTests.swift; sourceTree = ""; }; - /* End PBXFileReference section */ + DA7A10CA710E000000000001 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + DA7A10CA710E000000000002 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; +/* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ A5001030 /* Frameworks */ = { @@ -261,6 +265,8 @@ A5001100 /* Assets.xcassets in Resources */, 84E00D47E4584162AE53BC8D /* xterm-ghostty in Resources */, A5002000 /* THIRD_PARTY_LICENSES.md in Resources */, + DA7A10CA710E000000000003 /* Localizable.xcstrings in Resources */, + DA7A10CA710E000000000004 /* InfoPlist.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -385,6 +391,8 @@ B2E7294509CC42FE9191870E /* xterm-ghostty */, A5002001 /* THIRD_PARTY_LICENSES.md */, C1ADE00001A1B2C3D4E5F719 /* claude */, + DA7A10CA710E000000000001 /* Localizable.xcstrings */, + DA7A10CA710E000000000002 /* InfoPlist.xcstrings */, ); path = Resources; sourceTree = ""; @@ -534,6 +542,7 @@ knownRegions = ( en, Base, + ja, ); mainGroup = A5001040; packageReferences = ( diff --git a/Resources/InfoPlist.xcstrings b/Resources/InfoPlist.xcstrings new file mode 100644 index 00000000..fa672ecd --- /dev/null +++ b/Resources/InfoPlist.xcstrings @@ -0,0 +1,57 @@ +{ + "sourceLanguage" : "en", + "version" : "1.0", + "strings" : { + "NSMicrophoneUsageDescription" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "A program running within cmux would like to use your microphone." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "cmux 内で実行中のプログラムがマイクの使用を求めています。" + } + } + } + }, + "New $(PRODUCT_NAME) Workspace Here" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New $(PRODUCT_NAME) Workspace Here" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ここに新規 $(PRODUCT_NAME) ワークスペースを作成" + } + } + } + }, + "New $(PRODUCT_NAME) Window Here" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New $(PRODUCT_NAME) Window Here" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ここに新規 $(PRODUCT_NAME) ウインドウを作成" + } + } + } + } + } +} diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings new file mode 100644 index 00000000..9ee38bc3 --- /dev/null +++ b/Resources/Localizable.xcstrings @@ -0,0 +1,10733 @@ +{ + "sourceLanguage": "en", + "version": "1.0", + "strings": { + "about.appName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux" + } + } + } + }, + "about.build": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Build" + } + } + } + }, + "about.commit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Commit" + } + } + } + }, + "about.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A Ghostty-based terminal with vertical tabs\\nand a notification panel for macOS." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Ghosttyベースの縦タブ付きターミナルと\nmacOS用通知パネル。" + } + } + } + }, + "about.docs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Docs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ドキュメント" + } + } + } + }, + "about.github": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + } + } + }, + "about.licenses": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Licenses" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライセンス" + } + } + } + }, + "about.licenses.notFound": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Licenses file not found." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライセンスファイルが見つかりません。" + } + } + } + }, + "about.licenses.windowTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Third-Party Licenses" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サードパーティライセンス" + } + } + } + }, + "about.version": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + } + } + }, + "accessibility.workspacePosition": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%1$@, workspace %2$lld of %3$lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%1$@、ワークスペース %3$lld中%2$lld" + } + } + } + }, + "alert.customColor.apply": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apply" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "適用" + } + } + } + }, + "alert.customColor.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "alert.customColor.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a hex color in the format #RRGGBB." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB形式で16進カラーコードを入力してください。" + } + } + } + }, + "alert.customColor.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom Workspace Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムワークスペースカラー" + } + } + } + }, + "alert.invalidColor.emptyMessage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a hex color in the format #RRGGBB." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "#RRGGBB形式で16進カラーコードを入力してください。" + } + } + } + }, + "alert.invalidColor.invalidMessage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "\"%@\" is not a valid hex color. Use #RRGGBB." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "「%@」は有効な16進カラーではありません。#RRGGBB形式で入力してください。" + } + } + } + }, + "alert.invalidColor.ok": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + } + } + }, + "alert.invalidColor.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Invalid Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "無効なカラー" + } + } + } + }, + "alert.renameWorkspace.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "alert.renameWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a custom name for this workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このワークスペースのカスタム名を入力してください。" + } + } + } + }, + "alert.renameWorkspace.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名" + } + } + } + }, + "alert.renameWorkspace.rename": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名称変更" + } + } + } + }, + "alert.renameWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更" + } + } + } + }, + "appIcon.automatic": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatic" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動" + } + } + } + }, + "appIcon.dark": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダーク" + } + } + } + }, + "appIcon.light": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライト" + } + } + } + }, + "appearance.auto": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動" + } + } + } + }, + "appearance.dark": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダーク" + } + } + } + }, + "appearance.light": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライト" + } + } + } + }, + "appearance.system": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "システム" + } + } + } + }, + "browser.action.newTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "new tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ" + } + } + } + }, + "browser.addressBar.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search or enter URL" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索またはURLを入力" + } + } + } + }, + "browser.addressBarSuggestions": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Address bar suggestions" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アドレスバーの候補" + } + } + } + }, + "browser.alwaysAllowHost": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Always allow this host in cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このホストを cmux で常に許可" + } + } + } + }, + "browser.contextMenu.openLinkInDefaultBrowser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Link in Default Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デフォルトブラウザでリンクを開く" + } + } + } + }, + "browser.contextMenu.openLinkInNewTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Link in New Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブでリンクを開く" + } + } + } + }, + "browser.dialog.pageSays": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This page says:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このページの内容:" + } + } + } + }, + "browser.dialog.pageSaysAt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The page at %@ says:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ページ %@ のメッセージ:" + } + } + } + }, + "browser.downloadInProgress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Download in progress" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中" + } + } + } + }, + "browser.downloading": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading..." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中..." + } + } + } + }, + "browser.error.cantOpen.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Can't open this page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このページを開けません" + } + } + } + }, + "browser.error.cantReach.messageSite": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The site refused to connect. Check that a server is running on this address." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイトに接続できませんでした。このアドレスでサーバーが実行されていることを確認してください。" + } + } + } + }, + "browser.error.cantReach.messageURL": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ refused to connect. Check that a server is running on this address." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@ に接続できませんでした。このアドレスでサーバーが実行されていることを確認してください。" + } + } + } + }, + "browser.error.cantReach.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Can't reach this page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このページに到達できません" + } + } + } + }, + "browser.error.checkNetwork": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check your network connection and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ネットワーク接続を確認してもう一度お試しください。" + } + } + } + }, + "browser.error.frameLoadInterrupted": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Frame load interrupted" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フレームの読み込みが中断されました" + } + } + } + }, + "browser.error.insecure.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ uses plain HTTP, so traffic can be read or modified on the network.\n\nOpen this URL in your default browser, or proceed in cmux." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@ は HTTP 接続を使用しているため、通信内容がネットワーク上で読み取られたり改ざんされる可能性があります。\n\nデフォルトブラウザで開くか、cmux で続行してください。" + } + } + } + }, + "browser.error.insecure.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection isn't secure" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "接続は安全ではありません" + } + } + } + }, + "browser.error.invalidCertificate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The certificate for this site is invalid." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このサイトの証明書が無効です。" + } + } + } + }, + "browser.error.noInternet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No internet connection" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インターネット接続がありません" + } + } + } + }, + "browser.error.reload": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再読み込み" + } + } + } + }, + "browser.goBack": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go Back" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "戻る" + } + } + } + }, + "browser.goForward": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go Forward" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "進む" + } + } + } + }, + "browser.goToURL": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "go to URL" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "URLに移動" + } + } + } + }, + "browser.newTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ" + } + } + } + }, + "browser.openInDefaultBrowser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open in Default Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デフォルトブラウザで開く" + } + } + } + }, + "browser.proceedInCmux": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Proceed in cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux で続行" + } + } + } + }, + "browser.reload": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再読み込み" + } + } + } + }, + "browser.search.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索" + } + } + } + }, + "browser.stop": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "停止" + } + } + } + }, + "browser.switchToTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Switch to tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブに切替" + } + } + } + }, + "browser.toggleDevTools": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デベロッパツールを切替" + } + } + } + }, + "cli.install.adminRequired": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Administrator privileges were required to write to /usr/local/bin." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/binへの書き込みに管理者権限が必要でした。" + } + } + } + }, + "cli.install.symlinkCreated": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Created symlink:\n\n%1$@ -> %2$@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シンボリックリンクを作成しました:\n\n%1$@ -> %2$@" + } + } + } + }, + "cli.installFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't Install cmux CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI をインストールできませんでした" + } + } + } + }, + "cli.installed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Installed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI がインストールされました" + } + } + } + }, + "cli.uninstall.adminRequired": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Administrator privileges were required to modify /usr/local/bin." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "/usr/local/binの変更に管理者権限が必要でした。" + } + } + } + }, + "cli.uninstall.notFound": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No cmux CLI symlink was found at %@." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@にcmux CLIのシンボリックリンクが見つかりませんでした。" + } + } + } + }, + "cli.uninstall.removed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Removed %@." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@を削除しました。" + } + } + } + }, + "cli.uninstallFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't Uninstall cmux CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI をアンインストールできませんでした" + } + } + } + }, + "cli.uninstalled": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI Uninstalled" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux CLI がアンインストールされました" + } + } + } + }, + "command.applyUpdateIfAvailable.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.applyUpdateIfAvailable.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Apply Update (If Available)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを適用(利用可能な場合)" + } + } + } + }, + "command.attemptUpdate.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.attemptUpdate.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Attempt Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを試行" + } + } + } + }, + "command.browserBack.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Back" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "戻る" + } + } + } + }, + "command.browserClearHistory.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ" + } + } + } + }, + "command.browserClearHistory.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Browser History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴をクリア" + } + } + } + }, + "command.browserConsole.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show JavaScript Console" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "JavaScriptコンソールを表示" + } + } + } + }, + "command.browserDuplicateRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザレイアウト" + } + } + } + }, + "command.browserDuplicateRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Duplicate Browser to the Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に複製" + } + } + } + }, + "command.browserFocusAddressBar.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Address Bar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アドレスバーにフォーカス" + } + } + } + }, + "command.browserForward.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Forward" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "進む" + } + } + } + }, + "command.browserOpenDefault.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Page in Default Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のページをデフォルトブラウザで開く" + } + } + } + }, + "command.browserReload.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload Page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ページを再読み込み" + } + } + } + }, + "command.browserSplitDown.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザレイアウト" + } + } + } + }, + "command.browserSplitDown.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "command.browserSplitRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザレイアウト" + } + } + } + }, + "command.browserSplitRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "command.browserToggleDevTools.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デベロッパツールの切り替え" + } + } + } + }, + "command.browserZoomIn.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom In" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "拡大" + } + } + } + }, + "command.browserZoomOut.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom Out" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縮小" + } + } + } + }, + "command.browserZoomReset.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actual Size" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "実際のサイズ" + } + } + } + }, + "command.checkForUpdates.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.checkForUpdates.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認" + } + } + } + }, + "command.clearTabName.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Tab Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名をクリア" + } + } + } + }, + "command.clearWorkspaceName.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Workspace Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名をクリア" + } + } + } + }, + "command.closeTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "command.closeTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを閉じる" + } + } + } + }, + "command.closeWindow.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ" + } + } + } + }, + "command.closeWindow.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウを閉じる" + } + } + } + }, + "command.closeWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "command.closeWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "command.equalizeSplits.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Equalize Splits" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "分割を均等にする" + } + } + } + }, + "command.installCLI.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + } + } + }, + "command.installCLI.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shell Command: Install 'cmux' in PATH" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シェルコマンド: 'cmux'をPATHにインストール" + } + } + } + }, + "command.jumpUnread.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "command.jumpUnread.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "command.markTabRead.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Tab as Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを既読にする" + } + } + } + }, + "command.markTabUnread.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Tab as Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを未読にする" + } + } + } + }, + "command.newBrowserTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "command.newBrowserTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Tab (Browser)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ(ブラウザ)" + } + } + } + }, + "command.newTerminalTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "command.newTerminalTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Tab (Terminal)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規タブ(ターミナル)" + } + } + } + }, + "command.newWindow.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ" + } + } + } + }, + "command.newWindow.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "command.newWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "command.newWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "command.nextTabInPane.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブナビゲーション" + } + } + } + }, + "command.nextTabInPane.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Tab in Pane" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペイン内の次のタブ" + } + } + } + }, + "command.nextWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースナビゲーション" + } + } + } + }, + "command.nextWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のワークスペース" + } + } + } + }, + "command.openFolder.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "command.openFolder.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く…" + } + } + } + }, + "command.openSettings.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.openSettings.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定を開く" + } + } + } + }, + "command.openWorkspacePRLinks.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open All Workspace PR Links" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのPRリンクをすべて開く" + } + } + } + }, + "command.pinTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブをピンで固定" + } + } + } + }, + "command.pinWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをピンで固定" + } + } + } + }, + "command.previousTabInPane.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブナビゲーション" + } + } + } + }, + "command.previousTabInPane.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Tab in Pane" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペイン内の前のタブ" + } + } + } + }, + "command.previousWorkspace.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Navigation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースナビゲーション" + } + } + } + }, + "command.previousWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のワークスペース" + } + } + } + }, + "command.renameTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの名称変更…" + } + } + } + }, + "command.renameWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更…" + } + } + } + }, + "command.reopenClosedBrowserTab.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ" + } + } + } + }, + "command.reopenClosedBrowserTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reopen Closed Browser Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じたブラウザタブを再度開く" + } + } + } + }, + "command.restartSocketListener.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Global" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "グローバル" + } + } + } + }, + "command.restartSocketListener.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart CLI Listener" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CLIリスナーを再起動" + } + } + } + }, + "command.showNotifications.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "command.showNotifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "command.terminalFind.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索…" + } + } + } + }, + "command.terminalFindNext.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Next" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次を検索" + } + } + } + }, + "command.terminalFindPrevious.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Previous" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前を検索" + } + } + } + }, + "command.terminalHideFind.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide Find Bar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索バーを非表示" + } + } + } + }, + "command.terminalSplitBrowserDown.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitBrowserDown.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "command.terminalSplitBrowserRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitBrowserRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "command.terminalSplitDown.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitDown.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "command.terminalSplitRight.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.terminalSplitRight.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + }, + "command.terminalUseSelectionForFind.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use Selection for Find" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択範囲を検索に使用" + } + } + } + }, + "command.toggleFullScreen.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ" + } + } + } + }, + "command.toggleFullScreen.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Full Screen" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルスクリーンの切り替え" + } + } + } + }, + "command.toggleSidebar.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "レイアウト" + } + } + } + }, + "command.toggleSidebar.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの切り替え" + } + } + } + }, + "command.toggleSplitZoom.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルレイアウト" + } + } + } + }, + "command.toggleSplitZoom.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Pane Zoom" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペインズームの切り替え" + } + } + } + }, + "command.triggerFlash.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "表示" + } + } + } + }, + "command.triggerFlash.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Flash Focused Panel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォーカスペインを強調" + } + } + } + }, + "command.uninstallCLI.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CLI" + } + } + } + }, + "command.uninstallCLI.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shell Command: Uninstall 'cmux' from PATH" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シェルコマンド: 'cmux'をPATHからアンインストール" + } + } + } + }, + "command.unpinTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブのピンを外す" + } + } + } + }, + "command.unpinWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのピンを外す" + } + } + } + }, + "command.vscodeServeWebRestart.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart VS Code Inline Server" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "VS Codeインラインサーバーを再起動" + } + } + } + }, + "command.vscodeServeWebStop.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop VS Code Inline Server" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "VS Codeインラインサーバーを停止" + } + } + } + }, + "commandPalette.kind.workspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "commandPalette.rename.clearCustomName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "(clear custom name)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "(カスタム名をクリア)" + } + } + } + }, + "commandPalette.rename.tabConfirmHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Press Enter to apply this tab name, or Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Enterキーでタブ名を適用、Escapeキーでキャンセルします。" + } + } + } + }, + "commandPalette.rename.tabDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a custom tab name." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブのカスタム名を選択してください。" + } + } + } + }, + "commandPalette.rename.tabInputHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a tab name. Press Enter to rename, Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名を入力してください。Enterで名称変更、Escapeでキャンセルします。" + } + } + } + }, + "commandPalette.rename.tabPlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名" + } + } + } + }, + "commandPalette.rename.tabTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの名称変更" + } + } + } + }, + "commandPalette.rename.workspaceConfirmHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Press Enter to apply this workspace name, or Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Enterキーでワークスペース名を適用、Escapeキーでキャンセルします。" + } + } + } + }, + "commandPalette.rename.workspaceDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a custom workspace name." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのカスタム名を選択してください。" + } + } + } + }, + "commandPalette.rename.workspaceInputHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a workspace name. Press Enter to rename, Escape to cancel." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名を入力してください。Enterで名称変更、Escapeでキャンセルします。" + } + } + } + }, + "commandPalette.rename.workspacePlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名" + } + } + } + }, + "commandPalette.rename.workspaceTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更" + } + } + } + }, + "commandPalette.search.commandsEmpty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No commands match your search." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索に一致するコマンドがありません。" + } + } + } + }, + "commandPalette.search.commandsPlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a command" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドを入力" + } + } + } + }, + "commandPalette.search.switcherEmpty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No workspaces match your search." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索に一致するワークスペースがありません。" + } + } + } + }, + "commandPalette.search.switcherPlaceholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを検索" + } + } + } + }, + "commandPalette.subtitle.browserWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ • %@" + } + } + } + }, + "commandPalette.subtitle.tabWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ • %@" + } + } + } + }, + "commandPalette.subtitle.tabFallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "commandPalette.subtitle.terminalWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナル • %@" + } + } + } + }, + "commandPalette.subtitle.workspaceWithName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace • %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース • %@" + } + } + } + }, + "commandPalette.subtitle.workspaceFallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "commandPalette.switcher.windowLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window %lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ %lld" + } + } + } + }, + "commandPalette.switcher.workspaceLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "common.allow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Allow" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "許可" + } + } + } + }, + "common.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "common.close": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じる" + } + } + } + }, + "common.copyDetails": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy Details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細をコピー" + } + } + } + }, + "common.dontSave": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Don't Save" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "保存しない" + } + } + } + }, + "common.installAndRelaunch": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install and Relaunch" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インストールして再起動" + } + } + } + }, + "common.later": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Later" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "後で" + } + } + } + }, + "common.notNow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not Now" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "今はしない" + } + } + } + }, + "common.ok": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + } + } + }, + "common.rename": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名前を変更" + } + } + } + }, + "common.restartLater": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart Later" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "後で再起動" + } + } + } + }, + "common.restartNow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart Now" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "今すぐ再起動" + } + } + } + }, + "common.retry": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Retry" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再試行" + } + } + } + }, + "common.skip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "スキップ" + } + } + } + }, + "contextMenu.chooseCustomColor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose Custom Color…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムカラーを選択…" + } + } + } + }, + "contextMenu.clearColor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カラーをクリア" + } + } + } + }, + "contextMenu.closeOtherWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Other Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "他のワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspacesAbove": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspaces Above" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上のワークスペースを閉じる" + } + } + } + }, + "contextMenu.closeWorkspacesBelow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspaces Below" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下のワークスペースを閉じる" + } + } + } + }, + "contextMenu.markWorkspaceRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspace as Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを既読にする" + } + } + } + }, + "contextMenu.markWorkspaceUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspace as Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを未読にする" + } + } + } + }, + "contextMenu.markWorkspacesRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspaces as Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを既読にする" + } + } + } + }, + "contextMenu.markWorkspacesUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark Workspaces as Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを未読にする" + } + } + } + }, + "contextMenu.moveDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に移動" + } + } + } + }, + "contextMenu.moveToTop": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move to Top" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "一番上に移動" + } + } + } + }, + "contextMenu.moveUp": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Up" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上に移動" + } + } + } + }, + "contextMenu.moveWorkspaceToWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Workspace to Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをウインドウに移動" + } + } + } + }, + "contextMenu.moveWorkspacesToWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Workspaces to Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをウインドウに移動" + } + } + } + }, + "contextMenu.newWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "contextMenu.pinWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをピンで固定" + } + } + } + }, + "contextMenu.pinWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pin Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースをピンで固定" + } + } + } + }, + "contextMenu.removeCustomWorkspaceName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove Custom Workspace Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムワークスペース名を削除" + } + } + } + }, + "contextMenu.renameWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更…" + } + } + } + }, + "contextMenu.unpinWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのピンを外す" + } + } + } + }, + "contextMenu.unpinWorkspaces": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unpin Workspaces" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースのピンを外す" + } + } + } + }, + "contextMenu.workspaceColor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Color" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースカラー" + } + } + } + }, + "dialog.closeLastTabWindow.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the last tab and close the window." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最後のタブを閉じ、ウインドウを閉じます。" + } + } + } + }, + "dialog.closeLastTabWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the last tab and close its workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最後のタブを閉じ、ワークスペースを閉じます。" + } + } + } + }, + "dialog.closeOtherTabs.message.one": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close 1 tab in this pane:\n%@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このペインの 1 個のタブを閉じます:\n%@" + } + } + } + }, + "dialog.closeOtherTabs.message.other": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close %1$lld tabs in this pane:\n%2$@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このペインの %1$lld 個のタブを閉じます:\n%2$@" + } + } + } + }, + "dialog.closeOtherTabs.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close other tabs?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "他のタブを閉じますか?" + } + } + } + }, + "dialog.closeTab.close": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じる" + } + } + } + }, + "dialog.closeTab.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the current tab." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のタブを閉じます。" + } + } + } + }, + "dialog.closeTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close tab?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを閉じますか?" + } + } + } + }, + "dialog.closeWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close the workspace and all of its panels." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースとそのすべてのパネルを閉じます。" + } + } + } + }, + "dialog.closeWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close workspace?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じますか?" + } + } + } + }, + "dialog.dontWarnCmdQ": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Don't warn again for Cmd+Q" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q の警告を表示しない" + } + } + } + }, + "dialog.enableNotifications.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications are disabled for cmux. Enable them in System Settings to see alerts." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxの通知が無効になっています。アラートを表示するにはシステム設定で有効にしてください。" + } + } + } + }, + "dialog.enableNotifications.notNow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not Now" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "今はしない" + } + } + } + }, + "dialog.enableNotifications.openSettings": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定を開く" + } + } + } + }, + "dialog.enableNotifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Notifications for cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxの通知を有効にする" + } + } + } + }, + "dialog.moveFailed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux could not move this tab to the selected destination." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxはこのタブを選択した移動先に移動できませんでした。" + } + } + } + }, + "dialog.moveFailed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Failed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "移動失敗" + } + } + } + }, + "dialog.moveTab.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose a destination for this tab." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このタブの移動先を選択してください。" + } + } + } + }, + "dialog.moveTab.move": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "移動" + } + } + } + }, + "dialog.moveTab.newWorkspaceCurrentWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace in Current Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のウインドウの新規ワークスペース" + } + } + } + }, + "dialog.moveTab.selectedWorkspaceNewWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Selected Workspace in New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択したワークスペースを新規ウインドウに" + } + } + } + }, + "dialog.moveTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの移動" + } + } + } + }, + "dialog.quitCmux.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will close all windows and workspaces." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてのウインドウとワークスペースを閉じます。" + } + } + } + }, + "dialog.quitCmux.quit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "終了" + } + } + } + }, + "dialog.quitCmux.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quit cmux?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux を終了しますか?" + } + } + } + }, + "dialog.renameTab.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a custom name for this tab." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このタブのカスタム名を入力してください。" + } + } + } + }, + "dialog.renameTab.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名" + } + } + } + }, + "dialog.renameTab.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブの名称変更" + } + } + } + }, + "dialog.renameWorkspace.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a custom name for this workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "このワークスペースのカスタム名を入力してください。" + } + } + } + }, + "dialog.renameWorkspace.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名" + } + } + } + }, + "dialog.renameWorkspace.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更" + } + } + } + }, + "error.clipboardFolderPath": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Could not load any folder path from the clipboard." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリップボードからフォルダパスを読み込めませんでした。" + } + } + } + }, + "menu.app.about": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxについて" + } + } + } + }, + "menu.app.checkForUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認…" + } + } + } + }, + "menu.app.ghosttySettings": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ghostty Settings…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Ghostty設定…" + } + } + } + }, + "menu.app.reloadConfiguration": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload Configuration" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "構成を再読み込み" + } + } + } + }, + "menu.app.settings": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定…" + } + } + } + }, + "menu.checkForUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認…" + } + } + } + }, + "menu.currentWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Current Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のウインドウ" + } + } + } + }, + "menu.file.closeOtherTabs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Other Tabs in Pane" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペイン内の他のタブを閉じる" + } + } + } + }, + "menu.file.closeTab": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブを閉じる" + } + } + } + }, + "menu.file.closeWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "menu.file.commandPalette": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレット…" + } + } + } + }, + "menu.file.goToWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go to Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースに移動…" + } + } + } + }, + "menu.file.newWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "menu.file.newWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "menu.file.openFolder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く…" + } + } + } + }, + "menu.file.openFolder.panelPrompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "開く" + } + } + } + }, + "menu.file.openFolder.panelTitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く" + } + } + } + }, + "menu.file.reopenClosedBrowserPanel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reopen Closed Browser Panel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じたブラウザパネルを再度開く" + } + } + } + }, + "menu.find.find": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索…" + } + } + } + }, + "menu.find.findNext": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Next" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次を検索" + } + } + } + }, + "menu.find.findPrevious": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find Previous" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前を検索" + } + } + } + }, + "menu.find.hideFindBar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide Find Bar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索バーを非表示" + } + } + } + }, + "menu.find.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Find" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索" + } + } + } + }, + "menu.find.useSelectionForFind": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use Selection for Find" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "選択範囲を検索に使用" + } + } + } + }, + "menu.notifications.clearAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear All" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてクリア" + } + } + } + }, + "menu.notifications.jumpToUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "menu.notifications.markAllRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark All Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべて既読にする" + } + } + } + }, + "menu.notifications.show": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "menu.notifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "menu.openInAndroidStudio": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Android Studio" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Android Studio で開く" + } + } + } + }, + "menu.openInAntigravity": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Antigravity" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Antigravity で開く" + } + } + } + }, + "menu.openInCursor": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Cursor" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Cursor で開く" + } + } + } + }, + "menu.openInFinder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Finder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Finder で開く" + } + } + } + }, + "menu.openInGhostty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Ghostty" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Ghostty で開く" + } + } + } + }, + "menu.openInITerm2": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in iTerm2" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを iTerm2 で開く" + } + } + } + }, + "menu.openInTerminal": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリをターミナルで開く" + } + } + } + }, + "menu.openInTower": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Tower" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Tower で開く" + } + } + } + }, + "menu.openInVSCode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in VS Code (Inline)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを VS Code で開く(インライン)" + } + } + } + }, + "menu.openInWarp": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Warp" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Warp で開く" + } + } + } + }, + "menu.openInWindsurf": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Windsurf" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Windsurf で開く" + } + } + } + }, + "menu.openInXcode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Xcode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Xcode で開く" + } + } + } + }, + "menu.openInZed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Current Directory in Zed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在のディレクトリを Zed で開く" + } + } + } + }, + "menu.preferences": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preferences…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定…" + } + } + } + }, + "menu.quitCmux": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Quit cmux" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux を終了" + } + } + } + }, + "menu.showNotifications": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "menu.updateLogs.copyFocusLogs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy Focus Logs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォーカスログをコピー" + } + } + } + }, + "menu.updateLogs.copyUpdateLogs": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy Update Logs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートログをコピー" + } + } + } + }, + "menu.updateLogs.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Logs" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートログ" + } + } + } + }, + "menu.view.actualSize": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actual Size" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "実際のサイズ" + } + } + } + }, + "menu.view.back": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Back" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "戻る" + } + } + } + }, + "menu.view.clearBrowserHistory": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Browser History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴をクリア" + } + } + } + }, + "menu.view.forward": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Forward" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "進む" + } + } + } + }, + "menu.view.jumpToUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "menu.view.nextSurface": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のサーフェス" + } + } + } + }, + "menu.view.nextWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のワークスペース" + } + } + } + }, + "menu.view.previousSurface": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のサーフェス" + } + } + } + }, + "menu.view.previousWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のワークスペース" + } + } + } + }, + "menu.view.reloadPage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload Page" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ページを再読み込み" + } + } + } + }, + "menu.view.renameWorkspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースの名称変更…" + } + } + } + }, + "menu.view.showJSConsole": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show JavaScript Console" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "JavaScriptコンソールを表示" + } + } + } + }, + "menu.view.showNotifications": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "menu.view.splitBrowserDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "menu.view.splitBrowserRight": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "menu.view.splitDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "menu.view.splitRight": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + }, + "menu.view.toggleDevTools": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デベロッパツールの切り替え" + } + } + } + }, + "menu.view.toggleSidebar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの切り替え" + } + } + } + }, + "menu.view.workspace": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace %lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース %lld" + } + } + } + }, + "menu.view.zoomIn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom In" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "拡大" + } + } + } + }, + "menu.view.zoomOut": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zoom Out" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縮小" + } + } + } + }, + "menu.windowNumber": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window %lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ %lld" + } + } + } + }, + "notifications.clearAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear All" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてクリア" + } + } + } + }, + "notifications.empty.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Desktop notifications will appear here for quick review." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デスクトップ通知がここに表示されます。" + } + } + } + }, + "notifications.empty.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Desktop notifications will appear here." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デスクトップ通知がここに表示されます。" + } + } + } + }, + "notifications.empty.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No notifications yet" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まだ通知はありません" + } + } + } + }, + "notifications.jumpToLatestUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "notifications.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "panel.displayName.fallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ" + } + } + } + }, + "panel.openFolder.prompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "開く" + } + } + } + }, + "panel.openFolder.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く" + } + } + } + }, + "search.close.help": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close (Esc)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "閉じる (Esc)" + } + } + } + }, + "search.nextMatch.help": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next match (Return)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次の一致 (Return)" + } + } + } + }, + "search.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索" + } + } + } + }, + "search.previousMatch.help": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous match (Shift+Return)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前の一致 (Shift+Return)" + } + } + } + }, + "settings.app.appIcon": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Icon" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリアイコン" + } + } + } + }, + "settings.app.dockBadge": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dock Badge" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Dockバッジ" + } + } + } + }, + "settings.app.dockBadge.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show unread count on app icon (Dock and Cmd+Tab)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリアイコン(DockおよびCmd+Tab)に未読数を表示します。" + } + } + } + }, + "settings.app.newWorkspacePlacement": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace Placement" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペースの配置" + } + } + } + }, + "settings.app.openSidebarPRLinks": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Sidebar PR Links in cmux Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーのPRリンクをcmuxブラウザで開く" + } + } + } + }, + "settings.app.openSidebarPRLinks.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicks open in your default browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリックするとデフォルトブラウザで開きます。" + } + } + } + }, + "settings.app.openSidebarPRLinks.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clicks open inside cmux browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリックするとcmuxブラウザ内で開きます。" + } + } + } + }, + "settings.app.renameSelectsName": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Selects Existing Name" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名称変更時に既存の名前を選択" + } + } + } + }, + "settings.app.renameSelectsName.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette rename keeps the caret at the end." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレットの名称変更ではキャレットが末尾に置かれます。" + } + } + } + }, + "settings.app.renameSelectsName.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette rename starts with all text selected." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コマンドパレットの名称変更ではテキスト全体が選択された状態で始まります。" + } + } + } + }, + "settings.app.reorderOnNotification": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reorder on Notification" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知時に並べ替え" + } + } + } + }, + "settings.app.reorderOnNotification.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move workspaces to the top when they receive a notification. Disable for stable shortcut positions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を受け取ったワークスペースを一番上に移動します。ショートカット位置を固定するには無効にしてください。" + } + } + } + }, + "settings.app.showBranchDirectory": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Branch + Directory in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにブランチ+ディレクトリを表示" + } + } + } + }, + "settings.app.showBranchDirectory.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display the built-in git branch and working-directory row." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "組み込みのgitブランチと作業ディレクトリの行を表示します。" + } + } + } + }, + "settings.app.showLog": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Latest Log in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーに最新ログを表示" + } + } + } + }, + "settings.app.showLog.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display the latest imperative log/status message." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の命令型ログ/ステータスメッセージを表示します。" + } + } + } + }, + "settings.app.showMetadata": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Custom Metadata in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにカスタムメタデータを表示" + } + } + } + }, + "settings.app.showMetadata.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display custom metadata from report_meta/set_status and report_meta_block." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "report_meta/set_statusおよびreport_meta_blockからのカスタムメタデータを表示します。" + } + } + } + }, + "settings.app.showPorts": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Listening Ports in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにリスニングポートを表示" + } + } + } + }, + "settings.app.showPorts.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display detected listening ports for the active workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブなワークスペースで検出されたリスニングポートを表示します。" + } + } + } + }, + "settings.app.showProgress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Progress in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーに進捗を表示" + } + } + } + }, + "settings.app.showProgress.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display the built-in progress bar from set_progress." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "set_progressによる組み込みプログレスバーを表示します。" + } + } + } + }, + "settings.app.showPullRequests": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Pull Requests in Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにプルリクエストを表示" + } + } + } + }, + "settings.app.showPullRequests.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Display review items (PR/MR/etc.) with status, number, and clickable link." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ステータス、番号、クリック可能なリンク付きのレビュー項目(PR/MRなど)を表示します。" + } + } + } + }, + "settings.app.sidebarBranchLayout": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sidebar Branch Layout" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーのブランチレイアウト" + } + } + } + }, + "settings.app.sidebarBranchLayout.inline": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Inline" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インライン" + } + } + } + }, + "settings.app.sidebarBranchLayout.subtitleInline": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Inline: all branches share one line." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インライン: すべてのブランチが1行に表示されます。" + } + } + } + }, + "settings.app.sidebarBranchLayout.subtitleVertical": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vertical: each branch appears on its own line." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縦: 各ブランチがそれぞれの行に表示されます。" + } + } + } + }, + "settings.app.sidebarBranchLayout.vertical": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vertical" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "縦" + } + } + } + }, + "settings.app.telemetry": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send anonymous telemetry" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "匿名のテレメトリを送信" + } + } + } + }, + "settings.app.telemetry.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Share anonymized crash and usage data to help improve cmux." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmuxの改善に役立てるため、匿名化されたクラッシュおよび使用状況データを共有します。" + } + } + } + }, + "settings.app.telemetry.subtitleChanged": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change takes effect on next launch." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "変更は次回起動時に反映されます。" + } + } + } + }, + "settings.app.theme": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "テーマ" + } + } + } + }, + "settings.app.warnBeforeQuit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Warn Before Quit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "終了前に警告" + } + } + } + }, + "settings.app.warnBeforeQuit.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Q quits immediately without confirmation." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Qで確認なしにすぐ終了します。" + } + } + } + }, + "settings.app.warnBeforeQuit.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show a confirmation before quitting with Cmd+Q." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd+Qで終了する前に確認を表示します。" + } + } + } + }, + "settings.automation.claudeCode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Claude Code Integration" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Claude Code連携" + } + } + } + }, + "settings.automation.claudeCode.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When enabled, cmux wraps the claude command to inject session tracking and notification hooks. Disable if you prefer to manage Claude Code hooks yourself." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "有効にすると、cmuxはclaudeコマンドをラップしてセッション追跡と通知フックを挿入します。Claude Codeのフックを自分で管理する場合は無効にしてください。" + } + } + } + }, + "settings.automation.claudeCode.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Claude Code runs without cmux integration." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Claude Codeはcmux連携なしで実行されます。" + } + } + } + }, + "settings.automation.claudeCode.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sidebar shows Claude session status and notifications." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーにClaudeセッションのステータスと通知が表示されます。" + } + } + } + }, + "settings.automation.openAccess.dialog.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "settings.automation.openAccess.dialog.confirm": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Full Open Access" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルオープンアクセスを有効にする" + } + } + } + }, + "settings.automation.openAccess.dialog.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This disables ancestry and password checks and opens the socket to all local users. Only enable when you understand the risk." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "これにより、祖先プロセスチェックとパスワードチェックが無効になり、すべてのローカルユーザーにソケットが公開されます。リスクを理解した上で有効にしてください。" + } + } + } + }, + "settings.automation.openAccess.dialog.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable full open access?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルオープンアクセスを有効にしますか?" + } + } + } + }, + "settings.automation.openAccessWarning": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Warning: Full open access makes the control socket world-readable/writable on this Mac and disables auth checks. Use only for local debugging." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "警告: フルオープンアクセスにすると、このMac上の制御ソケットが誰でも読み書き可能になり、認証チェックが無効になります。ローカルデバッグ用途にのみ使用してください。" + } + } + } + }, + "settings.automation.port.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Each workspace gets CMUX_PORT and CMUX_PORT_END env vars with a dedicated port range. New terminals inherit these values." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "各ワークスペースにはCMUX_PORTとCMUX_PORT_END環境変数で専用のポート範囲が割り当てられます。新しいターミナルはこれらの値を継承します。" + } + } + } + }, + "settings.automation.portBase": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Port Base" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポートベース" + } + } + } + }, + "settings.automation.portBase.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starting port for CMUX_PORT env var." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CMUX_PORT環境変数の開始ポート。" + } + } + } + }, + "settings.automation.portRange": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Port Range Size" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポート範囲サイズ" + } + } + } + }, + "settings.automation.portRange.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Number of ports per workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースあたりのポート数。" + } + } + } + }, + "settings.automation.socketMode": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Socket Control Mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソケット制御モード" + } + } + } + }, + "settings.automation.socketMode.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Controls access to the local Unix socket for programmatic control. Choose a mode that matches your threat model." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "プログラムによる制御のためのローカルUnix socketへのアクセスを制御します。脅威モデルに合ったモードを選択してください。" + } + } + } + }, + "settings.automation.socketOverrides.note": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Overrides: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE, and CMUX_SOCKET_PATH (set CMUX_ALLOW_SOCKET_OVERRIDE=1 for stable/nightly builds)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オーバーライド: CMUX_SOCKET_ENABLE、CMUX_SOCKET_MODE、CMUX_SOCKET_PATH(stable/nightlyビルドではCMUX_ALLOW_SOCKET_OVERRIDE=1を設定)。" + } + } + } + }, + "settings.automation.socketPassword": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Socket Password" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソケットパスワード" + } + } + } + }, + "settings.automation.socketPassword.change": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "変更" + } + } + } + }, + "settings.automation.socketPassword.clear": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クリア" + } + } + } + }, + "settings.automation.socketPassword.clearFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to clear password (%@)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードのクリアに失敗しました(%@)。" + } + } + } + }, + "settings.automation.socketPassword.cleared": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password cleared." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードをクリアしました。" + } + } + } + }, + "settings.automation.socketPassword.enterFirst": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a password first." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "まずパスワードを入力してください。" + } + } + } + }, + "settings.automation.socketPassword.placeholder": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワード" + } + } + } + }, + "settings.automation.socketPassword.saveFailed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to save password (%@)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードの保存に失敗しました(%@)。" + } + } + } + }, + "settings.automation.socketPassword.saved": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password saved." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードを保存しました。" + } + } + } + }, + "settings.automation.socketPassword.set": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Set" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定" + } + } + } + }, + "settings.automation.socketPassword.subtitleSet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stored in Application Support." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Application Supportに保存済み。" + } + } + } + }, + "settings.automation.socketPassword.subtitleUnset": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No password set. External clients will be blocked until one is configured." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードが設定されていません。設定するまで外部クライアントはブロックされます。" + } + } + } + }, + "settings.blendMode.behindWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Behind Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウの背面" + } + } + } + }, + "settings.blendMode.withinWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Within Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ内" + } + } + } + }, + "settings.browser.externalPatterns": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "URLs to Always Open Externally" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "常に外部で開くURL" + } + } + } + }, + "settings.browser.externalPatterns.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to terminal link clicks and intercepted `open https://...` calls. One rule per line. Plain text matches any URL substring, or prefix with `re:` for regex (for example: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルのリンククリックおよびインターセプトされた`open https://...`呼び出しに適用されます。1行に1ルール。プレーンテキストはURL部分文字列に一致し、`re:`プレフィックスで正規表現を使用できます(例: openai.com/usage, re:^https?://[^/]*\\\\.example\\\\.com/(billing|usage))。" + } + } + } + }, + "settings.browser.history": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browsing History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴" + } + } + } + }, + "settings.browser.history.clearButton": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear History…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "履歴をクリア…" + } + } + } + }, + "settings.browser.history.clearDialog.cancel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キャンセル" + } + } + } + }, + "settings.browser.history.clearDialog.confirm": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear History" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "履歴をクリア" + } + } + } + }, + "settings.browser.history.clearDialog.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This removes visited-page suggestions from the browser omnibar." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザのオムニバーから訪問済みページの候補が削除されます。" + } + } + } + }, + "settings.browser.history.clearDialog.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear browser history?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ履歴をクリアしますか?" + } + } + } + }, + "settings.browser.history.subtitleEmpty": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No saved pages yet." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "保存済みのページはまだありません。" + } + } + } + }, + "settings.browser.history.subtitleMany": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld saved pages appear in omnibar suggestions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%lld件の保存済みページがオムニバーの候補に表示されます。" + } + } + } + }, + "settings.browser.history.subtitleOne": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "1 saved page appears in omnibar suggestions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "1件の保存済みページがオムニバーの候補に表示されます。" + } + } + } + }, + "settings.browser.hostWhitelist": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hosts to Open in Embedded Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "内蔵ブラウザで開くホスト" + } + } + } + }, + "settings.browser.hostWhitelist.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to terminal link clicks and intercepted `open https://...` calls. Only these hosts open in cmux. Others open in your default browser. One host or wildcard per line (for example: example.com, *.internal.example). Leave empty to open all hosts in cmux." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルのリンククリックおよびインターセプトされた`open https://...`呼び出しに適用されます。これらのホストのみcmuxで開きます。その他はデフォルトブラウザで開きます。1行に1つのホストまたはワイルドカード(例: example.com, *.internal.example)。空欄にするとすべてのホストをcmuxで開きます。" + } + } + } + }, + "settings.browser.httpAllowlist": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "HTTP Hosts Allowed in Embedded Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "内蔵ブラウザで許可するHTTPホスト" + } + } + } + }, + "settings.browser.httpAllowlist.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Controls which HTTP (non-HTTPS) hosts can open in cmux without a warning prompt. Defaults include localhost, 127.0.0.1, ::1, 0.0.0.0, and *.localtest.me." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "警告プロンプトなしでcmuxで開けるHTTP(非HTTPS)ホストを制御します。デフォルトにはlocalhost、127.0.0.1、::1、0.0.0.0、*.localtest.meが含まれます。" + } + } + } + }, + "settings.browser.httpAllowlist.hint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "1行に1つのホストまたはワイルドカード(例: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me)。" + } + } + } + }, + "settings.browser.httpAllowlist.save": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Save" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "保存" + } + } + } + }, + "settings.browser.interceptOpen": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Intercept open http(s) in Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルでopen http(s)をインターセプト" + } + } + } + }, + "settings.browser.interceptOpen.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, `open https://...` and `open http://...` always use your default browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オフの場合、`open https://...`および`open http://...`は常にデフォルトブラウザを使用します。" + } + } + } + }, + "settings.browser.openTerminalLinks": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Terminal Links in cmux Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルのリンクをcmuxブラウザで開く" + } + } + } + }, + "settings.browser.openTerminalLinks.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, links clicked in terminal output open in your default browser." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オフの場合、ターミナル出力のリンクはデフォルトブラウザで開きます。" + } + } + } + }, + "settings.browser.searchEngine": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Default Search Engine" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "デフォルト検索エンジン" + } + } + } + }, + "settings.browser.searchEngine.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Used by the browser address bar when input is not a URL." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザのアドレスバーで入力がURLでない場合に使用されます。" + } + } + } + }, + "settings.browser.searchSuggestions": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Search Suggestions" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "検索候補を表示" + } + } + } + }, + "settings.browser.theme": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser Theme" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザテーマ" + } + } + } + }, + "settings.browser.theme.subtitleForced": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ forces that color scheme for compatible pages." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%@は対応ページにそのカラースキームを強制します。" + } + } + } + }, + "settings.browser.theme.subtitleSystem": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System follows app and macOS appearance." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "システムはアプリとmacOSの外観に従います。" + } + } + } + }, + "settings.material.contentBackground": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Content Background" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "コンテンツ背景" + } + } + } + }, + "settings.material.fullScreenUI": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Full Screen UI" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フルスクリーンUI" + } + } + } + }, + "settings.material.headerView": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Header View" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ヘッダビュー" + } + } + } + }, + "settings.material.hudWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "HUD Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "HUDウインドウ" + } + } + } + }, + "settings.material.liquidGlass": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass (macOS 26+)" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Liquid Glass(macOS 26以降)" + } + } + } + }, + "settings.material.menu": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Menu" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "メニュー" + } + } + } + }, + "settings.material.none": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "None" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "なし" + } + } + } + }, + "settings.material.popover": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Popover" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポップオーバー" + } + } + } + }, + "settings.material.sheet": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sheet" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "シート" + } + } + } + }, + "settings.material.sidebar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバー" + } + } + } + }, + "settings.material.toolTip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tool Tip" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ツールチップ" + } + } + } + }, + "settings.material.underWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ下" + } + } + } + }, + "settings.material.windowBackground": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window Background" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ背景" + } + } + } + }, + "settings.preset.hudGlass": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "HUD Glass" + } + } + } + }, + "settings.preset.nativeSidebar": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Native Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ネイティブサイドバー" + } + } + } + }, + "settings.preset.popoverGlass": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Popover Glass" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ポップオーバーGlass" + } + } + } + }, + "settings.preset.raycastGray": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Raycast Gray" + } + } + } + }, + "settings.preset.softBlur": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Soft Blur" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソフトブラー" + } + } + } + }, + "settings.preset.underWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Under Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウ下" + } + } + } + }, + "settings.reset.resetAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset All Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべての設定をリセット" + } + } + } + }, + "settings.section.app": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリ" + } + } + } + }, + "settings.section.automation": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オートメーション" + } + } + } + }, + "settings.section.browser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ" + } + } + } + }, + "settings.section.keyboardShortcuts": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keyboard Shortcuts" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "キーボードショートカット" + } + } + } + }, + "settings.section.reset": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リセット" + } + } + } + }, + "settings.section.workspaceColors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Colors" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースカラー" + } + } + } + }, + "settings.shortcuts.recordHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click a shortcut value to record a new shortcut." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ショートカット値をクリックして新しいショートカットを記録します。" + } + } + } + }, + "settings.shortcuts.showHints": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Cmd/Ctrl-Hold Shortcut Hints" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd/Ctrl長押しのショートカットヒントを表示" + } + } + } + }, + "settings.shortcuts.showHints.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Holding Cmd or Ctrl keeps shortcut hint pills hidden." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "CmdまたはCtrlを長押ししてもショートカットヒントピルは非表示のままです。" + } + } + } + }, + "settings.shortcuts.showHints.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Cmd(サイドバー/タイトルバー)またはCtrl/Cmd(ペインタブ)を長押しするとショートカットヒントピルが表示されます。" + } + } + } + }, + "settings.state.active": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Active" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブ" + } + } + } + }, + "settings.state.followWindow": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Follow Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウに追従" + } + } + } + }, + "settings.state.inactive": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Inactive" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "非アクティブ" + } + } + } + }, + "settings.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "設定" + } + } + } + }, + "settings.workspaceColors.base": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Base: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ベース: %@" + } + } + } + }, + "settings.workspaceColors.customColors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom Colors" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムカラー" + } + } + } + }, + "settings.workspaceColors.indicator": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace Color Indicator" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースカラーインジケーター" + } + } + } + }, + "settings.workspaceColors.noCustomColors": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom colors: none yet. Use \"Choose Custom Color...\" from a workspace context menu." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "カスタムカラー: まだありません。ワークスペースのコンテキストメニューから「カスタムカラーを選択…」を使用してください。" + } + } + } + }, + "settings.workspaceColors.paletteNote": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Customize the workspace color palette used by Sidebar > Workspace Color. \"Choose Custom Color...\" entries are persisted below." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバー > ワークスペースカラーで使用するカラーパレットをカスタマイズできます。「カスタムカラーを選択…」のエントリは以下に保存されます。" + } + } + } + }, + "settings.workspaceColors.remove": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "削除" + } + } + } + }, + "settings.workspaceColors.resetPalette": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset Palette" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パレットをリセット" + } + } + } + }, + "settings.workspaceColors.resetPalette.button": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リセット" + } + } + } + }, + "settings.workspaceColors.resetPalette.subtitle": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore built-in defaults and clear all custom colors." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "組み込みのデフォルトに戻し、すべてのカスタムカラーをクリアします。" + } + } + } + }, + "shortcut.closeWindow.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ウインドウを閉じる" + } + } + } + }, + "shortcut.closeWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "shortcut.flashFocusedPanel.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Flash Focused Panel" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォーカスペインを強調" + } + } + } + }, + "shortcut.focusPaneDown.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下のペインにフォーカス" + } + } + } + }, + "shortcut.focusPaneLeft.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Left" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "左のペインにフォーカス" + } + } + } + }, + "shortcut.focusPaneRight.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右のペインにフォーカス" + } + } + } + }, + "shortcut.focusPaneUp.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Focus Pane Up" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上のペインにフォーカス" + } + } + } + }, + "shortcut.jumpToUnread.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "shortcut.newSurface.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規サーフェス" + } + } + } + }, + "shortcut.newWindow.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Window" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ウインドウ" + } + } + } + }, + "shortcut.newWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "shortcut.nextSurface.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のサーフェス" + } + } + } + }, + "shortcut.nextWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Next Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "次のワークスペース" + } + } + } + }, + "shortcut.openBrowser.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを開く" + } + } + } + }, + "shortcut.openFolder.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Folder" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "フォルダを開く" + } + } + } + }, + "shortcut.pressShortcut.prompt": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Press shortcut…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ショートカットを入力…" + } + } + } + }, + "shortcut.previousSurface.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Surface" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のサーフェス" + } + } + } + }, + "shortcut.previousWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "前のワークスペース" + } + } + } + }, + "shortcut.renameTab.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "タブ名を変更" + } + } + } + }, + "shortcut.renameWorkspace.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Rename Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース名を変更" + } + } + } + }, + "shortcut.showBrowserJSConsole.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Browser JavaScript Console" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザ JavaScript コンソールを表示" + } + } + } + }, + "shortcut.showNotifications.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "shortcut.splitBrowserDown.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを下に分割" + } + } + } + }, + "shortcut.splitBrowserRight.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Browser Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザを右に分割" + } + } + } + }, + "shortcut.splitDown.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "shortcut.splitRight.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + }, + "shortcut.toggleBrowserDevTools.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Browser Developer Tools" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ブラウザデベロッパツールを切替" + } + } + } + }, + "shortcut.togglePaneZoom.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Pane Zoom" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ペインズームを切替" + } + } + } + }, + "shortcut.toggleSidebar.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーを切替" + } + } + } + }, + "shortcut.toggleTerminalCopyMode.label": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Terminal Copy Mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ターミナルコピーモードを切替" + } + } + } + }, + "sidebar.closeWorkspace.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じる" + } + } + } + }, + "sidebar.folderIcon.dragHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Drag to open in Finder or another app" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ドラッグしてFinderまたは他のアプリで開く" + } + } + } + }, + "sidebar.indicator.leftRail": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Left Rail" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "左レール" + } + } + } + }, + "sidebar.indicator.solidFill": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Solid Fill" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソリッドフィル" + } + } + } + }, + "sidebar.metadata.showLess": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show less" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "表示を減らす" + } + } + } + }, + "sidebar.metadata.showLessDetails": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show less details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細を折りたたむ" + } + } + } + }, + "sidebar.metadata.showMore": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show more" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "さらに表示" + } + } + } + }, + "sidebar.metadata.showMoreDetails": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show more details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細を展開" + } + } + } + }, + "sidebar.pathMenu.macintoshHD": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "Macintosh HD" + } + } + } + }, + "sidebar.pullRequest.openTooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open %1$@ #%2$lld" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "%1$@ #%2$lldを開く" + } + } + } + }, + "sidebar.pullRequest.statusClosed": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "closed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "クローズ" + } + } + } + }, + "sidebar.pullRequest.statusMerged": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "merged" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "マージ済み" + } + } + } + }, + "sidebar.pullRequest.statusOpen": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "open" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オープン" + } + } + } + }, + "sidebar.workspace.accessibilityHint": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activate to focus this workspace. Drag to reorder, or use Move Up and Move Down actions." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブにしてこのワークスペースにフォーカスします。ドラッグで並べ替え、または「上に移動」「下に移動」アクションを使用します。" + } + } + } + }, + "sidebar.workspace.moveDownAction": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に移動" + } + } + } + }, + "sidebar.workspace.moveUpAction": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move Up" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "上に移動" + } + } + } + }, + "socketControl.allowAll.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Allow any local process and user to connect with no auth. Unsafe." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "認証なしで任意のローカルプロセスおよびユーザーの接続を許可します。安全ではありません。" + } + } + } + }, + "socketControl.allowAll.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Full open access" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "完全オープンアクセス" + } + } + } + }, + "socketControl.automation.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Allow external local automation clients from this macOS user (no ancestry check)." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "この macOS ユーザーからの外部ローカル自動化クライアントを許可します(プロセス系譜チェックなし)。" + } + } + } + }, + "socketControl.automation.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automation mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動化モード" + } + } + } + }, + "socketControl.cmuxOnly.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Only processes started inside cmux terminals can send commands." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux ターミナル内で起動したプロセスのみがコマンドを送信できます。" + } + } + } + }, + "socketControl.cmuxOnly.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux processes only" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux プロセスのみ" + } + } + } + }, + "socketControl.error.passwordFilePath": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unable to resolve socket password file path." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ソケットパスワードファイルのパスを解決できません。" + } + } + } + }, + "socketControl.off.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disable the local control socket." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ローカルコントロールソケットを無効にします。" + } + } + } + }, + "socketControl.off.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "オフ" + } + } + } + }, + "socketControl.password.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Require socket authentication with a password stored in a local file." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ローカルファイルに保存されたパスワードでソケット認証を要求します。" + } + } + } + }, + "socketControl.password.name": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Password mode" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "パスワードモード" + } + } + } + }, + "statusMenu.clearAll": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear All" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべてクリア" + } + } + } + }, + "statusMenu.jumpToLatestUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Jump to Latest Unread" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新の未読にジャンプ" + } + } + } + }, + "statusMenu.markAllRead": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark All Read" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すべて既読にする" + } + } + } + }, + "statusMenu.noUnread": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No unread notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読の通知はありません" + } + } + } + }, + "statusMenu.showNotifications": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "statusMenu.unreadCount.one": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "1 unread notification" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 1件" + } + } + } + }, + "statusMenu.unreadCount.other": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unread notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 %lld件" + } + } + } + }, + "statusMenu.tooltip.unread.one": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "1 unread notification" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 1件" + } + } + } + }, + "statusMenu.tooltip.unread.other": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unread notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "未読通知 %lld件" + } + } + } + }, + "tab.untitled": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Untitled Tab" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "名称未設定タブ" + } + } + } + }, + "theme.dark": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダーク" + } + } + } + }, + "theme.light": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ライト" + } + } + } + }, + "theme.system": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "システム" + } + } + } + }, + "titlebar.newWorkspace.accessibilityLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "titlebar.newWorkspace.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ワークスペース" + } + } + } + }, + "titlebar.notifications.accessibilityLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知" + } + } + } + }, + "titlebar.notifications.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show notifications" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "通知を表示" + } + } + } + }, + "titlebar.sidebar.accessibilityLabel": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの切り替え" + } + } + } + }, + "titlebar.sidebar.tooltip": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show or hide the sidebar" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイドバーの表示/非表示" + } + } + } + }, + "update.available.short": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Available" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートがあります" + } + } + } + }, + "update.available.withVersion": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Available: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートあり: %@" + } + } + } + }, + "update.checking": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checking for Updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認中…" + } + } + } + }, + "update.configureAutoUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Configure automatic update preferences" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動アップデートの設定" + } + } + } + }, + "update.downloadAndInstall": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Download and install the latest version" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新バージョンをダウンロードしてインストール" + } + } + } + }, + "update.downloading.progress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中: %@" + } + } + } + }, + "update.downloading.status": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ダウンロード中…" + } + } + } + }, + "update.downloadingPackage": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading the update package" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートパッケージをダウンロード中" + } + } + } + }, + "update.error.appLocation.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Location Issue" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アプリの場所の問題" + } + } + } + }, + "update.error.connectionLost.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The network connection was lost while checking for updates. Try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートの確認中にネットワーク接続が切れました。もう一度お試しください。" + } + } + } + }, + "update.error.connectionLost.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection Lost" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "接続が切れました" + } + } + } + }, + "update.error.downloadFailed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't Download Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをダウンロードできませんでした" + } + } + } + }, + "update.error.failed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Failed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートに失敗しました" + } + } + } + }, + "update.error.feedDownload.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux couldn't download the update feed. Check your connection and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はアップデートフィードをダウンロードできませんでした。接続を確認してもう一度お試しください。" + } + } + } + }, + "update.error.feedError.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Feed Error" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードエラー" + } + } + } + }, + "update.error.feedRead.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update feed could not be read. Please try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードを読み取れませんでした。後でもう一度お試しください。" + } + } + } + }, + "update.error.insecureFeed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update feed is insecure. Please contact support." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードが安全ではありません。サポートにお問い合わせください。" + } + } + } + }, + "update.error.insecureFeed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Insecure Update Feed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "安全でないアップデートフィード" + } + } + } + }, + "update.error.invalidFeed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update feed URL is invalid. Please contact support." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートフィードのURLが無効です。サポートにお問い合わせください。" + } + } + } + }, + "update.error.invalidFeed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Invalid Update Feed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "無効なアップデートフィード" + } + } + } + }, + "update.error.noInternet.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux can't reach the update server. Check your internet connection and try again." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はアップデートサーバーに接続できません。インターネット接続を確認してもう一度お試しください。" + } + } + } + }, + "update.error.noInternet.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Internet Connection" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インターネット接続がありません" + } + } + } + }, + "update.error.permissionError.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Move cmux into Applications and relaunch to enable updates." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを有効にするには、cmux を「アプリケーション」に移動して再起動してください。" + } + } + } + }, + "update.error.permissionError.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updater Permission Error" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデーター権限エラー" + } + } + } + }, + "update.error.secureConnectionFailed.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A secure connection to the update server couldn't be established. Try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートサーバーへのセキュア接続を確立できませんでした。後でもう一度お試しください。" + } + } + } + }, + "update.error.secureConnectionFailed.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Secure Connection Failed" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "セキュア接続に失敗しました" + } + } + } + }, + "update.error.serverNotFound.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update server can't be found. Check your connection or try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートサーバーが見つかりません。接続を確認するか、後でもう一度お試しください。" + } + } + } + }, + "update.error.serverNotFound.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Server Not Found" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サーバーが見つかりません" + } + } + } + }, + "update.error.serverUnreachable.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux couldn't connect to the update server. Check your connection or try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はアップデートサーバーに接続できませんでした。接続を確認するか、後でもう一度お試しください。" + } + } + } + }, + "update.error.serverUnreachable.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Server Unreachable" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サーバーに接続できません" + } + } + } + }, + "update.error.signatureError.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update's signature could not be verified. Please try again later." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートの署名を検証できませんでした。後でもう一度お試しください。" + } + } + } + }, + "update.error.signatureError.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Signature Error" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデート署名エラー" + } + } + } + }, + "update.error.timedOut.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update server took too long to respond. Try again in a moment." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートサーバーの応答に時間がかかりすぎました。しばらくしてからもう一度お試しください。" + } + } + } + }, + "update.error.timedOut.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Timed Out" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートがタイムアウトしました" + } + } + } + }, + "update.extracting.progress": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preparing: %@" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "準備中: %@" + } + } + } + }, + "update.installAndRelaunch": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install Update and Relaunch" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをインストールして再起動" + } + } + } + }, + "update.installing.status": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "インストール中…" + } + } + } + }, + "update.installingAndRestarting": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing update and preparing to restart" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをインストールして再起動を準備中" + } + } + } + }, + "update.noUpdates.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You are running the latest version" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "最新バージョンを使用しています" + } + } + } + }, + "update.noUpdates.title": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Updates Available" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートはありません" + } + } + } + }, + "update.permissionRequest.text": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable Automatic Updates?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動アップデートを有効にしますか?" + } + } + } + }, + "update.pleaseWait": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Please wait while we check for available updates" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認しています。しばらくお待ちください" + } + } + } + }, + "update.popover.autoUpdatesDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cmux can automatically check for updates in the background." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux はバックグラウンドで自動的にアップデートを確認できます。" + } + } + } + }, + "update.popover.checking": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checking for updates…" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを確認中…" + } + } + } + }, + "update.popover.details": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Details" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "詳細" + } + } + } + }, + "update.popover.downloadingUpdate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloading Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートをダウンロード中" + } + } + } + }, + "update.popover.enableAutoUpdates": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enable automatic updates?" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "自動アップデートを有効にしますか?" + } + } + } + }, + "update.popover.noUpdatesFound": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No Updates Found" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートは見つかりませんでした" + } + } + } + }, + "update.popover.noUpdatesFound.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "You're already running the latest version." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "すでに最新バージョンを使用しています。" + } + } + } + }, + "update.popover.preparingUpdate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Preparing Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを準備中" + } + } + } + }, + "update.popover.released": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Released:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リリース日:" + } + } + } + }, + "update.popover.restartRequired": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart Required" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "再起動が必要です" + } + } + } + }, + "update.popover.restartRequired.message": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The update is ready. Please restart the application to complete the installation." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートの準備ができました。インストールを完了するにはアプリケーションを再起動してください。" + } + } + } + }, + "update.popover.size": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Size:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "サイズ:" + } + } + } + }, + "update.popover.updateAvailable": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update Available" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートがあります" + } + } + } + }, + "update.popover.version": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Version:" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "バージョン:" + } + } + } + }, + "update.preparingUpdate": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Extracting and preparing the update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを展開して準備中" + } + } + } + }, + "update.restartToComplete": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart to Complete Update" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アップデートを完了するには再起動してください" + } + } + } + }, + "update.viewGitHubCommit": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View GitHub Commit" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "GitHub コミットを表示" + } + } + } + }, + "update.viewReleaseNotes": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View Release Notes" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "リリースノートを表示" + } + } + } + }, + "workspace.displayName.fallback": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Workspace" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペース" + } + } + } + }, + "workspace.placement.afterCurrent": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "After current" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "現在の後" + } + } + } + }, + "workspace.placement.afterCurrent.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Insert new workspaces directly after the active workspace." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アクティブなワークスペースの直後に新しいワークスペースを挿入します。" + } + } + } + }, + "workspace.placement.end": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "End" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "末尾" + } + } + } + }, + "workspace.placement.end.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Append new workspaces to the bottom of the list." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新しいワークスペースをリストの末尾に追加します。" + } + } + } + }, + "workspace.placement.top": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Top" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "先頭" + } + } + } + }, + "workspace.placement.top.description": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Insert new workspaces at the top of the list." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新しいワークスペースをリストの先頭に挿入します。" + } + } + } + }, + "workspace.tooltip.newBrowser": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Browser" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ブラウザ" + } + } + } + }, + "workspace.tooltip.newTerminal": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Terminal" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "新規ターミナル" + } + } + } + }, + "workspace.tooltip.splitDown": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Down" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "下に分割" + } + } + } + }, + "workspace.tooltip.splitRight": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Split Right" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "右に分割" + } + } + } + } + } +} \ No newline at end of file diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 254381a6..a6f996fb 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -80,31 +80,31 @@ enum TerminalDirectoryOpenTarget: String, CaseIterable { var commandPaletteTitle: String { switch self { case .androidStudio: - return "Open Current Directory in Android Studio" + return String(localized: "menu.openInAndroidStudio", defaultValue: "Open Current Directory in Android Studio") case .antigravity: - return "Open Current Directory in Antigravity" + return String(localized: "menu.openInAntigravity", defaultValue: "Open Current Directory in Antigravity") case .cursor: - return "Open Current Directory in Cursor" + return String(localized: "menu.openInCursor", defaultValue: "Open Current Directory in Cursor") case .finder: - return "Open Current Directory in Finder" + return String(localized: "menu.openInFinder", defaultValue: "Open Current Directory in Finder") case .ghostty: - return "Open Current Directory in Ghostty" + return String(localized: "menu.openInGhostty", defaultValue: "Open Current Directory in Ghostty") case .iterm2: - return "Open Current Directory in iTerm2" + return String(localized: "menu.openInITerm2", defaultValue: "Open Current Directory in iTerm2") case .terminal: - return "Open Current Directory in Terminal" + return String(localized: "menu.openInTerminal", defaultValue: "Open Current Directory in Terminal") case .tower: - return "Open Current Directory in Tower" + return String(localized: "menu.openInTower", defaultValue: "Open Current Directory in Tower") case .vscode: - return "Open Current Directory in VS Code (Inline)" + return String(localized: "menu.openInVSCode", defaultValue: "Open Current Directory in VS Code (Inline)") case .warp: - return "Open Current Directory in Warp" + return String(localized: "menu.openInWarp", defaultValue: "Open Current Directory in Warp") case .windsurf: - return "Open Current Directory in Windsurf" + return String(localized: "menu.openInWindsurf", defaultValue: "Open Current Directory in Windsurf") case .xcode: - return "Open Current Directory in Xcode" + return String(localized: "menu.openInXcode", defaultValue: "Open Current Directory in Xcode") case .zed: - return "Open Current Directory in Zed" + return String(localized: "menu.openInZed", defaultValue: "Open Current Directory in Zed") } } @@ -1461,7 +1461,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private lazy var titlebarAccessoryController = UpdateTitlebarAccessoryController(viewModel: updateViewModel) private let windowDecorationsController = WindowDecorationsController() private var menuBarExtraController: MenuBarExtraController? - private static let serviceErrorNoPath = NSString(string: "Could not load any folder path from the clipboard.") + private static let serviceErrorNoPath = NSString(string: String(localized: "error.clipboardFolderPath", defaultValue: "Could not load any folder path from the clipboard.")) private static let didInstallWindowKeyEquivalentSwizzle: Void = { let targetClass: AnyClass = NSWindow.self let originalSelector = #selector(NSWindow.performKeyEquivalent(with:)) @@ -3695,9 +3695,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent var labels: [UUID: String] = [:] for (index, summary) in orderedSummaries.enumerated() { if summary.windowId == referenceWindowId { - labels[summary.windowId] = "Current Window" + labels[summary.windowId] = String(localized: "menu.currentWindow", defaultValue: "Current Window") } else { - labels[summary.windowId] = "Window \(index + 1)" + let number = index + 1 + labels[summary.windowId] = String(localized: "menu.windowNumber", defaultValue: "Window \(number)") } } return labels @@ -3705,7 +3706,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent private func workspaceDisplayName(_ workspace: Workspace) -> String { let trimmed = workspace.title.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmed.isEmpty ? "Workspace" : trimmed + return trimmed.isEmpty ? String(localized: "workspace.displayName.fallback", defaultValue: "Workspace") : trimmed } private func rollbackDetachedSurface( @@ -4648,22 +4649,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let installer = CmuxCLIPathInstaller() do { let outcome = try installer.install() - var informativeText = """ - Created symlink: - - \(outcome.destinationURL.path) -> \(outcome.sourceURL.path) - """ + var informativeText = String(localized: "cli.install.symlinkCreated", defaultValue: "Created symlink:\n\n\(outcome.destinationURL.path) -> \(outcome.sourceURL.path)") if outcome.usedAdministratorPrivileges { - informativeText += "\n\nAdministrator privileges were required to write to /usr/local/bin." + informativeText += "\n\n" + String(localized: "cli.install.adminRequired", defaultValue: "Administrator privileges were required to write to /usr/local/bin.") } presentCLIPathAlert( - title: "cmux CLI Installed", + title: String(localized: "cli.installed", defaultValue: "cmux CLI Installed"), informativeText: informativeText, style: .informational ) } catch { presentCLIPathAlert( - title: "Couldn't Install cmux CLI", + title: String(localized: "cli.installFailed", defaultValue: "Couldn't Install cmux CLI"), informativeText: error.localizedDescription, style: .warning ) @@ -4675,20 +4672,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent do { let outcome = try installer.uninstall() let prefix = outcome.removedExistingEntry - ? "Removed \(outcome.destinationURL.path)." - : "No cmux CLI symlink was found at \(outcome.destinationURL.path)." + ? String(localized: "cli.uninstall.removed", defaultValue: "Removed \(outcome.destinationURL.path).") + : String(localized: "cli.uninstall.notFound", defaultValue: "No cmux CLI symlink was found at \(outcome.destinationURL.path).") var informativeText = prefix if outcome.usedAdministratorPrivileges { - informativeText += "\n\nAdministrator privileges were required to modify /usr/local/bin." + informativeText += "\n\n" + String(localized: "cli.uninstall.adminRequired", defaultValue: "Administrator privileges were required to modify /usr/local/bin.") } presentCLIPathAlert( - title: "cmux CLI Uninstalled", + title: String(localized: "cli.uninstalled", defaultValue: "cmux CLI Uninstalled"), informativeText: informativeText, style: .informational ) } catch { presentCLIPathAlert( - title: "Couldn't Uninstall cmux CLI", + title: String(localized: "cli.uninstallFailed", defaultValue: "Couldn't Uninstall cmux CLI"), informativeText: error.localizedDescription, style: .warning ) @@ -4704,7 +4701,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent alert.alertStyle = style alert.messageText = title alert.informativeText = informativeText - alert.addButton(withTitle: "OK") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) if let window = NSApp.keyWindow ?? NSApp.mainWindow { alert.beginSheetModal(for: window, completionHandler: nil) @@ -6003,12 +6000,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent let alert = NSAlert() alert.alertStyle = .warning - alert.messageText = "Quit cmux?" - alert.informativeText = "This will close all windows and workspaces." - alert.addButton(withTitle: "Quit") - alert.addButton(withTitle: "Cancel") + alert.messageText = String(localized: "dialog.quitCmux.title", defaultValue: "Quit cmux?") + alert.informativeText = String(localized: "dialog.quitCmux.message", defaultValue: "This will close all windows and workspaces.") + alert.addButton(withTitle: String(localized: "dialog.quitCmux.quit", defaultValue: "Quit")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) alert.showsSuppressionButton = true - alert.suppressionButton?.title = "Don't warn again for Cmd+Q" + alert.suppressionButton?.title = String(localized: "dialog.dontWarnCmdQ", defaultValue: "Don't warn again for Cmd+Q") let response = alert.runModal() if alert.suppressionButton?.state == .on { @@ -6030,14 +6027,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent } let alert = NSAlert() - alert.messageText = "Rename Workspace" - alert.informativeText = "Enter a custom name for this workspace." + alert.messageText = String(localized: "dialog.renameWorkspace.title", defaultValue: "Rename Workspace") + alert.informativeText = String(localized: "dialog.renameWorkspace.message", defaultValue: "Enter a custom name for this workspace.") let input = NSTextField(string: tab.customTitle ?? tab.title) - input.placeholderString = "Workspace name" + input.placeholderString = String(localized: "dialog.renameWorkspace.placeholder", defaultValue: "Workspace name") input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Rename") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.rename", defaultValue: "Rename")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input DispatchQueue.main.async { @@ -8083,17 +8080,17 @@ final class MenuBarExtraController: NSObject, NSMenuDelegate { private var notificationsCancellable: AnyCancellable? private let buildHintTitle: String? - private let stateHintItem = NSMenuItem(title: "No unread notifications", action: nil, keyEquivalent: "") + private let stateHintItem = NSMenuItem(title: String(localized: "statusMenu.noUnread", defaultValue: "No unread notifications"), action: nil, keyEquivalent: "") private let buildHintItem = NSMenuItem(title: "", action: nil, keyEquivalent: "") private let notificationListSeparator = NSMenuItem.separator() private let notificationSectionSeparator = NSMenuItem.separator() - private let showNotificationsItem = NSMenuItem(title: "Show Notifications", action: nil, keyEquivalent: "") - private let jumpToUnreadItem = NSMenuItem(title: "Jump to Latest Unread", action: nil, keyEquivalent: "") - private let markAllReadItem = NSMenuItem(title: "Mark All Read", action: nil, keyEquivalent: "") - private let clearAllItem = NSMenuItem(title: "Clear All", action: nil, keyEquivalent: "") - private let checkForUpdatesItem = NSMenuItem(title: "Check for Updates…", action: nil, keyEquivalent: "") - private let preferencesItem = NSMenuItem(title: "Preferences…", action: nil, keyEquivalent: "") - private let quitItem = NSMenuItem(title: "Quit cmux", action: nil, keyEquivalent: "") + private let showNotificationsItem = NSMenuItem(title: String(localized: "statusMenu.showNotifications", defaultValue: "Show Notifications"), action: nil, keyEquivalent: "") + private let jumpToUnreadItem = NSMenuItem(title: String(localized: "statusMenu.jumpToLatestUnread", defaultValue: "Jump to Latest Unread"), action: nil, keyEquivalent: "") + private let markAllReadItem = NSMenuItem(title: String(localized: "statusMenu.markAllRead", defaultValue: "Mark All Read"), action: nil, keyEquivalent: "") + private let clearAllItem = NSMenuItem(title: String(localized: "statusMenu.clearAll", defaultValue: "Clear All"), action: nil, keyEquivalent: "") + private let checkForUpdatesItem = NSMenuItem(title: String(localized: "menu.checkForUpdates", defaultValue: "Check for Updates…"), action: nil, keyEquivalent: "") + private let preferencesItem = NSMenuItem(title: String(localized: "menu.preferences", defaultValue: "Preferences…"), action: nil, keyEquivalent: "") + private let quitItem = NSMenuItem(title: String(localized: "menu.quitCmux", defaultValue: "Quit cmux"), action: nil, keyEquivalent: "") private var notificationItems: [NSMenuItem] = [] private let maxInlineNotificationItems = 6 @@ -8222,7 +8219,9 @@ final class MenuBarExtraController: NSObject, NSMenuDelegate { button.image = MenuBarIconRenderer.makeImage(unreadCount: displayedUnreadCount) button.toolTip = displayedUnreadCount == 0 ? "cmux" - : "cmux: \(displayedUnreadCount) unread notification\(displayedUnreadCount == 1 ? "" : "s")" + : displayedUnreadCount == 1 + ? "cmux: " + String(localized: "statusMenu.tooltip.unread.one", defaultValue: "1 unread notification") + : "cmux: " + String(localized: "statusMenu.tooltip.unread.other", defaultValue: "\(displayedUnreadCount) unread notifications") } } @@ -8345,9 +8344,14 @@ enum NotificationMenuSnapshotBuilder { } static func stateHintTitle(unreadCount: Int) -> String { - unreadCount == 0 - ? "No unread notifications" - : "\(unreadCount) unread notification\(unreadCount == 1 ? "" : "s")" + switch unreadCount { + case 0: + return String(localized: "statusMenu.noUnread", defaultValue: "No unread notifications") + case 1: + return String(localized: "statusMenu.unreadCount.one", defaultValue: "1 unread notification") + default: + return String(localized: "statusMenu.unreadCount.other", defaultValue: "\(unreadCount) unread notifications") + } } } diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 2b57e7fa..a4884f4c 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1287,27 +1287,27 @@ struct ContentView: View { var title: String { switch kind { case .workspace: - return "Rename Workspace" + return String(localized: "commandPalette.rename.workspaceTitle", defaultValue: "Rename Workspace") case .tab: - return "Rename Tab" + return String(localized: "commandPalette.rename.tabTitle", defaultValue: "Rename Tab") } } var description: String { switch kind { case .workspace: - return "Choose a custom workspace name." + return String(localized: "commandPalette.rename.workspaceDescription", defaultValue: "Choose a custom workspace name.") case .tab: - return "Choose a custom tab name." + return String(localized: "commandPalette.rename.tabDescription", defaultValue: "Choose a custom tab name.") } } var placeholder: String { switch kind { case .workspace: - return "Workspace name" + return String(localized: "commandPalette.rename.workspacePlaceholder", defaultValue: "Workspace name") case .tab: - return "Tab name" + return String(localized: "commandPalette.rename.tabPlaceholder", defaultValue: "Tab name") } } } @@ -2904,7 +2904,7 @@ struct ContentView: View { Divider() - Text("Enter a \(renameTargetNoun(target)) name. Press Enter to rename, Escape to cancel.") + Text(renameInputHintText(target: target)) .font(.system(size: 11)) .foregroundStyle(.secondary) .lineLimit(1) @@ -2933,7 +2933,7 @@ struct ContentView: View { proposedName: String ) -> some View { let trimmedName = proposedName.trimmingCharacters(in: .whitespacesAndNewlines) - let nextName = trimmedName.isEmpty ? "(clear custom name)" : trimmedName + let nextName = trimmedName.isEmpty ? String(localized: "commandPalette.rename.clearCustomName", defaultValue: "(clear custom name)") : trimmedName return VStack(spacing: 0) { Text(nextName) @@ -2945,7 +2945,7 @@ struct ContentView: View { Divider() - Text("Press Enter to apply this \(renameTargetNoun(target)) name, or Escape to cancel.") + Text(renameConfirmHintText(target: target)) .font(.system(size: 11)) .foregroundStyle(.secondary) .lineLimit(1) @@ -2966,12 +2966,21 @@ struct ContentView: View { } } - private func renameTargetNoun(_ target: CommandPaletteRenameTarget) -> String { + private func renameInputHintText(target: CommandPaletteRenameTarget) -> String { switch target.kind { case .workspace: - return "workspace" + return String(localized: "commandPalette.rename.workspaceInputHint", defaultValue: "Enter a workspace name. Press Enter to rename, Escape to cancel.") case .tab: - return "tab" + return String(localized: "commandPalette.rename.tabInputHint", defaultValue: "Enter a tab name. Press Enter to rename, Escape to cancel.") + } + } + + private func renameConfirmHintText(target: CommandPaletteRenameTarget) -> String { + switch target.kind { + case .workspace: + return String(localized: "commandPalette.rename.workspaceConfirmHint", defaultValue: "Press Enter to apply this workspace name, or Escape to cancel.") + case .tab: + return String(localized: "commandPalette.rename.tabConfirmHint", defaultValue: "Press Enter to apply this tab name, or Escape to cancel.") } } @@ -2985,18 +2994,18 @@ struct ContentView: View { private var commandPaletteSearchPlaceholder: String { switch commandPaletteListScope { case .commands: - return "Type a command" + return String(localized: "commandPalette.search.commandsPlaceholder", defaultValue: "Type a command") case .switcher: - return "Search workspaces" + return String(localized: "commandPalette.search.switcherPlaceholder", defaultValue: "Search workspaces") } } private var commandPaletteEmptyStateText: String { switch commandPaletteListScope { case .commands: - return "No commands match your search." + return String(localized: "commandPalette.search.commandsEmpty", defaultValue: "No commands match your search.") case .switcher: - return "No workspaces match your search." + return String(localized: "commandPalette.search.switcherEmpty", defaultValue: "No workspaces match your search.") } } @@ -3089,7 +3098,7 @@ struct ContentView: View { guard commandPaletteListScope == .switcher else { return nil } if command.id.hasPrefix("switcher.workspace.") { - return CommandPaletteTrailingLabel(text: "Workspace", style: .kind) + return CommandPaletteTrailingLabel(text: String(localized: "commandPalette.kind.workspace", defaultValue: "Workspace"), style: .kind) } return nil } @@ -3139,7 +3148,7 @@ struct ContentView: View { id: workspaceCommandId, rank: nextRank, title: workspaceName, - subtitle: commandPaletteSwitcherSubtitle(base: "Workspace", windowLabel: context.windowLabel), + subtitle: commandPaletteSwitcherSubtitle(base: String(localized: "commandPalette.switcher.workspaceLabel", defaultValue: "Workspace"), windowLabel: context.windowLabel), shortcutHint: nil, keywords: workspaceKeywords, dismissOnRun: true, @@ -3183,7 +3192,7 @@ struct ContentView: View { var windowLabelById: [UUID: String] = [:] if orderedSummaries.count > 1 { for (index, summary) in orderedSummaries.enumerated() where summary.windowId != windowId { - windowLabelById[summary.windowId] = "Window \(index + 1)" + windowLabelById[summary.windowId] = String(localized: "commandPalette.switcher.windowLabel", defaultValue: "Window \(index + 1)") } } @@ -3449,23 +3458,23 @@ struct ContentView: View { } func workspaceSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.workspaceName) ?? "Workspace" - return "Workspace • \(name)" + let name = context.string(CommandPaletteContextKeys.workspaceName) ?? String(localized: "commandPalette.subtitle.workspaceFallback", defaultValue: "Workspace") + return String(localized: "commandPalette.subtitle.workspaceWithName", defaultValue: "Workspace • \(name)") } func panelSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.panelName) ?? "Tab" - return "Tab • \(name)" + let name = context.string(CommandPaletteContextKeys.panelName) ?? String(localized: "commandPalette.subtitle.tabFallback", defaultValue: "Tab") + return String(localized: "commandPalette.subtitle.tabWithName", defaultValue: "Tab • \(name)") } func browserPanelSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.panelName) ?? "Tab" - return "Browser • \(name)" + let name = context.string(CommandPaletteContextKeys.panelName) ?? String(localized: "commandPalette.subtitle.tabFallback", defaultValue: "Tab") + return String(localized: "commandPalette.subtitle.browserWithName", defaultValue: "Browser • \(name)") } func terminalPanelSubtitle(_ context: CommandPaletteContextSnapshot) -> String { - let name = context.string(CommandPaletteContextKeys.panelName) ?? "Tab" - return "Terminal • \(name)" + let name = context.string(CommandPaletteContextKeys.panelName) ?? String(localized: "commandPalette.subtitle.tabFallback", defaultValue: "Tab") + return String(localized: "commandPalette.subtitle.terminalWithName", defaultValue: "Terminal • \(name)") } var contributions: [CommandPaletteCommandContribution] = [] @@ -3473,24 +3482,24 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.newWorkspace", - title: constant("New Workspace"), - subtitle: constant("Workspace"), + title: constant(String(localized: "command.newWorkspace.title", defaultValue: "New Workspace")), + subtitle: constant(String(localized: "command.newWorkspace.subtitle", defaultValue: "Workspace")), keywords: ["create", "new", "workspace"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.newWindow", - title: constant("New Window"), - subtitle: constant("Window"), + title: constant(String(localized: "command.newWindow.title", defaultValue: "New Window")), + subtitle: constant(String(localized: "command.newWindow.subtitle", defaultValue: "Window")), keywords: ["create", "new", "window"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.installCLI", - title: constant("Shell Command: Install 'cmux' in PATH"), - subtitle: constant("CLI"), + title: constant(String(localized: "command.installCLI.title", defaultValue: "Shell Command: Install 'cmux' in PATH")), + subtitle: constant(String(localized: "command.installCLI.subtitle", defaultValue: "CLI")), keywords: ["install", "cli", "path", "shell", "command", "symlink"], when: { _ in !(AppDelegate.shared?.isCmuxCLIInstalledInPATH() ?? false) } ) @@ -3498,8 +3507,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.uninstallCLI", - title: constant("Shell Command: Uninstall 'cmux' from PATH"), - subtitle: constant("CLI"), + title: constant(String(localized: "command.uninstallCLI.title", defaultValue: "Shell Command: Uninstall 'cmux' from PATH")), + subtitle: constant(String(localized: "command.uninstallCLI.subtitle", defaultValue: "CLI")), keywords: ["uninstall", "remove", "cli", "path", "shell", "command", "symlink"], when: { _ in AppDelegate.shared?.isCmuxCLIInstalledInPATH() ?? false } ) @@ -3507,16 +3516,16 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.openFolder", - title: constant("Open Folder…"), - subtitle: constant("Workspace"), + title: constant(String(localized: "command.openFolder.title", defaultValue: "Open Folder…")), + subtitle: constant(String(localized: "command.openFolder.subtitle", defaultValue: "Workspace")), keywords: ["open", "folder", "repository", "project", "directory"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.newTerminalTab", - title: constant("New Tab (Terminal)"), - subtitle: constant("Tab"), + title: constant(String(localized: "command.newTerminalTab.title", defaultValue: "New Tab (Terminal)")), + subtitle: constant(String(localized: "command.newTerminalTab.subtitle", defaultValue: "Tab")), shortcutHint: "⌘T", keywords: ["new", "terminal", "tab"] ) @@ -3524,8 +3533,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.newBrowserTab", - title: constant("New Tab (Browser)"), - subtitle: constant("Tab"), + title: constant(String(localized: "command.newBrowserTab.title", defaultValue: "New Tab (Browser)")), + subtitle: constant(String(localized: "command.newBrowserTab.subtitle", defaultValue: "Tab")), shortcutHint: "⌘⇧L", keywords: ["new", "browser", "tab", "web"] ) @@ -3533,8 +3542,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.closeTab", - title: constant("Close Tab"), - subtitle: constant("Tab"), + title: constant(String(localized: "command.closeTab.title", defaultValue: "Close Tab")), + subtitle: constant(String(localized: "command.closeTab.subtitle", defaultValue: "Tab")), shortcutHint: "⌘W", keywords: ["close", "tab"] ) @@ -3542,8 +3551,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.closeWorkspace", - title: constant("Close Workspace"), - subtitle: constant("Workspace"), + title: constant(String(localized: "command.closeWorkspace.title", defaultValue: "Close Workspace")), + subtitle: constant(String(localized: "command.closeWorkspace.subtitle", defaultValue: "Workspace")), shortcutHint: "⌘⇧W", keywords: ["close", "workspace"] ) @@ -3551,24 +3560,24 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.closeWindow", - title: constant("Close Window"), - subtitle: constant("Window"), + title: constant(String(localized: "command.closeWindow.title", defaultValue: "Close Window")), + subtitle: constant(String(localized: "command.closeWindow.subtitle", defaultValue: "Window")), keywords: ["close", "window"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.toggleFullScreen", - title: constant("Toggle Full Screen"), - subtitle: constant("Window"), + title: constant(String(localized: "command.toggleFullScreen.title", defaultValue: "Toggle Full Screen")), + subtitle: constant(String(localized: "command.toggleFullScreen.subtitle", defaultValue: "Window")), keywords: ["fullscreen", "full", "screen", "window", "toggle"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.reopenClosedBrowserTab", - title: constant("Reopen Closed Browser Tab"), - subtitle: constant("Browser"), + title: constant(String(localized: "command.reopenClosedBrowserTab.title", defaultValue: "Reopen Closed Browser Tab")), + subtitle: constant(String(localized: "command.reopenClosedBrowserTab.subtitle", defaultValue: "Browser")), shortcutHint: "⌘⇧T", keywords: ["reopen", "closed", "browser"] ) @@ -3576,40 +3585,40 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.toggleSidebar", - title: constant("Toggle Sidebar"), - subtitle: constant("Layout"), + title: constant(String(localized: "command.toggleSidebar.title", defaultValue: "Toggle Sidebar")), + subtitle: constant(String(localized: "command.toggleSidebar.subtitle", defaultValue: "Layout")), keywords: ["toggle", "sidebar", "layout"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.triggerFlash", - title: constant("Flash Focused Panel"), - subtitle: constant("View"), + title: constant(String(localized: "command.triggerFlash.title", defaultValue: "Flash Focused Panel")), + subtitle: constant(String(localized: "command.triggerFlash.subtitle", defaultValue: "View")), keywords: ["flash", "highlight", "focus", "panel"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.showNotifications", - title: constant("Show Notifications"), - subtitle: constant("Notifications"), + title: constant(String(localized: "command.showNotifications.title", defaultValue: "Show Notifications")), + subtitle: constant(String(localized: "command.showNotifications.subtitle", defaultValue: "Notifications")), keywords: ["notifications", "inbox"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.jumpUnread", - title: constant("Jump to Latest Unread"), - subtitle: constant("Notifications"), + title: constant(String(localized: "command.jumpUnread.title", defaultValue: "Jump to Latest Unread")), + subtitle: constant(String(localized: "command.jumpUnread.subtitle", defaultValue: "Notifications")), keywords: ["jump", "unread", "notification"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.openSettings", - title: constant("Open Settings"), - subtitle: constant("Global"), + title: constant(String(localized: "command.openSettings.title", defaultValue: "Open Settings")), + subtitle: constant(String(localized: "command.openSettings.subtitle", defaultValue: "Global")), shortcutHint: "⌘,", keywords: ["settings", "preferences"] ) @@ -3617,16 +3626,16 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.checkForUpdates", - title: constant("Check for Updates"), - subtitle: constant("Global"), + title: constant(String(localized: "command.checkForUpdates.title", defaultValue: "Check for Updates")), + subtitle: constant(String(localized: "command.checkForUpdates.subtitle", defaultValue: "Global")), keywords: ["update", "upgrade", "release"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.applyUpdateIfAvailable", - title: constant("Apply Update (If Available)"), - subtitle: constant("Global"), + title: constant(String(localized: "command.applyUpdateIfAvailable.title", defaultValue: "Apply Update (If Available)")), + subtitle: constant(String(localized: "command.applyUpdateIfAvailable.subtitle", defaultValue: "Global")), keywords: ["apply", "install", "update", "available"], when: { $0.bool(CommandPaletteContextKeys.updateHasAvailable) } ) @@ -3634,16 +3643,16 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.attemptUpdate", - title: constant("Attempt Update"), - subtitle: constant("Global"), + title: constant(String(localized: "command.attemptUpdate.title", defaultValue: "Attempt Update")), + subtitle: constant(String(localized: "command.attemptUpdate.subtitle", defaultValue: "Global")), keywords: ["attempt", "check", "update", "upgrade", "release"] ) ) contributions.append( CommandPaletteCommandContribution( commandId: "palette.restartSocketListener", - title: constant("Restart CLI Listener"), - subtitle: constant("Global"), + title: constant(String(localized: "command.restartSocketListener.title", defaultValue: "Restart CLI Listener")), + subtitle: constant(String(localized: "command.restartSocketListener.subtitle", defaultValue: "Global")), keywords: ["restart", "socket", "listener", "cli", "cmux", "control"] ) ) @@ -3651,7 +3660,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.renameWorkspace", - title: constant("Rename Workspace…"), + title: constant(String(localized: "command.renameWorkspace.title", defaultValue: "Rename Workspace…")), subtitle: workspaceSubtitle, keywords: ["rename", "workspace", "title"], dismissOnRun: false, @@ -3661,7 +3670,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.clearWorkspaceName", - title: constant("Clear Workspace Name"), + title: constant(String(localized: "command.clearWorkspaceName.title", defaultValue: "Clear Workspace Name")), subtitle: workspaceSubtitle, keywords: ["clear", "workspace", "name"], when: { @@ -3674,7 +3683,7 @@ struct ContentView: View { CommandPaletteCommandContribution( commandId: "palette.toggleWorkspacePin", title: { context in - context.bool(CommandPaletteContextKeys.workspaceShouldPin) ? "Pin Workspace" : "Unpin Workspace" + context.bool(CommandPaletteContextKeys.workspaceShouldPin) ? String(localized: "command.pinWorkspace.title", defaultValue: "Pin Workspace") : String(localized: "command.unpinWorkspace.title", defaultValue: "Unpin Workspace") }, subtitle: workspaceSubtitle, keywords: ["workspace", "pin", "pinned"], @@ -3684,8 +3693,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.nextWorkspace", - title: constant("Next Workspace"), - subtitle: constant("Workspace Navigation"), + title: constant(String(localized: "command.nextWorkspace.title", defaultValue: "Next Workspace")), + subtitle: constant(String(localized: "command.nextWorkspace.subtitle", defaultValue: "Workspace Navigation")), keywords: ["next", "workspace", "navigate"], when: { $0.bool(CommandPaletteContextKeys.hasWorkspace) } ) @@ -3693,8 +3702,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.previousWorkspace", - title: constant("Previous Workspace"), - subtitle: constant("Workspace Navigation"), + title: constant(String(localized: "command.previousWorkspace.title", defaultValue: "Previous Workspace")), + subtitle: constant(String(localized: "command.previousWorkspace.subtitle", defaultValue: "Workspace Navigation")), keywords: ["previous", "workspace", "navigate"], when: { $0.bool(CommandPaletteContextKeys.hasWorkspace) } ) @@ -3703,7 +3712,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.renameTab", - title: constant("Rename Tab…"), + title: constant(String(localized: "command.renameTab.title", defaultValue: "Rename Tab…")), subtitle: panelSubtitle, keywords: ["rename", "tab", "title"], dismissOnRun: false, @@ -3713,7 +3722,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.clearTabName", - title: constant("Clear Tab Name"), + title: constant(String(localized: "command.clearTabName.title", defaultValue: "Clear Tab Name")), subtitle: panelSubtitle, keywords: ["clear", "tab", "name"], when: { @@ -3726,7 +3735,7 @@ struct ContentView: View { CommandPaletteCommandContribution( commandId: "palette.toggleTabPin", title: { context in - context.bool(CommandPaletteContextKeys.panelShouldPin) ? "Pin Tab" : "Unpin Tab" + context.bool(CommandPaletteContextKeys.panelShouldPin) ? String(localized: "command.pinTab.title", defaultValue: "Pin Tab") : String(localized: "command.unpinTab.title", defaultValue: "Unpin Tab") }, subtitle: panelSubtitle, keywords: ["tab", "pin", "pinned"], @@ -3737,7 +3746,7 @@ struct ContentView: View { CommandPaletteCommandContribution( commandId: "palette.toggleTabUnread", title: { context in - context.bool(CommandPaletteContextKeys.panelHasUnread) ? "Mark Tab as Read" : "Mark Tab as Unread" + context.bool(CommandPaletteContextKeys.panelHasUnread) ? String(localized: "command.markTabRead.title", defaultValue: "Mark Tab as Read") : String(localized: "command.markTabUnread.title", defaultValue: "Mark Tab as Unread") }, subtitle: panelSubtitle, keywords: ["tab", "read", "unread", "notification"], @@ -3747,8 +3756,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.nextTabInPane", - title: constant("Next Tab in Pane"), - subtitle: constant("Tab Navigation"), + title: constant(String(localized: "command.nextTabInPane.title", defaultValue: "Next Tab in Pane")), + subtitle: constant(String(localized: "command.nextTabInPane.subtitle", defaultValue: "Tab Navigation")), keywords: ["next", "tab", "pane"], when: { $0.bool(CommandPaletteContextKeys.hasFocusedPanel) } ) @@ -3756,8 +3765,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.previousTabInPane", - title: constant("Previous Tab in Pane"), - subtitle: constant("Tab Navigation"), + title: constant(String(localized: "command.previousTabInPane.title", defaultValue: "Previous Tab in Pane")), + subtitle: constant(String(localized: "command.previousTabInPane.subtitle", defaultValue: "Tab Navigation")), keywords: ["previous", "tab", "pane"], when: { $0.bool(CommandPaletteContextKeys.hasFocusedPanel) } ) @@ -3766,7 +3775,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.openWorkspacePullRequests", - title: constant("Open All Workspace PR Links"), + title: constant(String(localized: "command.openWorkspacePRLinks.title", defaultValue: "Open All Workspace PR Links")), subtitle: workspaceSubtitle, keywords: ["pull", "request", "review", "merge", "pr", "mr", "open", "links", "workspace"], when: { @@ -3778,7 +3787,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserBack", - title: constant("Back"), + title: constant(String(localized: "command.browserBack.title", defaultValue: "Back")), subtitle: browserPanelSubtitle, shortcutHint: "⌘[", keywords: ["browser", "back", "history"], @@ -3788,7 +3797,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserForward", - title: constant("Forward"), + title: constant(String(localized: "command.browserForward.title", defaultValue: "Forward")), subtitle: browserPanelSubtitle, shortcutHint: "⌘]", keywords: ["browser", "forward", "history"], @@ -3798,7 +3807,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserReload", - title: constant("Reload Page"), + title: constant(String(localized: "command.browserReload.title", defaultValue: "Reload Page")), subtitle: browserPanelSubtitle, shortcutHint: "⌘R", keywords: ["browser", "reload", "refresh"], @@ -3808,7 +3817,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserOpenDefault", - title: constant("Open Current Page in Default Browser"), + title: constant(String(localized: "command.browserOpenDefault.title", defaultValue: "Open Current Page in Default Browser")), subtitle: browserPanelSubtitle, keywords: ["open", "default", "external", "browser"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3817,7 +3826,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserFocusAddressBar", - title: constant("Focus Address Bar"), + title: constant(String(localized: "command.browserFocusAddressBar.title", defaultValue: "Focus Address Bar")), subtitle: browserPanelSubtitle, shortcutHint: "⌘L", keywords: ["browser", "address", "omnibar", "url"], @@ -3827,7 +3836,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserToggleDevTools", - title: constant("Toggle Developer Tools"), + title: constant(String(localized: "command.browserToggleDevTools.title", defaultValue: "Toggle Developer Tools")), subtitle: browserPanelSubtitle, keywords: ["browser", "devtools", "inspector"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3836,7 +3845,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserConsole", - title: constant("Show JavaScript Console"), + title: constant(String(localized: "command.browserConsole.title", defaultValue: "Show JavaScript Console")), subtitle: browserPanelSubtitle, keywords: ["browser", "console", "javascript"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3845,7 +3854,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserZoomIn", - title: constant("Zoom In"), + title: constant(String(localized: "command.browserZoomIn.title", defaultValue: "Zoom In")), subtitle: browserPanelSubtitle, keywords: ["browser", "zoom", "in"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3854,7 +3863,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserZoomOut", - title: constant("Zoom Out"), + title: constant(String(localized: "command.browserZoomOut.title", defaultValue: "Zoom Out")), subtitle: browserPanelSubtitle, keywords: ["browser", "zoom", "out"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3863,7 +3872,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserZoomReset", - title: constant("Actual Size"), + title: constant(String(localized: "command.browserZoomReset.title", defaultValue: "Actual Size")), subtitle: browserPanelSubtitle, keywords: ["browser", "zoom", "reset", "actual size"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } @@ -3872,8 +3881,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserClearHistory", - title: constant("Clear Browser History"), - subtitle: constant("Browser"), + title: constant(String(localized: "command.browserClearHistory.title", defaultValue: "Clear Browser History")), + subtitle: constant(String(localized: "command.browserClearHistory.subtitle", defaultValue: "Browser")), keywords: ["browser", "history", "clear"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3881,8 +3890,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserSplitRight", - title: constant("Split Browser Right"), - subtitle: constant("Browser Layout"), + title: constant(String(localized: "command.browserSplitRight.title", defaultValue: "Split Browser Right")), + subtitle: constant(String(localized: "command.browserSplitRight.subtitle", defaultValue: "Browser Layout")), keywords: ["browser", "split", "right"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3890,8 +3899,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserSplitDown", - title: constant("Split Browser Down"), - subtitle: constant("Browser Layout"), + title: constant(String(localized: "command.browserSplitDown.title", defaultValue: "Split Browser Down")), + subtitle: constant(String(localized: "command.browserSplitDown.subtitle", defaultValue: "Browser Layout")), keywords: ["browser", "split", "down"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3899,8 +3908,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.browserDuplicateRight", - title: constant("Duplicate Browser to the Right"), - subtitle: constant("Browser Layout"), + title: constant(String(localized: "command.browserDuplicateRight.title", defaultValue: "Duplicate Browser to the Right")), + subtitle: constant(String(localized: "command.browserDuplicateRight.subtitle", defaultValue: "Browser Layout")), keywords: ["browser", "duplicate", "clone", "split"], when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) } ) @@ -3923,7 +3932,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.vscodeServeWebStop", - title: constant("Stop VS Code Inline Server"), + title: constant(String(localized: "command.vscodeServeWebStop.title", defaultValue: "Stop VS Code Inline Server")), subtitle: terminalPanelSubtitle, keywords: ["vscode", "inline", "serve-web", "stop", "server"], when: { context in @@ -3935,7 +3944,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.vscodeServeWebRestart", - title: constant("Restart VS Code Inline Server"), + title: constant(String(localized: "command.vscodeServeWebRestart.title", defaultValue: "Restart VS Code Inline Server")), subtitle: terminalPanelSubtitle, keywords: ["vscode", "inline", "serve-web", "restart", "server"], when: { context in @@ -3947,7 +3956,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalFind", - title: constant("Find…"), + title: constant(String(localized: "command.terminalFind.title", defaultValue: "Find…")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘F", keywords: ["terminal", "find", "search"], @@ -3957,7 +3966,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalFindNext", - title: constant("Find Next"), + title: constant(String(localized: "command.terminalFindNext.title", defaultValue: "Find Next")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘G", keywords: ["terminal", "find", "next", "search"], @@ -3967,7 +3976,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalFindPrevious", - title: constant("Find Previous"), + title: constant(String(localized: "command.terminalFindPrevious.title", defaultValue: "Find Previous")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘⇧G", keywords: ["terminal", "find", "previous", "search"], @@ -3977,7 +3986,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalHideFind", - title: constant("Hide Find Bar"), + title: constant(String(localized: "command.terminalHideFind.title", defaultValue: "Hide Find Bar")), subtitle: terminalPanelSubtitle, shortcutHint: "⌘⇧F", keywords: ["terminal", "hide", "find", "search"], @@ -3987,7 +3996,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalUseSelectionForFind", - title: constant("Use Selection for Find"), + title: constant(String(localized: "command.terminalUseSelectionForFind.title", defaultValue: "Use Selection for Find")), subtitle: terminalPanelSubtitle, keywords: ["terminal", "selection", "find"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } @@ -3996,8 +4005,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitRight", - title: constant("Split Right"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitRight.title", defaultValue: "Split Right")), + subtitle: constant(String(localized: "command.terminalSplitRight.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "right"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4005,8 +4014,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitDown", - title: constant("Split Down"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitDown.title", defaultValue: "Split Down")), + subtitle: constant(String(localized: "command.terminalSplitDown.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "down"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4014,8 +4023,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitBrowserRight", - title: constant("Split Browser Right"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitBrowserRight.title", defaultValue: "Split Browser Right")), + subtitle: constant(String(localized: "command.terminalSplitBrowserRight.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "browser", "right"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4023,8 +4032,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.terminalSplitBrowserDown", - title: constant("Split Browser Down"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.terminalSplitBrowserDown.title", defaultValue: "Split Browser Down")), + subtitle: constant(String(localized: "command.terminalSplitBrowserDown.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "split", "browser", "down"], when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) } ) @@ -4032,8 +4041,8 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.toggleSplitZoom", - title: constant("Toggle Pane Zoom"), - subtitle: constant("Terminal Layout"), + title: constant(String(localized: "command.toggleSplitZoom.title", defaultValue: "Toggle Pane Zoom")), + subtitle: constant(String(localized: "command.toggleSplitZoom.subtitle", defaultValue: "Terminal Layout")), keywords: ["terminal", "pane", "split", "zoom", "maximize"], when: { context in context.bool(CommandPaletteContextKeys.panelIsTerminal) && @@ -4044,7 +4053,7 @@ struct ContentView: View { contributions.append( CommandPaletteCommandContribution( commandId: "palette.equalizeSplits", - title: constant("Equalize Splits"), + title: constant(String(localized: "command.equalizeSplits.title", defaultValue: "Equalize Splits")), subtitle: workspaceSubtitle, keywords: ["split", "equalize", "balance", "divider", "layout"], when: { $0.bool(CommandPaletteContextKeys.workspaceHasSplits) } @@ -4065,8 +4074,8 @@ struct ContentView: View { panel.canChooseFiles = false panel.canChooseDirectories = true panel.allowsMultipleSelection = false - panel.title = "Open Folder" - panel.prompt = "Open" + panel.title = String(localized: "panel.openFolder.title", defaultValue: "Open Folder") + panel.prompt = String(localized: "panel.openFolder.prompt", defaultValue: "Open") if panel.runModal() == .OK, let url = panel.url { tabManager.addWorkspace(workingDirectory: url.path) } @@ -4352,7 +4361,7 @@ struct ContentView: View { return custom } let title = workspace.title.trimmingCharacters(in: .whitespacesAndNewlines) - return title.isEmpty ? "Workspace" : title + return title.isEmpty ? String(localized: "workspace.displayName.fallback", defaultValue: "Workspace") : title } private func panelDisplayName(workspace: Workspace, panelId: UUID, fallback: String) -> String { @@ -4361,7 +4370,7 @@ struct ContentView: View { return title } let trimmedFallback = fallback.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmedFallback.isEmpty ? "Tab" : trimmedFallback + return trimmedFallback.isEmpty ? String(localized: "panel.displayName.fallback", defaultValue: "Tab") : trimmedFallback } private func commandPaletteSelectedIndex(resultCount: Int) -> Int { @@ -6551,6 +6560,10 @@ private struct TabItemView: View { } var body: some View { + let closeWorkspaceTooltip = String(localized: "sidebar.closeWorkspace.tooltip", defaultValue: "Close Workspace") + let accessibilityHintText = String(localized: "sidebar.workspace.accessibilityHint", defaultValue: "Activate to focus this workspace. Drag to reorder, or use Move Up and Move Down actions.") + let moveUpActionText = String(localized: "sidebar.workspace.moveUpAction", defaultValue: "Move Up") + let moveDownActionText = String(localized: "sidebar.workspace.moveDownAction", defaultValue: "Move Down") let latestNotificationSubtitle = latestNotificationText let orderedPanelIds: [UUID]? = (sidebarShowBranchDirectory || sidebarShowPullRequest) ? tab.sidebarOrderedPanelIds() @@ -6630,7 +6643,7 @@ private struct TabItemView: View { .foregroundColor(activeSecondaryColor(0.7)) } .buttonStyle(.plain) - .help(KeyboardShortcutSettings.Action.closeWorkspace.tooltip("Close Workspace")) + .help(KeyboardShortcutSettings.Action.closeWorkspace.tooltip(closeWorkspaceTooltip)) .frame(width: 16, height: 16, alignment: .center) .opacity(showCloseButton && !showsWorkspaceShortcutHint ? 1 : 0) .allowsHitTesting(showCloseButton && !showsWorkspaceShortcutHint) @@ -6803,7 +6816,7 @@ private struct TabItemView: View { .foregroundColor(pullRequestForegroundColor) } .buttonStyle(.plain) - .help("Open \(pullRequest.label) #\(pullRequest.number)") + .help(String(localized: "sidebar.pullRequest.openTooltip", defaultValue: "Open \(pullRequest.label) #\(pullRequest.number)")) } } } @@ -6903,165 +6916,190 @@ private struct TabItemView: View { } .accessibilityElement(children: .combine) .accessibilityLabel(Text(accessibilityTitle)) - .accessibilityHint(Text("Activate to focus this workspace. Drag to reorder, or use Move Up and Move Down actions.")) - .accessibilityAction(named: Text("Move Up")) { + .accessibilityHint(Text(accessibilityHintText)) + .accessibilityAction(named: Text(moveUpActionText)) { moveBy(-1) } - .accessibilityAction(named: Text("Move Down")) { + .accessibilityAction(named: Text(moveDownActionText)) { moveBy(1) } - .contextMenu { - let targetIds = contextTargetIds() - let tabColorPalette = WorkspaceTabColorSettings.palette() - let shouldPin = !tab.isPinned - let pinLabel = targetIds.count > 1 - ? (shouldPin ? "Pin Workspaces" : "Unpin Workspaces") - : (shouldPin ? "Pin Workspace" : "Unpin Workspace") - let closeLabel = targetIds.count > 1 ? "Close Workspaces" : "Close Workspace" - let markReadLabel = targetIds.count > 1 ? "Mark Workspaces as Read" : "Mark Workspace as Read" - let markUnreadLabel = targetIds.count > 1 ? "Mark Workspaces as Unread" : "Mark Workspace as Unread" - let renameWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .renameWorkspace) - let closeWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .closeWorkspace) - Button(pinLabel) { - for id in targetIds { - if let tab = tabManager.tabs.first(where: { $0.id == id }) { - tabManager.setPinned(tab, pinned: shouldPin) - } - } - syncSelectionAfterMutation() - } + .contextMenu { workspaceContextMenu } + } - if let key = renameWorkspaceShortcut.keyEquivalent { - Button("Rename Workspace…") { - promptRename() - } - .keyboardShortcut(key, modifiers: renameWorkspaceShortcut.eventModifiers) - } else { - Button("Rename Workspace…") { - promptRename() + private func contextMenuLabel(multi: String, single: String, isMulti: Bool) -> String { + isMulti ? multi : single + } + + @ViewBuilder + private var workspaceContextMenu: some View { + let targetIds = contextTargetIds() + let isMulti = targetIds.count > 1 + let tabColorPalette = WorkspaceTabColorSettings.palette() + let shouldPin = !tab.isPinned + let pinLabel = shouldPin + ? contextMenuLabel( + multi: String(localized: "contextMenu.pinWorkspaces", defaultValue: "Pin Workspaces"), + single: String(localized: "contextMenu.pinWorkspace", defaultValue: "Pin Workspace"), + isMulti: isMulti) + : contextMenuLabel( + multi: String(localized: "contextMenu.unpinWorkspaces", defaultValue: "Unpin Workspaces"), + single: String(localized: "contextMenu.unpinWorkspace", defaultValue: "Unpin Workspace"), + isMulti: isMulti) + let closeLabel = contextMenuLabel( + multi: String(localized: "contextMenu.closeWorkspaces", defaultValue: "Close Workspaces"), + single: String(localized: "contextMenu.closeWorkspace", defaultValue: "Close Workspace"), + isMulti: isMulti) + let markReadLabel = contextMenuLabel( + multi: String(localized: "contextMenu.markWorkspacesRead", defaultValue: "Mark Workspaces as Read"), + single: String(localized: "contextMenu.markWorkspaceRead", defaultValue: "Mark Workspace as Read"), + isMulti: isMulti) + let markUnreadLabel = contextMenuLabel( + multi: String(localized: "contextMenu.markWorkspacesUnread", defaultValue: "Mark Workspaces as Unread"), + single: String(localized: "contextMenu.markWorkspaceUnread", defaultValue: "Mark Workspace as Unread"), + isMulti: isMulti) + let renameWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .renameWorkspace) + let closeWorkspaceShortcut = KeyboardShortcutSettings.shortcut(for: .closeWorkspace) + Button(pinLabel) { + for id in targetIds { + if let tab = tabManager.tabs.first(where: { $0.id == id }) { + tabManager.setPinned(tab, pinned: shouldPin) } } - - if tab.hasCustomTitle { - Button("Remove Custom Workspace Name") { - tabManager.clearCustomTitle(tabId: tab.id) - } - } - - Menu("Workspace Color") { - if tab.customColor != nil { - Button { - applyTabColor(nil, targetIds: targetIds) - } label: { - Label("Clear Color", systemImage: "xmark.circle") - } - } - - Button { - promptCustomColor(targetIds: targetIds) - } label: { - Label("Choose Custom Color…", systemImage: "paintpalette") - } - - if !tabColorPalette.isEmpty { - Divider() - } - - ForEach(tabColorPalette, id: \.id) { entry in - Button { - applyTabColor(entry.hex, targetIds: targetIds) - } label: { - Label { - Text(entry.name) - } icon: { - Image(nsImage: coloredCircleImage(color: tabColorSwatchColor(for: entry.hex))) - } - } - } - } - - Divider() - - Button("Move Up") { - moveBy(-1) - } - .disabled(index == 0) - - Button("Move Down") { - moveBy(1) - } - .disabled(index >= tabManager.tabs.count - 1) - - Button("Move to Top") { - tabManager.moveTabsToTop(Set(targetIds)) - syncSelectionAfterMutation() - } - .disabled(targetIds.isEmpty) - - let referenceWindowId = AppDelegate.shared?.windowId(for: tabManager) - let windowMoveTargets = AppDelegate.shared?.windowMoveTargets(referenceWindowId: referenceWindowId) ?? [] - let moveMenuTitle = targetIds.count > 1 ? "Move Workspaces to Window" : "Move Workspace to Window" - Menu(moveMenuTitle) { - Button("New Window") { - moveWorkspacesToNewWindow(targetIds) - } - .disabled(targetIds.isEmpty) - - if !windowMoveTargets.isEmpty { - Divider() - } - - ForEach(windowMoveTargets) { target in - Button(target.label) { - moveWorkspaces(targetIds, toWindow: target.windowId) - } - .disabled(target.isCurrentWindow || targetIds.isEmpty) - } - } - .disabled(targetIds.isEmpty) - - Divider() - - if let key = closeWorkspaceShortcut.keyEquivalent { - Button(closeLabel) { - closeTabs(targetIds, allowPinned: true) - } - .keyboardShortcut(key, modifiers: closeWorkspaceShortcut.eventModifiers) - .disabled(targetIds.isEmpty) - } else { - Button(closeLabel) { - closeTabs(targetIds, allowPinned: true) - } - .disabled(targetIds.isEmpty) - } - - Button("Close Other Workspaces") { - closeOtherTabs(targetIds) - } - .disabled(tabManager.tabs.count <= 1 || targetIds.count == tabManager.tabs.count) - - Button("Close Workspaces Below") { - closeTabsBelow(tabId: tab.id) - } - .disabled(index >= tabManager.tabs.count - 1) - - Button("Close Workspaces Above") { - closeTabsAbove(tabId: tab.id) - } - .disabled(index == 0) - - Divider() - - Button(markReadLabel) { - markTabsRead(targetIds) - } - .disabled(!hasUnreadNotifications(in: targetIds)) - - Button(markUnreadLabel) { - markTabsUnread(targetIds) - } - .disabled(!hasReadNotifications(in: targetIds)) + syncSelectionAfterMutation() } + + if let key = renameWorkspaceShortcut.keyEquivalent { + Button(String(localized: "contextMenu.renameWorkspace", defaultValue: "Rename Workspace…")) { + promptRename() + } + .keyboardShortcut(key, modifiers: renameWorkspaceShortcut.eventModifiers) + } else { + Button(String(localized: "contextMenu.renameWorkspace", defaultValue: "Rename Workspace…")) { + promptRename() + } + } + + if tab.hasCustomTitle { + Button(String(localized: "contextMenu.removeCustomWorkspaceName", defaultValue: "Remove Custom Workspace Name")) { + tabManager.clearCustomTitle(tabId: tab.id) + } + } + + Menu(String(localized: "contextMenu.workspaceColor", defaultValue: "Workspace Color")) { + if tab.customColor != nil { + Button { + applyTabColor(nil, targetIds: targetIds) + } label: { + Label(String(localized: "contextMenu.clearColor", defaultValue: "Clear Color"), systemImage: "xmark.circle") + } + } + + Button { + promptCustomColor(targetIds: targetIds) + } label: { + Label(String(localized: "contextMenu.chooseCustomColor", defaultValue: "Choose Custom Color…"), systemImage: "paintpalette") + } + + if !tabColorPalette.isEmpty { + Divider() + } + + ForEach(tabColorPalette, id: \.id) { entry in + Button { + applyTabColor(entry.hex, targetIds: targetIds) + } label: { + Label { + Text(entry.name) + } icon: { + Image(nsImage: coloredCircleImage(color: tabColorSwatchColor(for: entry.hex))) + } + } + } + } + + Divider() + + Button(String(localized: "contextMenu.moveUp", defaultValue: "Move Up")) { + moveBy(-1) + } + .disabled(index == 0) + + Button(String(localized: "contextMenu.moveDown", defaultValue: "Move Down")) { + moveBy(1) + } + .disabled(index >= tabManager.tabs.count - 1) + + Button(String(localized: "contextMenu.moveToTop", defaultValue: "Move to Top")) { + tabManager.moveTabsToTop(Set(targetIds)) + syncSelectionAfterMutation() + } + .disabled(targetIds.isEmpty) + + let referenceWindowId = AppDelegate.shared?.windowId(for: tabManager) + let windowMoveTargets = AppDelegate.shared?.windowMoveTargets(referenceWindowId: referenceWindowId) ?? [] + let moveMenuTitle = targetIds.count > 1 + ? String(localized: "contextMenu.moveWorkspacesToWindow", defaultValue: "Move Workspaces to Window") + : String(localized: "contextMenu.moveWorkspaceToWindow", defaultValue: "Move Workspace to Window") + Menu(moveMenuTitle) { + Button(String(localized: "contextMenu.newWindow", defaultValue: "New Window")) { + moveWorkspacesToNewWindow(targetIds) + } + .disabled(targetIds.isEmpty) + + if !windowMoveTargets.isEmpty { + Divider() + } + + ForEach(windowMoveTargets) { target in + Button(target.label) { + moveWorkspaces(targetIds, toWindow: target.windowId) + } + .disabled(target.isCurrentWindow || targetIds.isEmpty) + } + } + .disabled(targetIds.isEmpty) + + Divider() + + if let key = closeWorkspaceShortcut.keyEquivalent { + Button(closeLabel) { + closeTabs(targetIds, allowPinned: true) + } + .keyboardShortcut(key, modifiers: closeWorkspaceShortcut.eventModifiers) + .disabled(targetIds.isEmpty) + } else { + Button(closeLabel) { + closeTabs(targetIds, allowPinned: true) + } + .disabled(targetIds.isEmpty) + } + + Button(String(localized: "contextMenu.closeOtherWorkspaces", defaultValue: "Close Other Workspaces")) { + closeOtherTabs(targetIds) + } + .disabled(tabManager.tabs.count <= 1 || targetIds.count == tabManager.tabs.count) + + Button(String(localized: "contextMenu.closeWorkspacesBelow", defaultValue: "Close Workspaces Below")) { + closeTabsBelow(tabId: tab.id) + } + .disabled(index >= tabManager.tabs.count - 1) + + Button(String(localized: "contextMenu.closeWorkspacesAbove", defaultValue: "Close Workspaces Above")) { + closeTabsAbove(tabId: tab.id) + } + .disabled(index == 0) + + Divider() + + Button(markReadLabel) { + markTabsRead(targetIds) + } + .disabled(!hasUnreadNotifications(in: targetIds)) + + Button(markUnreadLabel) { + markTabsUnread(targetIds) + } + .disabled(!hasReadNotifications(in: targetIds)) } private var backgroundColor: Color { @@ -7126,7 +7164,7 @@ private struct TabItemView: View { } private var accessibilityTitle: String { - "\(tab.title), workspace \(index + 1) of \(tabManager.tabs.count)" + String(localized: "accessibility.workspacePosition", defaultValue: "\(tab.title), workspace \(index + 1) of \(tabManager.tabs.count)") } private func moveBy(_ delta: Int) { @@ -7412,9 +7450,9 @@ private struct TabItemView: View { private func pullRequestStatusLabel(_ status: SidebarPullRequestStatus) -> String { switch status { - case .open: return "open" - case .merged: return "merged" - case .closed: return "closed" + case .open: return String(localized: "sidebar.pullRequest.statusOpen", defaultValue: "open") + case .merged: return String(localized: "sidebar.pullRequest.statusMerged", defaultValue: "merged") + case .closed: return String(localized: "sidebar.pullRequest.statusClosed", defaultValue: "closed") } } @@ -7555,16 +7593,16 @@ private struct TabItemView: View { private func promptCustomColor(targetIds: [UUID]) { let alert = NSAlert() - alert.messageText = "Custom Workspace Color" - alert.informativeText = "Enter a hex color in the format #RRGGBB." + alert.messageText = String(localized: "alert.customColor.title", defaultValue: "Custom Workspace Color") + alert.informativeText = String(localized: "alert.customColor.message", defaultValue: "Enter a hex color in the format #RRGGBB.") let seed = tab.customColor ?? WorkspaceTabColorSettings.customColors().first ?? "" let input = NSTextField(string: seed) input.placeholderString = "#1565C0" input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Apply") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "alert.customColor.apply", defaultValue: "Apply")) + alert.addButton(withTitle: String(localized: "alert.customColor.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input @@ -7585,27 +7623,27 @@ private struct TabItemView: View { private func showInvalidColorAlert(_ value: String) { let alert = NSAlert() alert.alertStyle = .warning - alert.messageText = "Invalid Color" + alert.messageText = String(localized: "alert.invalidColor.title", defaultValue: "Invalid Color") let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { - alert.informativeText = "Enter a hex color in the format #RRGGBB." + alert.informativeText = String(localized: "alert.invalidColor.emptyMessage", defaultValue: "Enter a hex color in the format #RRGGBB.") } else { - alert.informativeText = "\"\(trimmed)\" is not a valid hex color. Use #RRGGBB." + alert.informativeText = String(localized: "alert.invalidColor.invalidMessage", defaultValue: "\"\(trimmed)\" is not a valid hex color. Use #RRGGBB.") } - alert.addButton(withTitle: "OK") + alert.addButton(withTitle: String(localized: "alert.invalidColor.ok", defaultValue: "OK")) _ = alert.runModal() } private func promptRename() { let alert = NSAlert() - alert.messageText = "Rename Workspace" - alert.informativeText = "Enter a custom name for this workspace." + alert.messageText = String(localized: "alert.renameWorkspace.title", defaultValue: "Rename Workspace") + alert.informativeText = String(localized: "alert.renameWorkspace.message", defaultValue: "Enter a custom name for this workspace.") let input = NSTextField(string: tab.customTitle ?? tab.title) - input.placeholderString = "Workspace name" + input.placeholderString = String(localized: "alert.renameWorkspace.placeholder", defaultValue: "Workspace name") input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Rename") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "alert.renameWorkspace.rename", defaultValue: "Rename")) + alert.addButton(withTitle: String(localized: "alert.renameWorkspace.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input DispatchQueue.main.async { @@ -7633,7 +7671,7 @@ private struct SidebarMetadataRows: View { } if shouldShowToggle { - Button(isExpanded ? "Show less" : "Show more") { + Button(isExpanded ? String(localized: "sidebar.metadata.showLess", defaultValue: "Show less") : String(localized: "sidebar.metadata.showMore", defaultValue: "Show more")) { onFocus() withAnimation(.easeInOut(duration: 0.15)) { isExpanded.toggle() @@ -7786,7 +7824,7 @@ private struct SidebarMetadataMarkdownBlocks: View { } if shouldShowToggle { - Button(isExpanded ? "Show less details" : "Show more details") { + Button(isExpanded ? String(localized: "sidebar.metadata.showLessDetails", defaultValue: "Show less details") : String(localized: "sidebar.metadata.showMoreDetails", defaultValue: "Show more details")) { onFocus() withAnimation(.easeInOut(duration: 0.15)) { isExpanded.toggle() @@ -8479,7 +8517,7 @@ private struct DraggableFolderIcon: View { var body: some View { DraggableFolderIconRepresentable(directory: directory) .frame(width: 16, height: 16) - .help("Drag to open in Finder or another app") + .help(String(localized: "sidebar.folderIcon.dragHint", defaultValue: "Drag to open in Finder or another app")) .onTapGesture(count: 2) { NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: directory) } @@ -8644,7 +8682,7 @@ final class DraggableFolderNSView: NSView, NSDraggingSource { if let volumeName = try? URL(fileURLWithPath: "/").resourceValues(forKeys: [.volumeNameKey]).volumeName { displayName = volumeName } else { - displayName = "Macintosh HD" + displayName = String(localized: "sidebar.pathMenu.macintoshHD", defaultValue: "Macintosh HD") } } else { displayName = FileManager.default.displayName(atPath: pathURL.path) @@ -8908,19 +8946,19 @@ enum SidebarMaterialOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .none: return "None" - case .liquidGlass: return "Liquid Glass (macOS 26+)" - case .sidebar: return "Sidebar" - case .hudWindow: return "HUD Window" - case .menu: return "Menu" - case .popover: return "Popover" - case .underWindowBackground: return "Under Window" - case .windowBackground: return "Window Background" - case .contentBackground: return "Content Background" - case .fullScreenUI: return "Full Screen UI" - case .sheet: return "Sheet" - case .headerView: return "Header View" - case .toolTip: return "Tool Tip" + case .none: return String(localized: "settings.material.none", defaultValue: "None") + case .liquidGlass: return String(localized: "settings.material.liquidGlass", defaultValue: "Liquid Glass (macOS 26+)") + case .sidebar: return String(localized: "settings.material.sidebar", defaultValue: "Sidebar") + case .hudWindow: return String(localized: "settings.material.hudWindow", defaultValue: "HUD Window") + case .menu: return String(localized: "settings.material.menu", defaultValue: "Menu") + case .popover: return String(localized: "settings.material.popover", defaultValue: "Popover") + case .underWindowBackground: return String(localized: "settings.material.underWindow", defaultValue: "Under Window") + case .windowBackground: return String(localized: "settings.material.windowBackground", defaultValue: "Window Background") + case .contentBackground: return String(localized: "settings.material.contentBackground", defaultValue: "Content Background") + case .fullScreenUI: return String(localized: "settings.material.fullScreenUI", defaultValue: "Full Screen UI") + case .sheet: return String(localized: "settings.material.sheet", defaultValue: "Sheet") + case .headerView: return String(localized: "settings.material.headerView", defaultValue: "Header View") + case .toolTip: return String(localized: "settings.material.toolTip", defaultValue: "Tool Tip") } } @@ -8956,8 +8994,8 @@ enum SidebarBlendModeOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .behindWindow: return "Behind Window" - case .withinWindow: return "Within Window" + case .behindWindow: return String(localized: "settings.blendMode.behindWindow", defaultValue: "Behind Window") + case .withinWindow: return String(localized: "settings.blendMode.withinWindow", defaultValue: "Within Window") } } @@ -8978,9 +9016,9 @@ enum SidebarStateOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .active: return "Active" - case .inactive: return "Inactive" - case .followWindow: return "Follow Window" + case .active: return String(localized: "settings.state.active", defaultValue: "Active") + case .inactive: return String(localized: "settings.state.inactive", defaultValue: "Inactive") + case .followWindow: return String(localized: "settings.state.followWindow", defaultValue: "Follow Window") } } @@ -9005,12 +9043,12 @@ enum SidebarPresetOption: String, CaseIterable, Identifiable { var title: String { switch self { - case .nativeSidebar: return "Native Sidebar" - case .glassBehind: return "Raycast Gray" - case .softBlur: return "Soft Blur" - case .popoverGlass: return "Popover Glass" - case .hudGlass: return "HUD Glass" - case .underWindow: return "Under Window" + case .nativeSidebar: return String(localized: "settings.preset.nativeSidebar", defaultValue: "Native Sidebar") + case .glassBehind: return String(localized: "settings.preset.raycastGray", defaultValue: "Raycast Gray") + case .softBlur: return String(localized: "settings.preset.softBlur", defaultValue: "Soft Blur") + case .popoverGlass: return String(localized: "settings.preset.popoverGlass", defaultValue: "Popover Glass") + case .hudGlass: return String(localized: "settings.preset.hudGlass", defaultValue: "HUD Glass") + case .underWindow: return String(localized: "settings.preset.underWindow", defaultValue: "Under Window") } } diff --git a/Sources/Find/SurfaceSearchOverlay.swift b/Sources/Find/SurfaceSearchOverlay.swift index ae2c63c8..44b37433 100644 --- a/Sources/Find/SurfaceSearchOverlay.swift +++ b/Sources/Find/SurfaceSearchOverlay.swift @@ -74,7 +74,7 @@ struct SurfaceSearchOverlay: View { Image(systemName: "chevron.up") } .buttonStyle(SearchButtonStyle()) - .help("Next match (Return)") + .help(String(localized: "search.nextMatch.help", defaultValue: "Next match (Return)")) Button(action: { #if DEBUG @@ -85,7 +85,7 @@ struct SurfaceSearchOverlay: View { Image(systemName: "chevron.down") } .buttonStyle(SearchButtonStyle()) - .help("Previous match (Shift+Return)") + .help(String(localized: "search.previousMatch.help", defaultValue: "Previous match (Shift+Return)")) Button(action: { #if DEBUG @@ -96,7 +96,7 @@ struct SurfaceSearchOverlay: View { Image(systemName: "xmark") } .buttonStyle(SearchButtonStyle()) - .help("Close (Esc)") + .help(String(localized: "search.close.help", defaultValue: "Close (Esc)")) } .padding(8) .background(.background) @@ -288,7 +288,7 @@ private struct SearchTextFieldRepresentable: NSViewRepresentable { func makeNSView(context: Context) -> SearchNativeTextField { let field = SearchNativeTextField(frame: .zero) field.font = .systemFont(ofSize: NSFont.systemFontSize) - field.placeholderString = "Search" + field.placeholderString = String(localized: "search.placeholder", defaultValue: "Search") field.delegate = context.coordinator field.stringValue = text context.coordinator.parentField = field diff --git a/Sources/KeyboardShortcutSettings.swift b/Sources/KeyboardShortcutSettings.swift index 922fe5f2..dae65482 100644 --- a/Sources/KeyboardShortcutSettings.swift +++ b/Sources/KeyboardShortcutSettings.swift @@ -45,35 +45,35 @@ enum KeyboardShortcutSettings { var label: String { switch self { - case .toggleSidebar: return "Toggle Sidebar" - case .newTab: return "New Workspace" - case .newWindow: return "New Window" - case .closeWindow: return "Close Window" - case .openFolder: return "Open Folder" - case .showNotifications: return "Show Notifications" - case .jumpToUnread: return "Jump to Latest Unread" - case .triggerFlash: return "Flash Focused Panel" - case .nextSurface: return "Next Surface" - case .prevSurface: return "Previous Surface" - case .nextSidebarTab: return "Next Workspace" - case .prevSidebarTab: return "Previous Workspace" - case .renameTab: return "Rename Tab" - case .renameWorkspace: return "Rename Workspace" - case .closeWorkspace: return "Close Workspace" - case .newSurface: return "New Surface" - case .toggleTerminalCopyMode: return "Toggle Terminal Copy Mode" - case .focusLeft: return "Focus Pane Left" - case .focusRight: return "Focus Pane Right" - case .focusUp: return "Focus Pane Up" - case .focusDown: return "Focus Pane Down" - case .splitRight: return "Split Right" - case .splitDown: return "Split Down" - case .toggleSplitZoom: return "Toggle Pane Zoom" - case .splitBrowserRight: return "Split Browser Right" - case .splitBrowserDown: return "Split Browser Down" - case .openBrowser: return "Open Browser" - case .toggleBrowserDeveloperTools: return "Toggle Browser Developer Tools" - case .showBrowserJavaScriptConsole: return "Show Browser JavaScript Console" + case .toggleSidebar: return String(localized: "shortcut.toggleSidebar.label", defaultValue: "Toggle Sidebar") + case .newTab: return String(localized: "shortcut.newWorkspace.label", defaultValue: "New Workspace") + case .newWindow: return String(localized: "shortcut.newWindow.label", defaultValue: "New Window") + case .closeWindow: return String(localized: "shortcut.closeWindow.label", defaultValue: "Close Window") + case .openFolder: return String(localized: "shortcut.openFolder.label", defaultValue: "Open Folder") + case .showNotifications: return String(localized: "shortcut.showNotifications.label", defaultValue: "Show Notifications") + case .jumpToUnread: return String(localized: "shortcut.jumpToUnread.label", defaultValue: "Jump to Latest Unread") + case .triggerFlash: return String(localized: "shortcut.flashFocusedPanel.label", defaultValue: "Flash Focused Panel") + case .nextSurface: return String(localized: "shortcut.nextSurface.label", defaultValue: "Next Surface") + case .prevSurface: return String(localized: "shortcut.previousSurface.label", defaultValue: "Previous Surface") + case .nextSidebarTab: return String(localized: "shortcut.nextWorkspace.label", defaultValue: "Next Workspace") + case .prevSidebarTab: return String(localized: "shortcut.previousWorkspace.label", defaultValue: "Previous Workspace") + case .renameTab: return String(localized: "shortcut.renameTab.label", defaultValue: "Rename Tab") + case .renameWorkspace: return String(localized: "shortcut.renameWorkspace.label", defaultValue: "Rename Workspace") + case .closeWorkspace: return String(localized: "shortcut.closeWorkspace.label", defaultValue: "Close Workspace") + case .newSurface: return String(localized: "shortcut.newSurface.label", defaultValue: "New Surface") + case .toggleTerminalCopyMode: return String(localized: "shortcut.toggleTerminalCopyMode.label", defaultValue: "Toggle Terminal Copy Mode") + case .focusLeft: return String(localized: "shortcut.focusPaneLeft.label", defaultValue: "Focus Pane Left") + case .focusRight: return String(localized: "shortcut.focusPaneRight.label", defaultValue: "Focus Pane Right") + case .focusUp: return String(localized: "shortcut.focusPaneUp.label", defaultValue: "Focus Pane Up") + case .focusDown: return String(localized: "shortcut.focusPaneDown.label", defaultValue: "Focus Pane Down") + case .splitRight: return String(localized: "shortcut.splitRight.label", defaultValue: "Split Right") + case .splitDown: return String(localized: "shortcut.splitDown.label", defaultValue: "Split Down") + case .toggleSplitZoom: return String(localized: "shortcut.togglePaneZoom.label", defaultValue: "Toggle Pane Zoom") + case .splitBrowserRight: return String(localized: "shortcut.splitBrowserRight.label", defaultValue: "Split Browser Right") + case .splitBrowserDown: return String(localized: "shortcut.splitBrowserDown.label", defaultValue: "Split Browser Down") + case .openBrowser: return String(localized: "shortcut.openBrowser.label", defaultValue: "Open Browser") + case .toggleBrowserDeveloperTools: return String(localized: "shortcut.toggleBrowserDevTools.label", defaultValue: "Toggle Browser Developer Tools") + case .showBrowserJavaScriptConsole: return String(localized: "shortcut.showBrowserJSConsole.label", defaultValue: "Show Browser JavaScript Console") } } @@ -474,7 +474,7 @@ private class ShortcutRecorderNSButton: NSButton { func updateTitle() { if isRecording { - title = "Press shortcut…" + title = String(localized: "shortcut.pressShortcut.prompt", defaultValue: "Press shortcut…") } else { title = shortcut.displayString } diff --git a/Sources/NotificationsPage.swift b/Sources/NotificationsPage.swift index 53cc8737..370477f0 100644 --- a/Sources/NotificationsPage.swift +++ b/Sources/NotificationsPage.swift @@ -67,7 +67,7 @@ struct NotificationsPage: View { private var header: some View { HStack { - Text("Notifications") + Text(String(localized: "notifications.title", defaultValue: "Notifications")) .font(.title2) .fontWeight(.semibold) @@ -76,7 +76,7 @@ struct NotificationsPage: View { if !notificationStore.notifications.isEmpty { jumpToUnreadButton - Button("Clear All") { + Button(String(localized: "notifications.clearAll", defaultValue: "Clear All")) { notificationStore.clearAll() } .buttonStyle(.bordered) @@ -91,9 +91,9 @@ struct NotificationsPage: View { Image(systemName: "bell.slash") .font(.system(size: 32)) .foregroundColor(.secondary) - Text("No notifications yet") + Text(String(localized: "notifications.empty.title", defaultValue: "No notifications yet")) .font(.headline) - Text("Desktop notifications will appear here for quick review.") + Text(String(localized: "notifications.empty.description", defaultValue: "Desktop notifications will appear here for quick review.")) .font(.subheadline) .foregroundColor(.secondary) } @@ -107,25 +107,25 @@ struct NotificationsPage: View { AppDelegate.shared?.jumpToLatestUnread() }) { HStack(spacing: 6) { - Text("Jump to Latest Unread") + Text(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread")) ShortcutAnnotation(text: jumpToUnreadShortcut.displayString) } } .buttonStyle(.bordered) .keyboardShortcut(key, modifiers: jumpToUnreadShortcut.eventModifiers) - .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip("Jump to Latest Unread")) + .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread"))) .disabled(!hasUnreadNotifications) } else { Button(action: { AppDelegate.shared?.jumpToLatestUnread() }) { HStack(spacing: 6) { - Text("Jump to Latest Unread") + Text(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread")) ShortcutAnnotation(text: jumpToUnreadShortcut.displayString) } } .buttonStyle(.bordered) - .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip("Jump to Latest Unread")) + .help(KeyboardShortcutSettings.Action.jumpToUnread.tooltip(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread"))) .disabled(!hasUnreadNotifications) } } diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 37d38fd7..41da6553 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -125,11 +125,11 @@ enum BrowserThemeMode: String, CaseIterable, Identifiable { var displayName: String { switch self { case .system: - return "System" + return String(localized: "theme.system", defaultValue: "System") case .light: - return "Light" + return String(localized: "theme.light", defaultValue: "Light") case .dark: - return "Dark" + return String(localized: "theme.dark", defaultValue: "Dark") } } @@ -1474,7 +1474,7 @@ final class BrowserPanel: Panel, ObservableObject { if let url = currentURL { return url.host ?? url.absoluteString } - return "New tab" + return String(localized: "browser.newTab", defaultValue: "New tab") } var displayIcon: String? { @@ -2074,17 +2074,13 @@ final class BrowserPanel: Panel, ObservableObject { let alert = insecureHTTPAlertFactory() alert.alertStyle = .warning - alert.messageText = "Connection isn't secure" - alert.informativeText = """ - \(host) uses plain HTTP, so traffic can be read or modified on the network. - - Open this URL in your default browser, or proceed in cmux. - """ - alert.addButton(withTitle: "Open in Default Browser") - alert.addButton(withTitle: "Proceed in cmux") - alert.addButton(withTitle: "Cancel") + alert.messageText = String(localized: "browser.error.insecure.title", defaultValue: "Connection isn\u{2019}t secure") + alert.informativeText = String(localized: "browser.error.insecure.message", defaultValue: "\(host) uses plain HTTP, so traffic can be read or modified on the network.\n\nOpen this URL in your default browser, or proceed in cmux.") + alert.addButton(withTitle: String(localized: "browser.openInDefaultBrowser", defaultValue: "Open in Default Browser")) + alert.addButton(withTitle: String(localized: "browser.proceedInCmux", defaultValue: "Proceed in cmux")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) alert.showsSuppressionButton = true - alert.suppressionButton?.title = "Always allow this host in cmux" + alert.suppressionButton?.title = String(localized: "browser.alwaysAllowHost", defaultValue: "Always allow this host in cmux") let handleResponse: (NSApplication.ModalResponse) -> Void = { [weak self, weak alert] response in self?.handleInsecureHTTPAlertResponse( @@ -3098,29 +3094,40 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate { case (NSURLErrorDomain, NSURLErrorCannotConnectToHost), (NSURLErrorDomain, NSURLErrorCannotFindHost), (NSURLErrorDomain, NSURLErrorTimedOut): - title = "Can\u{2019}t reach this page" - message = "\(failedURL.isEmpty ? "The site" : failedURL) refused to connect. Check that a server is running on this address." + title = String(localized: "browser.error.cantReach.title", defaultValue: "Can\u{2019}t reach this page") + if failedURL.isEmpty { + message = String(localized: "browser.error.cantReach.messageSite", defaultValue: "The site refused to connect. Check that a server is running on this address.") + } else { + message = String(localized: "browser.error.cantReach.messageURL", defaultValue: "\(failedURL) refused to connect. Check that a server is running on this address.") + } case (NSURLErrorDomain, NSURLErrorNotConnectedToInternet), (NSURLErrorDomain, NSURLErrorNetworkConnectionLost): - title = "No internet connection" - message = "Check your network connection and try again." + title = String(localized: "browser.error.noInternet", defaultValue: "No internet connection") + message = String(localized: "browser.error.checkNetwork", defaultValue: "Check your network connection and try again.") case (NSURLErrorDomain, NSURLErrorSecureConnectionFailed), (NSURLErrorDomain, NSURLErrorServerCertificateUntrusted), (NSURLErrorDomain, NSURLErrorServerCertificateHasUnknownRoot), (NSURLErrorDomain, NSURLErrorServerCertificateHasBadDate), (NSURLErrorDomain, NSURLErrorServerCertificateNotYetValid): - title = "Connection isn\u{2019}t secure" - message = "The certificate for this site is invalid." + title = String(localized: "browser.error.insecure.title", defaultValue: "Connection isn\u{2019}t secure") + message = String(localized: "browser.error.invalidCertificate", defaultValue: "The certificate for this site is invalid.") default: - title = "Can\u{2019}t open this page" + title = String(localized: "browser.error.cantOpen.title", defaultValue: "Can\u{2019}t open this page") message = error.localizedDescription } - let escapedURL = failedURL - .replacingOccurrences(of: "&", with: "&") - .replacingOccurrences(of: "<", with: "<") - .replacingOccurrences(of: ">", with: ">") - .replacingOccurrences(of: "\"", with: """) + let escapeHTML: (String) -> String = { value in + value + .replacingOccurrences(of: "&", with: "&") + .replacingOccurrences(of: "<", with: "<") + .replacingOccurrences(of: ">", with: ">") + .replacingOccurrences(of: "\"", with: """) + } + + let escapedTitle = escapeHTML(title) + let escapedMessage = escapeHTML(message) + let escapedURL = escapeHTML(failedURL) + let escapedReloadLabel = escapeHTML(String(localized: "browser.error.reload", defaultValue: "Reload")) let html = """ @@ -3156,10 +3163,10 @@ private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate {
-

\(title)

-

\(message)

+

\(escapedTitle)

+

\(escapedMessage)

\(escapedURL)
- +
@@ -3334,9 +3341,9 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { private func javaScriptDialogTitle(for webView: WKWebView) -> String { if let absolute = webView.url?.absoluteString, !absolute.isEmpty { - return "The page at \(absolute) says:" + return String(localized: "browser.dialog.pageSaysAt", defaultValue: "The page at \(absolute) says:") } - return "This page says:" + return String(localized: "browser.dialog.pageSays", defaultValue: "This page says:") } private func presentDialog( @@ -3429,7 +3436,7 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { alert.alertStyle = .informational alert.messageText = javaScriptDialogTitle(for: webView) alert.informativeText = message - alert.addButton(withTitle: "OK") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) presentDialog(alert, for: webView) { _ in completionHandler() } } @@ -3443,8 +3450,8 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { alert.alertStyle = .informational alert.messageText = javaScriptDialogTitle(for: webView) alert.informativeText = message - alert.addButton(withTitle: "OK") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) presentDialog(alert, for: webView) { response in completionHandler(response == .alertFirstButtonReturn) } @@ -3461,8 +3468,8 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { alert.alertStyle = .informational alert.messageText = javaScriptDialogTitle(for: webView) alert.informativeText = prompt - alert.addButton(withTitle: "OK") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) let field = NSTextField(frame: NSRect(x: 0, y: 0, width: 320, height: 24)) field.stringValue = defaultText ?? "" diff --git a/Sources/Panels/BrowserPanelView.swift b/Sources/Panels/BrowserPanelView.swift index a08e17a3..eae96a00 100644 --- a/Sources/Panels/BrowserPanelView.swift +++ b/Sources/Panels/BrowserPanelView.swift @@ -504,7 +504,7 @@ struct BrowserPanelView: View { .buttonStyle(OmnibarAddressButtonStyle()) .disabled(!panel.canGoBack) .opacity(panel.canGoBack ? 1.0 : 0.4) - .help("Go Back") + .help(String(localized: "browser.goBack", defaultValue: "Go Back")) Button(action: { #if DEBUG @@ -520,7 +520,7 @@ struct BrowserPanelView: View { .buttonStyle(OmnibarAddressButtonStyle()) .disabled(!panel.canGoForward) .opacity(panel.canGoForward ? 1.0 : 0.4) - .help("Go Forward") + .help(String(localized: "browser.goForward", defaultValue: "Go Forward")) Button(action: { if panel.isLoading { @@ -541,18 +541,18 @@ struct BrowserPanelView: View { .contentShape(Rectangle()) } .buttonStyle(OmnibarAddressButtonStyle()) - .help(panel.isLoading ? "Stop" : "Reload") + .help(panel.isLoading ? String(localized: "browser.stop", defaultValue: "Stop") : String(localized: "browser.reload", defaultValue: "Reload")) if panel.isDownloading { HStack(spacing: 4) { ProgressView() .controlSize(.small) - Text("Downloading...") + Text(String(localized: "browser.downloading", defaultValue: "Downloading...")) .font(.system(size: 11)) .foregroundStyle(.secondary) } .padding(.leading, 6) - .help("Download in progress") + .help(String(localized: "browser.downloadInProgress", defaultValue: "Download in progress")) } } } @@ -570,7 +570,7 @@ struct BrowserPanelView: View { } .buttonStyle(OmnibarAddressButtonStyle()) .frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center) - .help(KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.tooltip("Toggle Developer Tools")) + .help(KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.tooltip(String(localized: "browser.toggleDevTools", defaultValue: "Toggle Developer Tools"))) .accessibilityIdentifier("BrowserToggleDevToolsButton") } @@ -651,7 +651,7 @@ struct BrowserPanelView: View { ), isFocused: $addressBarFocused, inlineCompletion: inlineCompletion, - placeholder: "Search or enter URL", + placeholder: String(localized: "browser.addressBar.placeholder", defaultValue: "Search or enter URL"), onTap: { handleOmnibarTap() }, @@ -2146,7 +2146,7 @@ struct OmnibarSuggestion: Identifiable, Hashable { var trailingBadgeText: String? { switch kind { case .switchToTab: - return "Switch to tab" + return String(localized: "browser.switchToTab", defaultValue: "Switch to tab") default: return nil } @@ -2983,7 +2983,7 @@ private struct OmnibarSuggestionsView: View { .accessibilityElement(children: .contain) .accessibilityRespondsToUserInteraction(true) .accessibilityIdentifier("BrowserOmnibarSuggestions") - .accessibilityLabel("Address bar suggestions") + .accessibilityLabel(String(localized: "browser.addressBarSuggestions", defaultValue: "Address bar suggestions")) } } diff --git a/Sources/Panels/CmuxWebView.swift b/Sources/Panels/CmuxWebView.swift index c330f9ea..a8a9e144 100644 --- a/Sources/Panels/CmuxWebView.swift +++ b/Sources/Panels/CmuxWebView.swift @@ -1133,7 +1133,7 @@ final class CmuxWebView: WKWebView { debugLogContextMenuDownloadCandidate(item, index: index) if !hasDefaultBrowserOpenLinkItem, (item.action == #selector(contextMenuOpenLinkInDefaultBrowser(_:)) - || item.title == "Open Link in Default Browser") { + || item.title == String(localized: "browser.contextMenu.openLinkInDefaultBrowser", defaultValue: "Open Link in Default Browser")) { hasDefaultBrowserOpenLinkItem = true } @@ -1148,7 +1148,7 @@ final class CmuxWebView: WKWebView { // by opening the link as a new surface in the same pane. if item.identifier?.rawValue == "WKMenuItemIdentifierOpenLinkInNewWindow" || item.title.contains("Open Link in New Window") { - item.title = "Open Link in New Tab" + item.title = String(localized: "browser.contextMenu.openLinkInNewTab", defaultValue: "Open Link in New Tab") } if isDownloadImageMenuItem(item) { @@ -1188,7 +1188,7 @@ final class CmuxWebView: WKWebView { if let openLinkInsertionIndex, !hasDefaultBrowserOpenLinkItem { let item = NSMenuItem( - title: "Open Link in Default Browser", + title: String(localized: "browser.contextMenu.openLinkInDefaultBrowser", defaultValue: "Open Link in Default Browser"), action: #selector(contextMenuOpenLinkInDefaultBrowser(_:)), keyEquivalent: "" ) diff --git a/Sources/SocketControlSettings.swift b/Sources/SocketControlSettings.swift index f5a6825f..6a12a955 100644 --- a/Sources/SocketControlSettings.swift +++ b/Sources/SocketControlSettings.swift @@ -18,30 +18,30 @@ enum SocketControlMode: String, CaseIterable, Identifiable { var displayName: String { switch self { case .off: - return "Off" + return String(localized: "socketControl.off.name", defaultValue: "Off") case .cmuxOnly: - return "cmux processes only" + return String(localized: "socketControl.cmuxOnly.name", defaultValue: "cmux processes only") case .automation: - return "Automation mode" + return String(localized: "socketControl.automation.name", defaultValue: "Automation mode") case .password: - return "Password mode" + return String(localized: "socketControl.password.name", defaultValue: "Password mode") case .allowAll: - return "Full open access" + return String(localized: "socketControl.allowAll.name", defaultValue: "Full open access") } } var description: String { switch self { case .off: - return "Disable the local control socket." + return String(localized: "socketControl.off.description", defaultValue: "Disable the local control socket.") case .cmuxOnly: - return "Only processes started inside cmux terminals can send commands." + return String(localized: "socketControl.cmuxOnly.description", defaultValue: "Only processes started inside cmux terminals can send commands.") case .automation: - return "Allow external local automation clients from this macOS user (no ancestry check)." + return String(localized: "socketControl.automation.description", defaultValue: "Allow external local automation clients from this macOS user (no ancestry check).") case .password: - return "Require socket authentication with a password stored in a local file." + return String(localized: "socketControl.password.description", defaultValue: "Require socket authentication with a password stored in a local file.") case .allowAll: - return "Allow any local process and user to connect with no auth. Unsafe." + return String(localized: "socketControl.allowAll.description", defaultValue: "Allow any local process and user to connect with no auth. Unsafe.") } } @@ -183,7 +183,7 @@ enum SocketControlPasswordStore { throw NSError( domain: NSCocoaErrorDomain, code: NSFileNoSuchFileError, - userInfo: [NSLocalizedDescriptionKey: "Unable to resolve socket password file path."] + userInfo: [NSLocalizedDescriptionKey: String(localized: "socketControl.error.passwordFilePath", defaultValue: "Unable to resolve socket password file path.")] ) } let directory = fileURL.deletingLastPathComponent() diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index c4581c62..b89a4d84 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -19,22 +19,22 @@ enum NewWorkspacePlacement: String, CaseIterable, Identifiable { var displayName: String { switch self { case .top: - return "Top" + return String(localized: "workspace.placement.top", defaultValue: "Top") case .afterCurrent: - return "After current" + return String(localized: "workspace.placement.afterCurrent", defaultValue: "After current") case .end: - return "End" + return String(localized: "workspace.placement.end", defaultValue: "End") } } var description: String { switch self { case .top: - return "Insert new workspaces at the top of the list." + return String(localized: "workspace.placement.top.description", defaultValue: "Insert new workspaces at the top of the list.") case .afterCurrent: - return "Insert new workspaces directly after the active workspace." + return String(localized: "workspace.placement.afterCurrent.description", defaultValue: "Insert new workspaces directly after the active workspace.") case .end: - return "Append new workspaces to the bottom of the list." + return String(localized: "workspace.placement.end.description", defaultValue: "Append new workspaces to the bottom of the list.") } } } @@ -72,9 +72,9 @@ enum SidebarActiveTabIndicatorStyle: String, CaseIterable, Identifiable { var displayName: String { switch self { case .leftRail: - return "Left Rail" + return String(localized: "sidebar.indicator.leftRail", defaultValue: "Left Rail") case .solidFill: - return "Solid Fill" + return String(localized: "sidebar.indicator.solidFill", defaultValue: "Solid Fill") } } } @@ -1078,9 +1078,13 @@ class TabManager: ObservableObject { let count = plan.panelIds.count let titleLines = plan.titles.map { "• \($0)" }.joined(separator: "\n") - let message = "This is about to close \(count) tab\(count == 1 ? "" : "s") in this pane:\n\(titleLines)" + let message = if count == 1 { + String(localized: "dialog.closeOtherTabs.message.one", defaultValue: "This will close 1 tab in this pane:\n\(titleLines)") + } else { + String(localized: "dialog.closeOtherTabs.message.other", defaultValue: "This will close \(count) tabs in this pane:\n\(titleLines)") + } guard confirmClose( - title: "Close other tabs?", + title: String(localized: "dialog.closeOtherTabs.title", defaultValue: "Close other tabs?"), message: message, acceptCmdD: false ) else { return } @@ -1120,8 +1124,8 @@ class TabManager: ObservableObject { alert.messageText = title alert.informativeText = message alert.alertStyle = .warning - alert.addButton(withTitle: "Close") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.close", defaultValue: "Close")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) // macOS convention: Cmd+D = confirm destructive close (e.g. "Don't Save"). // We only opt into this for the "close last workspace => close window" path to avoid @@ -1182,15 +1186,15 @@ class TabManager: ObservableObject { if let collapsed, !collapsed.isEmpty { return collapsed } - return "Untitled Tab" + return String(localized: "tab.untitled", defaultValue: "Untitled Tab") } private func closeWorkspaceIfRunningProcess(_ workspace: Workspace) { let willCloseWindow = tabs.count <= 1 if workspaceNeedsConfirmClose(workspace), !confirmClose( - title: "Close workspace?", - message: "This will close the workspace and all of its panels.", + title: String(localized: "dialog.closeWorkspace.title", defaultValue: "Close workspace?"), + message: String(localized: "dialog.closeWorkspace.message", defaultValue: "This will close the workspace and all of its panels."), acceptCmdD: willCloseWindow ) { return @@ -1231,8 +1235,8 @@ class TabManager: ObservableObject { let needsConfirm = workspaceNeedsConfirmClose(tab) if needsConfirm { let message = willCloseWindow - ? "This will close the last tab and close the window." - : "This will close the last tab and close its workspace." + ? String(localized: "dialog.closeLastTabWindow.message", defaultValue: "This will close the last tab and close the window.") + : String(localized: "dialog.closeLastTabWorkspace.message", defaultValue: "This will close the last tab and close its workspace.") #if DEBUG dlog( "surface.close.shortcut.confirm tab=\(tab.id.uuidString.prefix(5)) " + @@ -1240,7 +1244,7 @@ class TabManager: ObservableObject { ) #endif guard confirmClose( - title: "Close tab?", + title: String(localized: "dialog.closeTab.title", defaultValue: "Close tab?"), message: message, acceptCmdD: willCloseWindow ) else { @@ -1272,8 +1276,8 @@ class TabManager: ObservableObject { ) #endif guard confirmClose( - title: "Close tab?", - message: "This will close the current tab.", + title: String(localized: "dialog.closeTab.title", defaultValue: "Close tab?"), + message: String(localized: "dialog.closeTab.message", defaultValue: "This will close the current tab."), acceptCmdD: false ) else { #if DEBUG @@ -1311,8 +1315,8 @@ class TabManager: ObservableObject { if let terminalPanel = tab.terminalPanel(for: surfaceId), terminalPanel.needsConfirmClose() { guard confirmClose( - title: "Close tab?", - message: "This will close the current tab.", + title: String(localized: "dialog.closeTab.title", defaultValue: "Close tab?"), + message: String(localized: "dialog.closeTab.message", defaultValue: "This will close the current tab."), acceptCmdD: false ) else { return } } diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 8985b6ce..06d8a97d 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -545,10 +545,10 @@ final class TerminalNotificationStore: ObservableObject { } let alert = notificationSettingsAlertFactory() - alert.messageText = "Enable Notifications for cmux" - alert.informativeText = "Notifications are disabled for cmux. Enable them in System Settings to see alerts." - alert.addButton(withTitle: "Open Settings") - alert.addButton(withTitle: "Not Now") + alert.messageText = String(localized: "dialog.enableNotifications.title", defaultValue: "Enable Notifications for cmux") + alert.informativeText = String(localized: "dialog.enableNotifications.message", defaultValue: "Notifications are disabled for cmux. Enable them in System Settings to see alerts.") + alert.addButton(withTitle: String(localized: "dialog.enableNotifications.openSettings", defaultValue: "Open Settings")) + alert.addButton(withTitle: String(localized: "dialog.enableNotifications.notNow", defaultValue: "Not Now")) alert.beginSheetModal(for: window) { [weak self] response in guard response == .alertFirstButtonReturn, let url = URL(string: "x-apple.systempreferences:com.apple.preference.notifications") else { diff --git a/Sources/Update/UpdatePill.swift b/Sources/Update/UpdatePill.swift index a976fee5..f8d69aff 100644 --- a/Sources/Update/UpdatePill.swift +++ b/Sources/Update/UpdatePill.swift @@ -72,7 +72,7 @@ struct InstallUpdateMenuItem: View { var body: some View { if model.state.isInstallable { - Button("Install Update and Relaunch") { + Button(String(localized: "update.installAndRelaunch", defaultValue: "Install Update and Relaunch")) { model.state.confirm() } } diff --git a/Sources/Update/UpdatePopoverView.swift b/Sources/Update/UpdatePopoverView.swift index 2b1fc3b1..2361775d 100644 --- a/Sources/Update/UpdatePopoverView.swift +++ b/Sources/Update/UpdatePopoverView.swift @@ -49,17 +49,17 @@ fileprivate struct PermissionRequestView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("Enable automatic updates?") + Text(String(localized: "update.popover.enableAutoUpdates", defaultValue: "Enable automatic updates?")) .font(.system(size: 13, weight: .semibold)) - Text("cmux can automatically check for updates in the background.") + Text(String(localized: "update.popover.autoUpdatesDescription", defaultValue: "cmux can automatically check for updates in the background.")) .font(.system(size: 11)) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) } HStack(spacing: 8) { - Button("Not Now") { + Button(String(localized: "common.notNow", defaultValue: "Not Now")) { request.reply(SUUpdatePermissionResponse( automaticUpdateChecks: false, sendSystemProfile: false)) @@ -69,7 +69,7 @@ fileprivate struct PermissionRequestView: View { Spacer() - Button("Allow") { + Button(String(localized: "common.allow", defaultValue: "Allow")) { request.reply(SUUpdatePermissionResponse( automaticUpdateChecks: true, sendSystemProfile: false)) @@ -92,13 +92,13 @@ fileprivate struct CheckingView: View { HStack(spacing: 10) { ProgressView() .controlSize(.small) - Text("Checking for updates…") + Text(String(localized: "update.popover.checking", defaultValue: "Checking for updates…")) .font(.system(size: 13)) } HStack { Spacer() - Button("Cancel") { + Button(String(localized: "common.cancel", defaultValue: "Cancel")) { checking.cancel() dismiss() } @@ -120,12 +120,12 @@ fileprivate struct UpdateAvailableView: View { VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 8) { - Text("Update Available") + Text(String(localized: "update.popover.updateAvailable", defaultValue: "Update Available")) .font(.system(size: 13, weight: .semibold)) VStack(alignment: .leading, spacing: 4) { HStack(spacing: 6) { - Text("Version:") + Text(String(localized: "update.popover.version", defaultValue: "Version:")) .foregroundColor(.secondary) .frame(width: labelWidth, alignment: .trailing) Text(update.appcastItem.displayVersionString) @@ -134,7 +134,7 @@ fileprivate struct UpdateAvailableView: View { if update.appcastItem.contentLength > 0 { HStack(spacing: 6) { - Text("Size:") + Text(String(localized: "update.popover.size", defaultValue: "Size:")) .foregroundColor(.secondary) .frame(width: labelWidth, alignment: .trailing) Text(ByteCountFormatter.string(fromByteCount: Int64(update.appcastItem.contentLength), countStyle: .file)) @@ -144,7 +144,7 @@ fileprivate struct UpdateAvailableView: View { if let date = update.appcastItem.date { HStack(spacing: 6) { - Text("Released:") + Text(String(localized: "update.popover.released", defaultValue: "Released:")) .foregroundColor(.secondary) .frame(width: labelWidth, alignment: .trailing) Text(date.formatted(date: .abbreviated, time: .omitted)) @@ -156,13 +156,13 @@ fileprivate struct UpdateAvailableView: View { } HStack(spacing: 8) { - Button("Skip") { + Button(String(localized: "common.skip", defaultValue: "Skip")) { update.reply(.skip) dismiss() } .controlSize(.small) - Button("Later") { + Button(String(localized: "common.later", defaultValue: "Later")) { update.reply(.dismiss) dismiss() } @@ -171,7 +171,7 @@ fileprivate struct UpdateAvailableView: View { Spacer() - Button("Install and Relaunch") { + Button(String(localized: "common.installAndRelaunch", defaultValue: "Install and Relaunch")) { update.reply(.install) dismiss() } @@ -214,7 +214,7 @@ fileprivate struct DownloadingView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("Downloading Update") + Text(String(localized: "update.popover.downloadingUpdate", defaultValue: "Downloading Update")) .font(.system(size: 13, weight: .semibold)) if let expectedLength = download.expectedLength, expectedLength > 0 { @@ -233,7 +233,7 @@ fileprivate struct DownloadingView: View { HStack { Spacer() - Button("Cancel") { + Button(String(localized: "common.cancel", defaultValue: "Cancel")) { download.cancel() dismiss() } @@ -250,7 +250,7 @@ fileprivate struct ExtractingView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text("Preparing Update") + Text(String(localized: "update.popover.preparingUpdate", defaultValue: "Preparing Update")) .font(.system(size: 13, weight: .semibold)) VStack(alignment: .leading, spacing: 6) { @@ -271,17 +271,17 @@ fileprivate struct InstallingView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("Restart Required") + Text(String(localized: "update.popover.restartRequired", defaultValue: "Restart Required")) .font(.system(size: 13, weight: .semibold)) - Text("The update is ready. Please restart the application to complete the installation.") + Text(String(localized: "update.popover.restartRequired.message", defaultValue: "The update is ready. Please restart the application to complete the installation.")) .font(.system(size: 11)) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) } HStack { - Button("Restart Later") { + Button(String(localized: "common.restartLater", defaultValue: "Restart Later")) { installing.dismiss() dismiss() } @@ -290,7 +290,7 @@ fileprivate struct InstallingView: View { Spacer() - Button("Restart Now") { + Button(String(localized: "common.restartNow", defaultValue: "Restart Now")) { installing.retryTerminatingApplication() dismiss() } @@ -310,10 +310,10 @@ fileprivate struct NotFoundView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { - Text("No Updates Found") + Text(String(localized: "update.popover.noUpdatesFound", defaultValue: "No Updates Found")) .font(.system(size: 13, weight: .semibold)) - Text("You're already running the latest version.") + Text(String(localized: "update.popover.noUpdatesFound.message", defaultValue: "You're already running the latest version.")) .font(.system(size: 11)) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -321,7 +321,7 @@ fileprivate struct NotFoundView: View { HStack { Spacer() - Button("OK") { + Button(String(localized: "common.ok", defaultValue: "OK")) { notFound.acknowledgement() dismiss() } @@ -363,7 +363,7 @@ fileprivate struct UpdateErrorView: View { } VStack(alignment: .leading, spacing: 6) { - Text("Details") + Text(String(localized: "update.popover.details", defaultValue: "Details")) .font(.system(size: 11, weight: .semibold)) Text(details) .font(.system(size: 10, design: .monospaced)) @@ -373,14 +373,14 @@ fileprivate struct UpdateErrorView: View { } HStack(spacing: 8) { - Button("Copy Details") { + Button(String(localized: "common.copyDetails", defaultValue: "Copy Details")) { let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.setString(details, forType: .string) } .controlSize(.small) - Button("OK") { + Button(String(localized: "common.ok", defaultValue: "OK")) { error.dismiss() dismiss() } @@ -389,7 +389,7 @@ fileprivate struct UpdateErrorView: View { Spacer() - Button("Retry") { + Button(String(localized: "common.retry", defaultValue: "Retry")) { error.retry() dismiss() } diff --git a/Sources/Update/UpdateTitlebarAccessory.swift b/Sources/Update/UpdateTitlebarAccessory.swift index 7ff7d03b..c7a96e0e 100644 --- a/Sources/Update/UpdateTitlebarAccessory.swift +++ b/Sources/Update/UpdateTitlebarAccessory.swift @@ -320,8 +320,8 @@ struct TitlebarControlsView: View { iconLabel(systemName: "sidebar.left", config: config) } .accessibilityIdentifier("titlebarControl.toggleSidebar") - .accessibilityLabel("Toggle Sidebar") - .help(KeyboardShortcutSettings.Action.toggleSidebar.tooltip("Show or hide the sidebar")) + .accessibilityLabel(String(localized: "titlebar.sidebar.accessibilityLabel", defaultValue: "Toggle Sidebar")) + .help(KeyboardShortcutSettings.Action.toggleSidebar.tooltip(String(localized: "titlebar.sidebar.tooltip", defaultValue: "Show or hide the sidebar"))) TitlebarControlButton(config: config, action: { #if DEBUG @@ -347,8 +347,8 @@ struct TitlebarControlsView: View { } .accessibilityIdentifier("titlebarControl.showNotifications") .background(NotificationsAnchorView { viewModel.notificationsAnchorView = $0 }) - .accessibilityLabel("Notifications") - .help(KeyboardShortcutSettings.Action.showNotifications.tooltip("Show notifications")) + .accessibilityLabel(String(localized: "titlebar.notifications.accessibilityLabel", defaultValue: "Notifications")) + .help(KeyboardShortcutSettings.Action.showNotifications.tooltip(String(localized: "titlebar.notifications.tooltip", defaultValue: "Show notifications"))) TitlebarControlButton(config: config, action: { #if DEBUG @@ -359,8 +359,8 @@ struct TitlebarControlsView: View { iconLabel(systemName: "plus", config: config) } .accessibilityIdentifier("titlebarControl.newTab") - .accessibilityLabel("New Workspace") - .help(KeyboardShortcutSettings.Action.newTab.tooltip("New workspace")) + .accessibilityLabel(String(localized: "titlebar.newWorkspace.accessibilityLabel", defaultValue: "New Workspace")) + .help(KeyboardShortcutSettings.Action.newTab.tooltip(String(localized: "titlebar.newWorkspace.tooltip", defaultValue: "New workspace"))) } let paddedContent = content.padding(config.groupPadding) @@ -901,11 +901,11 @@ private struct NotificationsPopoverView: View { var body: some View { VStack(spacing: 0) { HStack { - Text("Notifications") + Text(String(localized: "notifications.title", defaultValue: "Notifications")) .font(.headline) Spacer() if !notificationStore.notifications.isEmpty { - Button("Clear All") { + Button(String(localized: "notifications.clearAll", defaultValue: "Clear All")) { notificationStore.clearAll() } .buttonStyle(.bordered) @@ -921,9 +921,9 @@ private struct NotificationsPopoverView: View { Image(systemName: "bell.slash") .font(.system(size: 28)) .foregroundColor(.secondary) - Text("No notifications yet") + Text(String(localized: "notifications.empty.title", defaultValue: "No notifications yet")) .font(.headline) - Text("Desktop notifications will appear here.") + Text(String(localized: "notifications.empty.subtitle", defaultValue: "Desktop notifications will appear here.")) .font(.subheadline) .foregroundColor(.secondary) } diff --git a/Sources/Update/UpdateViewModel.swift b/Sources/Update/UpdateViewModel.swift index dd8a6697..4bdb9ad2 100644 --- a/Sources/Update/UpdateViewModel.swift +++ b/Sources/Update/UpdateViewModel.swift @@ -22,27 +22,29 @@ class UpdateViewModel: ObservableObject { case .idle: return "" case .permissionRequest: - return "Enable Automatic Updates?" + return String(localized: "update.permissionRequest.text", defaultValue: "Enable Automatic Updates?") case .checking: - return "Checking for Updates…" + return String(localized: "update.checking", defaultValue: "Checking for Updates…") case .updateAvailable(let update): let version = update.appcastItem.displayVersionString if !version.isEmpty { - return "Update Available: \(version)" + return String(localized: "update.available.withVersion", defaultValue: "Update Available: \(version)") } - return "Update Available" + return String(localized: "update.available.short", defaultValue: "Update Available") case .downloading(let download): if let expectedLength = download.expectedLength, expectedLength > 0 { let progress = Double(download.progress) / Double(expectedLength) - return String(format: "Downloading: %.0f%%", progress * 100) + let percent = String(format: "%.0f%%", progress * 100) + return String(localized: "update.downloading.progress", defaultValue: "Downloading: \(percent)") } - return "Downloading…" + return String(localized: "update.downloading.status", defaultValue: "Downloading…") case .extracting(let extracting): - return String(format: "Preparing: %.0f%%", extracting.progress * 100) + let percent = String(format: "%.0f%%", extracting.progress * 100) + return String(localized: "update.extracting.progress", defaultValue: "Preparing: \(percent)") case .installing(let install): - return install.isAutoUpdate ? "Restart to Complete Update" : "Installing…" + return install.isAutoUpdate ? String(localized: "update.restartToComplete", defaultValue: "Restart to Complete Update") : String(localized: "update.installing.status", defaultValue: "Installing…") case .notFound: - return "No Updates Available" + return String(localized: "update.noUpdates.title", defaultValue: "No Updates Available") case .error(let err): return Self.userFacingErrorTitle(for: err.error) } @@ -87,19 +89,19 @@ class UpdateViewModel: ObservableObject { case .idle: return "" case .permissionRequest: - return "Configure automatic update preferences" + return String(localized: "update.configureAutoUpdates", defaultValue: "Configure automatic update preferences") case .checking: - return "Please wait while we check for available updates" + return String(localized: "update.pleaseWait", defaultValue: "Please wait while we check for available updates") case .updateAvailable(let update): - return update.releaseNotes?.label ?? "Download and install the latest version" + return update.releaseNotes?.label ?? String(localized: "update.downloadAndInstall", defaultValue: "Download and install the latest version") case .downloading: - return "Downloading the update package" + return String(localized: "update.downloadingPackage", defaultValue: "Downloading the update package") case .extracting: - return "Extracting and preparing the update" + return String(localized: "update.preparingUpdate", defaultValue: "Extracting and preparing the update") case let .installing(install): - return install.isAutoUpdate ? "Restart to Complete Update" : "Installing update and preparing to restart" + return install.isAutoUpdate ? String(localized: "update.restartToComplete", defaultValue: "Restart to Complete Update") : String(localized: "update.installingAndRestarting", defaultValue: "Installing update and preparing to restart") case .notFound: - return "You are running the latest version" + return String(localized: "update.noUpdates.message", defaultValue: "You are running the latest version") case .error(let err): return Self.userFacingErrorMessage(for: err.error) } @@ -177,21 +179,21 @@ class UpdateViewModel: ObservableObject { if let networkError = networkError(from: nsError) { switch networkError.code { case NSURLErrorNotConnectedToInternet: - return "No Internet Connection" + return String(localized: "update.error.noInternet.title", defaultValue: "No Internet Connection") case NSURLErrorTimedOut: - return "Update Timed Out" + return String(localized: "update.error.timedOut.title", defaultValue: "Update Timed Out") case NSURLErrorCannotFindHost: - return "Server Not Found" + return String(localized: "update.error.serverNotFound.title", defaultValue: "Server Not Found") case NSURLErrorCannotConnectToHost: - return "Server Unreachable" + return String(localized: "update.error.serverUnreachable.title", defaultValue: "Server Unreachable") case NSURLErrorNetworkConnectionLost: - return "Connection Lost" + return String(localized: "update.error.connectionLost.title", defaultValue: "Connection Lost") case NSURLErrorSecureConnectionFailed, NSURLErrorServerCertificateUntrusted, NSURLErrorServerCertificateHasBadDate, NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorServerCertificateNotYetValid: - return "Secure Connection Failed" + return String(localized: "update.error.secureConnectionFailed.title", defaultValue: "Secure Connection Failed") default: break } @@ -199,24 +201,24 @@ class UpdateViewModel: ObservableObject { if nsError.domain == SUSparkleErrorDomain { switch nsError.code { case 4005: - return "Updater Permission Error" + return String(localized: "update.error.permissionError.title", defaultValue: "Updater Permission Error") case 2001: - return "Couldn't Download Update" + return String(localized: "update.error.downloadFailed.title", defaultValue: "Couldn't Download Update") case 1000, 1002: - return "Update Feed Error" + return String(localized: "update.error.feedError.title", defaultValue: "Update Feed Error") case 4: - return "Invalid Update Feed" + return String(localized: "update.error.invalidFeed.title", defaultValue: "Invalid Update Feed") case 3: - return "Insecure Update Feed" + return String(localized: "update.error.insecureFeed.title", defaultValue: "Insecure Update Feed") case 1, 2, 3001, 3002: - return "Update Signature Error" + return String(localized: "update.error.signatureError.title", defaultValue: "Update Signature Error") case 1003, 1005: - return "App Location Issue" + return String(localized: "update.error.appLocation.title", defaultValue: "App Location Issue") default: break } } - return "Update Failed" + return String(localized: "update.error.failed.title", defaultValue: "Update Failed") } static func userFacingErrorMessage(for error: Swift.Error) -> String { @@ -224,21 +226,21 @@ class UpdateViewModel: ObservableObject { if let networkError = networkError(from: nsError) { switch networkError.code { case NSURLErrorNotConnectedToInternet: - return "cmux can’t reach the update server. Check your internet connection and try again." + return String(localized: "update.error.noInternet.message", defaultValue: "cmux can’t reach the update server. Check your internet connection and try again.") case NSURLErrorTimedOut: - return "The update server took too long to respond. Try again in a moment." + return String(localized: "update.error.timedOut.message", defaultValue: "The update server took too long to respond. Try again in a moment.") case NSURLErrorCannotFindHost: - return "The update server can’t be found. Check your connection or try again later." + return String(localized: "update.error.serverNotFound.message", defaultValue: "The update server can’t be found. Check your connection or try again later.") case NSURLErrorCannotConnectToHost: - return "cmux couldn’t connect to the update server. Check your connection or try again later." + return String(localized: "update.error.serverUnreachable.message", defaultValue: "cmux couldn’t connect to the update server. Check your connection or try again later.") case NSURLErrorNetworkConnectionLost: - return "The network connection was lost while checking for updates. Try again." + return String(localized: "update.error.connectionLost.message", defaultValue: "The network connection was lost while checking for updates. Try again.") case NSURLErrorSecureConnectionFailed, NSURLErrorServerCertificateUntrusted, NSURLErrorServerCertificateHasBadDate, NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorServerCertificateNotYetValid: - return "A secure connection to the update server couldn’t be established. Try again later." + return String(localized: "update.error.secureConnectionFailed.message", defaultValue: "A secure connection to the update server couldn’t be established. Try again later.") default: break } @@ -246,17 +248,17 @@ class UpdateViewModel: ObservableObject { if nsError.domain == SUSparkleErrorDomain { switch nsError.code { case 2001: - return "cmux couldn't download the update feed. Check your connection and try again." + return String(localized: "update.error.feedDownload.message", defaultValue: "cmux couldn't download the update feed. Check your connection and try again.") case 1000, 1002: - return "The update feed could not be read. Please try again later." + return String(localized: "update.error.feedRead.message", defaultValue: "The update feed could not be read. Please try again later.") case 4: - return "The update feed URL is invalid. Please contact support." + return String(localized: "update.error.invalidFeed.message", defaultValue: "The update feed URL is invalid. Please contact support.") case 3: - return "The update feed is insecure. Please contact support." + return String(localized: "update.error.insecureFeed.message", defaultValue: "The update feed is insecure. Please contact support.") case 1, 2, 3001, 3002: - return "The update's signature could not be verified. Please try again later." + return String(localized: "update.error.signatureError.message", defaultValue: "The update's signature could not be verified. Please try again later.") case 1003, 1005, 4005: - return "Move cmux into Applications and relaunch to enable updates." + return String(localized: "update.error.permissionError.message", defaultValue: "Move cmux into Applications and relaunch to enable updates.") default: break } @@ -487,8 +489,8 @@ enum UpdateState: Equatable { var label: String { switch self { - case .commit: return "View GitHub Commit" - case .tagged: return "View Release Notes" + case .commit: return String(localized: "update.viewGitHubCommit", defaultValue: "View GitHub Commit") + case .tagged: return String(localized: "update.viewReleaseNotes", defaultValue: "View Release Notes") } } } diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 533f6580..010310b4 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -990,10 +990,10 @@ final class Workspace: Identifiable, ObservableObject { private static func currentSplitButtonTooltips() -> BonsplitConfiguration.SplitButtonTooltips { BonsplitConfiguration.SplitButtonTooltips( - newTerminal: KeyboardShortcutSettings.Action.newSurface.tooltip("New Terminal"), - newBrowser: KeyboardShortcutSettings.Action.openBrowser.tooltip("New Browser"), - splitRight: KeyboardShortcutSettings.Action.splitRight.tooltip("Split Right"), - splitDown: KeyboardShortcutSettings.Action.splitDown.tooltip("Split Down") + newTerminal: KeyboardShortcutSettings.Action.newSurface.tooltip(String(localized: "workspace.tooltip.newTerminal", defaultValue: "New Terminal")), + newBrowser: KeyboardShortcutSettings.Action.openBrowser.tooltip(String(localized: "workspace.tooltip.newBrowser", defaultValue: "New Browser")), + splitRight: KeyboardShortcutSettings.Action.splitRight.tooltip(String(localized: "workspace.tooltip.splitRight", defaultValue: "Split Right")), + splitDown: KeyboardShortcutSettings.Action.splitDown.tooltip(String(localized: "workspace.tooltip.splitDown", defaultValue: "Split Down")) ) } @@ -3331,15 +3331,15 @@ final class Workspace: Identifiable, ObservableObject { let panel = panels[panelId] else { return } let alert = NSAlert() - alert.messageText = "Rename Tab" - alert.informativeText = "Enter a custom name for this tab." + alert.messageText = String(localized: "dialog.renameTab.title", defaultValue: "Rename Tab") + alert.informativeText = String(localized: "dialog.renameTab.message", defaultValue: "Enter a custom name for this tab.") let currentTitle = panelCustomTitles[panelId] ?? panelTitles[panelId] ?? panel.displayTitle let input = NSTextField(string: currentTitle) - input.placeholderString = "Tab name" + input.placeholderString = String(localized: "dialog.renameTab.placeholder", defaultValue: "Tab name") input.frame = NSRect(x: 0, y: 0, width: 240, height: 22) alert.accessoryView = input - alert.addButton(withTitle: "Rename") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "common.rename", defaultValue: "Rename")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) let alertWindow = alert.window alertWindow.initialFirstResponder = input DispatchQueue.main.async { @@ -3368,24 +3368,24 @@ final class Workspace: Identifiable, ObservableObject { ) var options: [(title: String, destination: PanelMoveDestination)] = [ - ("New Workspace in Current Window", .newWorkspaceInCurrentWindow), - ("Selected Workspace in New Window", .selectedWorkspaceInNewWindow), + (String(localized: "dialog.moveTab.newWorkspaceCurrentWindow", defaultValue: "New Workspace in Current Window"), .newWorkspaceInCurrentWindow), + (String(localized: "dialog.moveTab.selectedWorkspaceNewWindow", defaultValue: "Selected Workspace in New Window"), .selectedWorkspaceInNewWindow), ] options.append(contentsOf: workspaceTargets.map { target in (target.label, .existingWorkspace(target.workspaceId)) }) let alert = NSAlert() - alert.messageText = "Move Tab" - alert.informativeText = "Choose a destination for this tab." + alert.messageText = String(localized: "dialog.moveTab.title", defaultValue: "Move Tab") + alert.informativeText = String(localized: "dialog.moveTab.message", defaultValue: "Choose a destination for this tab.") let popup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 320, height: 26), pullsDown: false) for option in options { popup.addItem(withTitle: option.title) } popup.selectItem(at: 0) alert.accessoryView = popup - alert.addButton(withTitle: "Move") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "dialog.moveTab.move", defaultValue: "Move")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) guard alert.runModal() == .alertFirstButtonReturn else { return } let selectedIndex = max(0, min(popup.indexOfSelectedItem, options.count - 1)) @@ -3431,9 +3431,9 @@ final class Workspace: Identifiable, ObservableObject { if !moved { let failure = NSAlert() failure.alertStyle = .warning - failure.messageText = "Move Failed" - failure.informativeText = "cmux could not move this tab to the selected destination." - failure.addButton(withTitle: "OK") + failure.messageText = String(localized: "dialog.moveFailed.title", defaultValue: "Move Failed") + failure.informativeText = String(localized: "dialog.moveFailed.message", defaultValue: "cmux could not move this tab to the selected destination.") + failure.addButton(withTitle: String(localized: "common.ok", defaultValue: "OK")) _ = failure.runModal() } } @@ -3500,11 +3500,11 @@ extension Workspace: BonsplitDelegate { @MainActor private func confirmClosePanel(for tabId: TabID) async -> Bool { let alert = NSAlert() - alert.messageText = "Close tab?" - alert.informativeText = "This will close the current tab." + alert.messageText = String(localized: "dialog.closeTab.title", defaultValue: "Close tab?") + alert.informativeText = String(localized: "dialog.closeTab.message", defaultValue: "This will close the current tab.") alert.alertStyle = .warning - alert.addButton(withTitle: "Close") - alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: String(localized: "dialog.closeTab.close", defaultValue: "Close")) + alert.addButton(withTitle: String(localized: "common.cancel", defaultValue: "Cancel")) // Prefer a sheet if we can find a window, otherwise fall back to modal. if let window = NSApp.keyWindow ?? NSApp.mainWindow { diff --git a/Sources/cmuxApp.swift b/Sources/cmuxApp.swift index 8ef4d5ab..f6ca3ebf 100644 --- a/Sources/cmuxApp.swift +++ b/Sources/cmuxApp.swift @@ -220,25 +220,25 @@ struct cmuxApp: App { .windowStyle(.hiddenTitleBar) .commands { CommandGroup(replacing: .appSettings) { - Button("Settings…") { + Button(String(localized: "menu.app.settings", defaultValue: "Settings…")) { appDelegate.openPreferencesWindow(debugSource: "menu.cmdComma") } .keyboardShortcut(",", modifiers: .command) } CommandGroup(replacing: .appInfo) { - Button("About cmux") { + Button(String(localized: "menu.app.about", defaultValue: "About cmux")) { showAboutPanel() } - Button("Ghostty Settings…") { + Button(String(localized: "menu.app.ghosttySettings", defaultValue: "Ghostty Settings…")) { GhosttyApp.shared.openConfigurationInTextEdit() } - Button("Reload Configuration") { + Button(String(localized: "menu.app.reloadConfiguration", defaultValue: "Reload Configuration")) { GhosttyApp.shared.reloadConfiguration(source: "menu.reload_configuration") } .keyboardShortcut(",", modifiers: [.command, .shift]) Divider() - Button("Check for Updates…") { + Button(String(localized: "menu.app.checkForUpdates", defaultValue: "Check for Updates…")) { appDelegate.checkForUpdates(nil) } InstallUpdateMenuItem(model: appDelegate.updateViewModel) @@ -264,16 +264,16 @@ struct cmuxApp: App { } #endif - CommandMenu("Update Logs") { - Button("Copy Update Logs") { + CommandMenu(String(localized: "menu.updateLogs.title", defaultValue: "Update Logs")) { + Button(String(localized: "menu.updateLogs.copyUpdateLogs", defaultValue: "Copy Update Logs")) { appDelegate.copyUpdateLogs(nil) } - Button("Copy Focus Logs") { + Button(String(localized: "menu.updateLogs.copyFocusLogs", defaultValue: "Copy Focus Logs")) { appDelegate.copyFocusLogs(nil) } } - CommandMenu("Notifications") { + CommandMenu(String(localized: "menu.notifications.title", defaultValue: "Notifications")) { let snapshot = notificationMenuSnapshot Button(snapshot.stateHintTitle) {} @@ -291,21 +291,21 @@ struct cmuxApp: App { Divider() } - splitCommandButton(title: "Show Notifications", shortcut: showNotificationsMenuShortcut) { + splitCommandButton(title: String(localized: "menu.notifications.show", defaultValue: "Show Notifications"), shortcut: showNotificationsMenuShortcut) { showNotificationsPopover() } - splitCommandButton(title: "Jump to Latest Unread", shortcut: jumpToUnreadMenuShortcut) { + splitCommandButton(title: String(localized: "menu.notifications.jumpToUnread", defaultValue: "Jump to Latest Unread"), shortcut: jumpToUnreadMenuShortcut) { appDelegate.jumpToLatestUnread() } .disabled(!snapshot.hasUnreadNotifications) - Button("Mark All Read") { + Button(String(localized: "menu.notifications.markAllRead", defaultValue: "Mark All Read")) { notificationStore.markAllRead() } .disabled(!snapshot.hasUnreadNotifications) - Button("Clear All") { + Button(String(localized: "menu.notifications.clearAll", defaultValue: "Clear All")) { notificationStore.clearAll() } .disabled(!snapshot.hasNotifications) @@ -375,11 +375,11 @@ struct cmuxApp: App { // New tab commands CommandGroup(replacing: .newItem) { - splitCommandButton(title: "New Window", shortcut: newWindowMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.newWindow", defaultValue: "New Window"), shortcut: newWindowMenuShortcut) { appDelegate.openNewMainWindow(nil) } - splitCommandButton(title: "New Workspace", shortcut: newWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.newWorkspace", defaultValue: "New Workspace"), shortcut: newWorkspaceMenuShortcut) { if let appDelegate = AppDelegate.shared { if appDelegate.addWorkspaceInPreferredMainWindow(debugSource: "menu.newWorkspace") == nil { #if DEBUG @@ -394,13 +394,13 @@ struct cmuxApp: App { } } - splitCommandButton(title: "Open Folder…", shortcut: openFolderMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.openFolder", defaultValue: "Open Folder…"), shortcut: openFolderMenuShortcut) { let panel = NSOpenPanel() panel.canChooseFiles = false panel.canChooseDirectories = true panel.allowsMultipleSelection = false - panel.title = "Open Folder" - panel.prompt = "Open" + panel.title = String(localized: "menu.file.openFolder.panelTitle", defaultValue: "Open Folder") + panel.prompt = String(localized: "menu.file.openFolder.panelPrompt", defaultValue: "Open") if panel.runModal() == .OK, let url = panel.url { if let appDelegate = AppDelegate.shared { if appDelegate.addWorkspaceInPreferredMainWindow( @@ -418,13 +418,13 @@ struct cmuxApp: App { // Close tab/workspace CommandGroup(after: .newItem) { - Button("Go to Workspace…") { + Button(String(localized: "menu.file.goToWorkspace", defaultValue: "Go to Workspace…")) { let targetWindow = NSApp.keyWindow ?? NSApp.mainWindow NotificationCenter.default.post(name: .commandPaletteSwitcherRequested, object: targetWindow) } .keyboardShortcut("p", modifiers: [.command]) - Button("Command Palette…") { + Button(String(localized: "menu.file.commandPalette", defaultValue: "Command Palette…")) { let targetWindow = NSApp.keyWindow ?? NSApp.mainWindow NotificationCenter.default.post(name: .commandPaletteRequested, object: targetWindow) } @@ -435,12 +435,12 @@ struct cmuxApp: App { // Terminal semantics: // Cmd+W closes the focused tab (with confirmation if needed). If this is the last // tab in the last workspace, it closes the window. - Button("Close Tab") { + Button(String(localized: "menu.file.closeTab", defaultValue: "Close Tab")) { closePanelOrWindow() } .keyboardShortcut("w", modifiers: .command) - Button("Close Other Tabs in Pane") { + Button(String(localized: "menu.file.closeOtherTabs", defaultValue: "Close Other Tabs in Pane")) { closeOtherTabsInFocusedPane() } .keyboardShortcut("t", modifiers: [.command, .option]) @@ -448,11 +448,11 @@ struct cmuxApp: App { // Cmd+Shift+W closes the current workspace (with confirmation if needed). If this // is the last workspace, it closes the window. - splitCommandButton(title: "Close Workspace", shortcut: closeWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.file.closeWorkspace", defaultValue: "Close Workspace"), shortcut: closeWorkspaceMenuShortcut) { closeTabOrWindow() } - Button("Reopen Closed Browser Panel") { + Button(String(localized: "menu.file.reopenClosedBrowserPanel", defaultValue: "Reopen Closed Browser Panel")) { _ = activeTabManager.reopenMostRecentlyClosedBrowserPanel() } .keyboardShortcut("t", modifiers: [.command, .shift]) @@ -460,8 +460,8 @@ struct cmuxApp: App { // Find CommandGroup(after: .textEditing) { - Menu("Find") { - Button("Find…") { + Menu(String(localized: "menu.find.title", defaultValue: "Find")) { + Button(String(localized: "menu.find.find", defaultValue: "Find…")) { #if DEBUG dlog("find.menu Cmd+F fired") #endif @@ -469,19 +469,19 @@ struct cmuxApp: App { } .keyboardShortcut("f", modifiers: .command) - Button("Find Next") { + Button(String(localized: "menu.find.findNext", defaultValue: "Find Next")) { activeTabManager.findNext() } .keyboardShortcut("g", modifiers: .command) - Button("Find Previous") { + Button(String(localized: "menu.find.findPrevious", defaultValue: "Find Previous")) { activeTabManager.findPrevious() } .keyboardShortcut("g", modifiers: [.command, .shift]) Divider() - Button("Hide Find Bar") { + Button(String(localized: "menu.find.hideFindBar", defaultValue: "Hide Find Bar")) { activeTabManager.hideFind() } .keyboardShortcut("f", modifiers: [.command, .shift]) @@ -489,7 +489,7 @@ struct cmuxApp: App { Divider() - Button("Use Selection for Find") { + Button(String(localized: "menu.find.useSelectionForFind", defaultValue: "Use Selection for Find")) { activeTabManager.searchSelection() } .keyboardShortcut("e", modifiers: .command) @@ -499,7 +499,7 @@ struct cmuxApp: App { // Tab navigation CommandGroup(after: .toolbar) { - splitCommandButton(title: "Toggle Sidebar", shortcut: toggleSidebarMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.toggleSidebar", defaultValue: "Toggle Sidebar"), shortcut: toggleSidebarMenuShortcut) { if AppDelegate.shared?.toggleSidebarInActiveMainWindow() != true { sidebarState.toggle() } @@ -507,89 +507,89 @@ struct cmuxApp: App { Divider() - splitCommandButton(title: "Next Surface", shortcut: nextSurfaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.nextSurface", defaultValue: "Next Surface"), shortcut: nextSurfaceMenuShortcut) { activeTabManager.selectNextSurface() } - splitCommandButton(title: "Previous Surface", shortcut: prevSurfaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.previousSurface", defaultValue: "Previous Surface"), shortcut: prevSurfaceMenuShortcut) { activeTabManager.selectPreviousSurface() } - Button("Back") { + Button(String(localized: "menu.view.back", defaultValue: "Back")) { activeTabManager.focusedBrowserPanel?.goBack() } .keyboardShortcut("[", modifiers: .command) - Button("Forward") { + Button(String(localized: "menu.view.forward", defaultValue: "Forward")) { activeTabManager.focusedBrowserPanel?.goForward() } .keyboardShortcut("]", modifiers: .command) - Button("Reload Page") { + Button(String(localized: "menu.view.reloadPage", defaultValue: "Reload Page")) { activeTabManager.focusedBrowserPanel?.reload() } .keyboardShortcut("r", modifiers: .command) - splitCommandButton(title: "Toggle Developer Tools", shortcut: toggleBrowserDeveloperToolsMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.toggleDevTools", defaultValue: "Toggle Developer Tools"), shortcut: toggleBrowserDeveloperToolsMenuShortcut) { let manager = activeTabManager if !manager.toggleDeveloperToolsFocusedBrowser() { NSSound.beep() } } - splitCommandButton(title: "Show JavaScript Console", shortcut: showBrowserJavaScriptConsoleMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.showJSConsole", defaultValue: "Show JavaScript Console"), shortcut: showBrowserJavaScriptConsoleMenuShortcut) { let manager = activeTabManager if !manager.showJavaScriptConsoleFocusedBrowser() { NSSound.beep() } } - Button("Zoom In") { + Button(String(localized: "menu.view.zoomIn", defaultValue: "Zoom In")) { _ = activeTabManager.zoomInFocusedBrowser() } .keyboardShortcut("=", modifiers: .command) - Button("Zoom Out") { + Button(String(localized: "menu.view.zoomOut", defaultValue: "Zoom Out")) { _ = activeTabManager.zoomOutFocusedBrowser() } .keyboardShortcut("-", modifiers: .command) - Button("Actual Size") { + Button(String(localized: "menu.view.actualSize", defaultValue: "Actual Size")) { _ = activeTabManager.resetZoomFocusedBrowser() } .keyboardShortcut("0", modifiers: .command) - Button("Clear Browser History") { + Button(String(localized: "menu.view.clearBrowserHistory", defaultValue: "Clear Browser History")) { BrowserHistoryStore.shared.clearHistory() } - splitCommandButton(title: "Next Workspace", shortcut: nextWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.nextWorkspace", defaultValue: "Next Workspace"), shortcut: nextWorkspaceMenuShortcut) { activeTabManager.selectNextTab() } - splitCommandButton(title: "Previous Workspace", shortcut: prevWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.previousWorkspace", defaultValue: "Previous Workspace"), shortcut: prevWorkspaceMenuShortcut) { activeTabManager.selectPreviousTab() } - splitCommandButton(title: "Rename Workspace…", shortcut: renameWorkspaceMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.renameWorkspace", defaultValue: "Rename Workspace…"), shortcut: renameWorkspaceMenuShortcut) { _ = AppDelegate.shared?.requestRenameWorkspaceViaCommandPalette() } Divider() - splitCommandButton(title: "Split Right", shortcut: splitRightMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitRight", defaultValue: "Split Right"), shortcut: splitRightMenuShortcut) { performSplitFromMenu(direction: .right) } - splitCommandButton(title: "Split Down", shortcut: splitDownMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitDown", defaultValue: "Split Down"), shortcut: splitDownMenuShortcut) { performSplitFromMenu(direction: .down) } - splitCommandButton(title: "Split Browser Right", shortcut: splitBrowserRightMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitBrowserRight", defaultValue: "Split Browser Right"), shortcut: splitBrowserRightMenuShortcut) { performBrowserSplitFromMenu(direction: .right) } - splitCommandButton(title: "Split Browser Down", shortcut: splitBrowserDownMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.splitBrowserDown", defaultValue: "Split Browser Down"), shortcut: splitBrowserDownMenuShortcut) { performBrowserSplitFromMenu(direction: .down) } @@ -597,7 +597,7 @@ struct cmuxApp: App { // Cmd+1 through Cmd+9 for workspace selection (9 = last workspace) ForEach(1...9, id: \.self) { number in - Button("Workspace \(number)") { + Button(String(localized: "menu.view.workspace", defaultValue: "Workspace \(number)")) { let manager = activeTabManager if let targetIndex = WorkspaceShortcutMapper.workspaceIndex(forCommandDigit: number, workspaceCount: manager.tabs.count) { manager.selectTab(at: targetIndex) @@ -608,11 +608,11 @@ struct cmuxApp: App { Divider() - splitCommandButton(title: "Jump to Latest Unread", shortcut: jumpToUnreadMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.jumpToUnread", defaultValue: "Jump to Latest Unread"), shortcut: jumpToUnreadMenuShortcut) { AppDelegate.shared?.jumpToLatestUnread() } - splitCommandButton(title: "Show Notifications", shortcut: showNotificationsMenuShortcut) { + splitCommandButton(title: String(localized: "menu.view.showNotifications", defaultValue: "Show Notifications"), shortcut: showNotificationsMenuShortcut) { showNotificationsPopover() } } @@ -1707,7 +1707,7 @@ private final class AcknowledgmentsWindowController: NSWindowController, NSWindo defer: false ) window.isReleasedWhenClosed = false - window.title = "Third-Party Licenses" + window.title = String(localized: "about.licenses.windowTitle", defaultValue: "Third-Party Licenses") window.identifier = NSUserInterfaceItemIdentifier("cmux.licenses") window.center() window.contentView = NSHostingView(rootView: AcknowledgmentsView()) @@ -1732,7 +1732,7 @@ private struct AcknowledgmentsView: View { let text = try? String(contentsOf: url) { return text } - return "Licenses file not found." + return String(localized: "about.licenses.notFound", defaultValue: "Licenses file not found.") }() var body: some View { @@ -1848,10 +1848,10 @@ private struct AboutPanelView: View { VStack(alignment: .center, spacing: 32) { VStack(alignment: .center, spacing: 8) { - Text("cmux") + Text(String(localized: "about.appName", defaultValue: "cmux")) .bold() .font(.title) - Text("A Ghostty-based terminal with vertical tabs\nand a notification panel for macOS.") + Text(String(localized: "about.description", defaultValue: "A Ghostty-based terminal with vertical tabs\nand a notification panel for macOS.")) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .font(.caption) @@ -1862,31 +1862,31 @@ private struct AboutPanelView: View { VStack(spacing: 2) { if let version { - AboutPropertyRow(label: "Version", text: version) + AboutPropertyRow(label: String(localized: "about.version", defaultValue: "Version"), text: version) } if let build { - AboutPropertyRow(label: "Build", text: build) + AboutPropertyRow(label: String(localized: "about.build", defaultValue: "Build"), text: build) } let commitText = commit ?? "—" let commitURL = commit.flatMap { hash in URL(string: "https://github.com/manaflow-ai/cmux/commit/\(hash)") } - AboutPropertyRow(label: "Commit", text: commitText, url: commitURL) + AboutPropertyRow(label: String(localized: "about.commit", defaultValue: "Commit"), text: commitText, url: commitURL) } .frame(maxWidth: .infinity) HStack(spacing: 8) { if let url = docsURL { - Button("Docs") { + Button(String(localized: "about.docs", defaultValue: "Docs")) { openURL(url) } } if let url = githubURL { - Button("GitHub") { + Button(String(localized: "about.github", defaultValue: "GitHub")) { openURL(url) } } - Button("Licenses") { + Button(String(localized: "about.licenses", defaultValue: "Licenses")) { AcknowledgmentsWindowController.shared.show() } } @@ -2577,13 +2577,13 @@ enum AppearanceMode: String, CaseIterable, Identifiable { var displayName: String { switch self { case .system: - return "System" + return String(localized: "appearance.system", defaultValue: "System") case .light: - return "Light" + return String(localized: "appearance.light", defaultValue: "Light") case .dark: - return "Dark" + return String(localized: "appearance.dark", defaultValue: "Dark") case .auto: - return "Auto" + return String(localized: "appearance.auto", defaultValue: "Auto") } } } @@ -2622,9 +2622,9 @@ enum AppIconMode: String, CaseIterable, Identifiable { var displayName: String { switch self { - case .automatic: return "Automatic" - case .light: return "Light" - case .dark: return "Dark" + case .automatic: return String(localized: "appIcon.automatic", defaultValue: "Automatic") + case .light: return String(localized: "appIcon.light", defaultValue: "Light") + case .dark: return String(localized: "appIcon.dark", defaultValue: "Dark") } } @@ -2840,11 +2840,11 @@ struct SettingsView: View { private var browserHistorySubtitle: String { switch browserHistoryEntryCount { case 0: - return "No saved pages yet." + return String(localized: "settings.browser.history.subtitleEmpty", defaultValue: "No saved pages yet.") case 1: - return "1 saved page appears in omnibar suggestions." + return String(localized: "settings.browser.history.subtitleOne", defaultValue: "1 saved page appears in omnibar suggestions.") default: - return "\(browserHistoryEntryCount) saved pages appear in omnibar suggestions." + return String(localized: "settings.browser.history.subtitleMany", defaultValue: "\(browserHistoryEntryCount) saved pages appear in omnibar suggestions.") } } @@ -2861,7 +2861,7 @@ struct SettingsView: View { private func saveSocketPassword() { let trimmed = socketPasswordDraft.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - socketPasswordStatusMessage = "Enter a password first." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.enterFirst", defaultValue: "Enter a password first.") socketPasswordStatusIsError = true return } @@ -2869,10 +2869,10 @@ struct SettingsView: View { do { try SocketControlPasswordStore.savePassword(trimmed) socketPasswordDraft = "" - socketPasswordStatusMessage = "Password saved." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.saved", defaultValue: "Password saved.") socketPasswordStatusIsError = false } catch { - socketPasswordStatusMessage = "Failed to save password (\(error.localizedDescription))." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.saveFailed", defaultValue: "Failed to save password (\(error.localizedDescription)).") socketPasswordStatusIsError = true } } @@ -2881,10 +2881,10 @@ struct SettingsView: View { do { try SocketControlPasswordStore.clearPassword() socketPasswordDraft = "" - socketPasswordStatusMessage = "Password cleared." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.cleared", defaultValue: "Password cleared.") socketPasswordStatusIsError = false } catch { - socketPasswordStatusMessage = "Failed to clear password (\(error.localizedDescription))." + socketPasswordStatusMessage = String(localized: "settings.automation.socketPassword.clearFailed", defaultValue: "Failed to clear password (\(error.localizedDescription)).") socketPasswordStatusIsError = true } } @@ -2893,9 +2893,9 @@ struct SettingsView: View { ZStack(alignment: .top) { ScrollView { VStack(alignment: .leading, spacing: 14) { - SettingsSectionHeader(title: "App") + SettingsSectionHeader(title: String(localized: "settings.section.app", defaultValue: "App")) SettingsCard { - SettingsCardRow("Theme", controlWidth: pickerColumnWidth) { + SettingsCardRow(String(localized: "settings.app.theme", defaultValue: "Theme"), controlWidth: pickerColumnWidth) { Picker("", selection: $appearanceMode) { ForEach(AppearanceMode.visibleCases) { mode in Text(mode.displayName).tag(mode.rawValue) @@ -2918,7 +2918,7 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "New Workspace Placement", + String(localized: "settings.app.newWorkspacePlacement", defaultValue: "New Workspace Placement"), subtitle: selectedWorkspacePlacement.description, controlWidth: pickerColumnWidth ) { @@ -2934,8 +2934,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Reorder on Notification", - subtitle: "Move workspaces to the top when they receive a notification. Disable for stable shortcut positions." + String(localized: "settings.app.reorderOnNotification", defaultValue: "Reorder on Notification"), + subtitle: String(localized: "settings.app.reorderOnNotification.subtitle", defaultValue: "Move workspaces to the top when they receive a notification. Disable for stable shortcut positions.") ) { Toggle("", isOn: $workspaceAutoReorder) .labelsHidden() @@ -2945,8 +2945,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Dock Badge", - subtitle: "Show unread count on app icon (Dock and Cmd+Tab)." + String(localized: "settings.app.dockBadge", defaultValue: "Dock Badge"), + subtitle: String(localized: "settings.app.dockBadge.subtitle", defaultValue: "Show unread count on app icon (Dock and Cmd+Tab).") ) { Toggle("", isOn: $notificationDockBadgeEnabled) .labelsHidden() @@ -2994,10 +2994,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Send anonymous telemetry", + String(localized: "settings.app.telemetry", defaultValue: "Send anonymous telemetry"), subtitle: sendAnonymousTelemetry != telemetryValueAtLaunch - ? "Change takes effect on next launch." - : "Share anonymized crash and usage data to help improve cmux." + ? String(localized: "settings.app.telemetry.subtitleChanged", defaultValue: "Change takes effect on next launch.") + : String(localized: "settings.app.telemetry.subtitle", defaultValue: "Share anonymized crash and usage data to help improve cmux.") ) { Toggle("", isOn: $sendAnonymousTelemetry) .labelsHidden() @@ -3007,10 +3007,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Warn Before Quit", + String(localized: "settings.app.warnBeforeQuit", defaultValue: "Warn Before Quit"), subtitle: warnBeforeQuitShortcut - ? "Show a confirmation before quitting with Cmd+Q." - : "Cmd+Q quits immediately without confirmation." + ? String(localized: "settings.app.warnBeforeQuit.subtitleOn", defaultValue: "Show a confirmation before quitting with Cmd+Q.") + : String(localized: "settings.app.warnBeforeQuit.subtitleOff", defaultValue: "Cmd+Q quits immediately without confirmation.") ) { Toggle("", isOn: $warnBeforeQuitShortcut) .labelsHidden() @@ -3020,10 +3020,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Rename Selects Existing Name", + String(localized: "settings.app.renameSelectsName", defaultValue: "Rename Selects Existing Name"), subtitle: commandPaletteRenameSelectAllOnFocus - ? "Command Palette rename starts with all text selected." - : "Command Palette rename keeps the caret at the end." + ? String(localized: "settings.app.renameSelectsName.subtitleOn", defaultValue: "Command Palette rename starts with all text selected.") + : String(localized: "settings.app.renameSelectsName.subtitleOff", defaultValue: "Command Palette rename keeps the caret at the end.") ) { Toggle("", isOn: $commandPaletteRenameSelectAllOnFocus) .labelsHidden() @@ -3033,15 +3033,15 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Sidebar Branch Layout", + String(localized: "settings.app.sidebarBranchLayout", defaultValue: "Sidebar Branch Layout"), subtitle: sidebarBranchVerticalLayout - ? "Vertical: each branch appears on its own line." - : "Inline: all branches share one line.", + ? String(localized: "settings.app.sidebarBranchLayout.subtitleVertical", defaultValue: "Vertical: each branch appears on its own line.") + : String(localized: "settings.app.sidebarBranchLayout.subtitleInline", defaultValue: "Inline: all branches share one line."), controlWidth: pickerColumnWidth ) { Picker("", selection: $sidebarBranchVerticalLayout) { - Text("Vertical").tag(true) - Text("Inline").tag(false) + Text(String(localized: "settings.app.sidebarBranchLayout.vertical", defaultValue: "Vertical")).tag(true) + Text(String(localized: "settings.app.sidebarBranchLayout.inline", defaultValue: "Inline")).tag(false) } .labelsHidden() .pickerStyle(.menu) @@ -3050,8 +3050,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Branch + Directory in Sidebar", - subtitle: "Display the built-in git branch and working-directory row." + String(localized: "settings.app.showBranchDirectory", defaultValue: "Show Branch + Directory in Sidebar"), + subtitle: String(localized: "settings.app.showBranchDirectory.subtitle", defaultValue: "Display the built-in git branch and working-directory row.") ) { Toggle("", isOn: $sidebarShowBranchDirectory) .labelsHidden() @@ -3061,8 +3061,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Pull Requests in Sidebar", - subtitle: "Display review items (PR/MR/etc.) with status, number, and clickable link." + String(localized: "settings.app.showPullRequests", defaultValue: "Show Pull Requests in Sidebar"), + subtitle: String(localized: "settings.app.showPullRequests.subtitle", defaultValue: "Display review items (PR/MR/etc.) with status, number, and clickable link.") ) { Toggle("", isOn: $sidebarShowPullRequest) .labelsHidden() @@ -3072,10 +3072,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Open Sidebar PR Links in cmux Browser", + String(localized: "settings.app.openSidebarPRLinks", defaultValue: "Open Sidebar PR Links in cmux Browser"), subtitle: openSidebarPullRequestLinksInCmuxBrowser - ? "Clicks open inside cmux browser." - : "Clicks open in your default browser." + ? String(localized: "settings.app.openSidebarPRLinks.subtitleOn", defaultValue: "Clicks open inside cmux browser.") + : String(localized: "settings.app.openSidebarPRLinks.subtitleOff", defaultValue: "Clicks open in your default browser.") ) { Toggle("", isOn: $openSidebarPullRequestLinksInCmuxBrowser) .labelsHidden() @@ -3085,8 +3085,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Listening Ports in Sidebar", - subtitle: "Display detected listening ports for the active workspace." + String(localized: "settings.app.showPorts", defaultValue: "Show Listening Ports in Sidebar"), + subtitle: String(localized: "settings.app.showPorts.subtitle", defaultValue: "Display detected listening ports for the active workspace.") ) { Toggle("", isOn: $sidebarShowPorts) .labelsHidden() @@ -3096,8 +3096,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Latest Log in Sidebar", - subtitle: "Display the latest imperative log/status message." + String(localized: "settings.app.showLog", defaultValue: "Show Latest Log in Sidebar"), + subtitle: String(localized: "settings.app.showLog.subtitle", defaultValue: "Display the latest imperative log/status message.") ) { Toggle("", isOn: $sidebarShowLog) .labelsHidden() @@ -3107,8 +3107,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Progress in Sidebar", - subtitle: "Display the built-in progress bar from set_progress." + String(localized: "settings.app.showProgress", defaultValue: "Show Progress in Sidebar"), + subtitle: String(localized: "settings.app.showProgress.subtitle", defaultValue: "Display the built-in progress bar from set_progress.") ) { Toggle("", isOn: $sidebarShowProgress) .labelsHidden() @@ -3118,8 +3118,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Show Custom Metadata in Sidebar", - subtitle: "Display custom metadata from report_meta/set_status and report_meta_block." + String(localized: "settings.app.showMetadata", defaultValue: "Show Custom Metadata in Sidebar"), + subtitle: String(localized: "settings.app.showMetadata.subtitle", defaultValue: "Display custom metadata from report_meta/set_status and report_meta_block.") ) { Toggle("", isOn: $sidebarShowMetadata) .labelsHidden() @@ -3127,10 +3127,10 @@ struct SettingsView: View { } } - SettingsSectionHeader(title: "Workspace Colors") + SettingsSectionHeader(title: String(localized: "settings.section.workspaceColors", defaultValue: "Workspace Colors")) SettingsCard { SettingsCardRow( - "Workspace Color Indicator", + String(localized: "settings.workspaceColors.indicator", defaultValue: "Workspace Color Indicator"), controlWidth: pickerColumnWidth ) { Picker("", selection: sidebarIndicatorStyleSelection) { @@ -3144,7 +3144,7 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("Customize the workspace color palette used by Sidebar > Workspace Color. \"Choose Custom Color...\" entries are persisted below.") + SettingsCardNote(String(localized: "settings.workspaceColors.paletteNote", defaultValue: "Customize the workspace color palette used by Sidebar > Workspace Color. \"Choose Custom Color...\" entries are persisted below.")) ForEach(Array(workspaceTabDefaultEntries.enumerated()), id: \.element.name) { index, entry in if index > 0 { @@ -3152,7 +3152,7 @@ struct SettingsView: View { } SettingsCardRow( entry.name, - subtitle: "Base: \(baseTabColorHex(for: entry.name))" + subtitle: String(localized: "settings.workspaceColors.base", defaultValue: "Base: \(baseTabColorHex(for: entry.name))") ) { HStack(spacing: 8) { ColorPicker( @@ -3174,10 +3174,10 @@ struct SettingsView: View { SettingsCardDivider() if workspaceTabCustomColors.isEmpty { - SettingsCardNote("Custom colors: none yet. Use \"Choose Custom Color...\" from a workspace context menu.") + SettingsCardNote(String(localized: "settings.workspaceColors.noCustomColors", defaultValue: "Custom colors: none yet. Use \"Choose Custom Color...\" from a workspace context menu.")) } else { VStack(alignment: .leading, spacing: 8) { - Text("Custom Colors") + Text(String(localized: "settings.workspaceColors.customColors", defaultValue: "Custom Colors")) .font(.system(size: 13, weight: .semibold)) ForEach(workspaceTabCustomColors, id: \.self) { hex in @@ -3192,7 +3192,7 @@ struct SettingsView: View { Spacer(minLength: 8) - Button("Remove") { + Button(String(localized: "settings.workspaceColors.remove", defaultValue: "Remove")) { removeWorkspaceCustomColor(hex) } .buttonStyle(.bordered) @@ -3207,10 +3207,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Reset Palette", - subtitle: "Restore built-in defaults and clear all custom colors." + String(localized: "settings.workspaceColors.resetPalette", defaultValue: "Reset Palette"), + subtitle: String(localized: "settings.workspaceColors.resetPalette.subtitle", defaultValue: "Restore built-in defaults and clear all custom colors.") ) { - Button("Reset") { + Button(String(localized: "settings.workspaceColors.resetPalette.button", defaultValue: "Reset")) { resetWorkspaceTabColors() } .buttonStyle(.bordered) @@ -3218,10 +3218,10 @@ struct SettingsView: View { } } - SettingsSectionHeader(title: "Automation") + SettingsSectionHeader(title: String(localized: "settings.section.automation", defaultValue: "Automation")) SettingsCard { SettingsCardRow( - "Socket Control Mode", + String(localized: "settings.automation.socketMode", defaultValue: "Socket Control Mode"), subtitle: selectedSocketControlMode.description, controlWidth: pickerColumnWidth ) { @@ -3237,27 +3237,27 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("Controls access to the local Unix socket for programmatic control. Choose a mode that matches your threat model.") + SettingsCardNote(String(localized: "settings.automation.socketMode.note", defaultValue: "Controls access to the local Unix socket for programmatic control. Choose a mode that matches your threat model.")) if selectedSocketControlMode == .password { SettingsCardDivider() SettingsCardRow( - "Socket Password", + String(localized: "settings.automation.socketPassword", defaultValue: "Socket Password"), subtitle: hasSocketPasswordConfigured - ? "Stored in Application Support." - : "No password set. External clients will be blocked until one is configured." + ? String(localized: "settings.automation.socketPassword.subtitleSet", defaultValue: "Stored in Application Support.") + : String(localized: "settings.automation.socketPassword.subtitleUnset", defaultValue: "No password set. External clients will be blocked until one is configured.") ) { HStack(spacing: 8) { - SecureField("Password", text: $socketPasswordDraft) + SecureField(String(localized: "settings.automation.socketPassword.placeholder", defaultValue: "Password"), text: $socketPasswordDraft) .textFieldStyle(.roundedBorder) .frame(width: 170) - Button(hasSocketPasswordConfigured ? "Change" : "Set") { + Button(hasSocketPasswordConfigured ? String(localized: "settings.automation.socketPassword.change", defaultValue: "Change") : String(localized: "settings.automation.socketPassword.set", defaultValue: "Set")) { saveSocketPassword() } .buttonStyle(.bordered) .controlSize(.small) .disabled(socketPasswordDraft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) if hasSocketPasswordConfigured { - Button("Clear") { + Button(String(localized: "settings.automation.socketPassword.clear", defaultValue: "Clear")) { clearSocketPassword() } .buttonStyle(.bordered) @@ -3275,21 +3275,21 @@ struct SettingsView: View { } if selectedSocketControlMode == .allowAll { SettingsCardDivider() - Text("Warning: Full open access makes the control socket world-readable/writable on this Mac and disables auth checks. Use only for local debugging.") + Text(String(localized: "settings.automation.openAccessWarning", defaultValue: "Warning: Full open access makes the control socket world-readable/writable on this Mac and disables auth checks. Use only for local debugging.")) .font(.caption) .foregroundStyle(.red) .padding(.horizontal, 14) .padding(.vertical, 8) } - SettingsCardNote("Overrides: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE, and CMUX_SOCKET_PATH (set CMUX_ALLOW_SOCKET_OVERRIDE=1 for stable/nightly builds).") + SettingsCardNote(String(localized: "settings.automation.socketOverrides.note", defaultValue: "Overrides: CMUX_SOCKET_ENABLE, CMUX_SOCKET_MODE, and CMUX_SOCKET_PATH (set CMUX_ALLOW_SOCKET_OVERRIDE=1 for stable/nightly builds).")) } SettingsCard { SettingsCardRow( - "Claude Code Integration", + String(localized: "settings.automation.claudeCode", defaultValue: "Claude Code Integration"), subtitle: claudeCodeHooksEnabled - ? "Sidebar shows Claude session status and notifications." - : "Claude Code runs without cmux integration." + ? String(localized: "settings.automation.claudeCode.subtitleOn", defaultValue: "Sidebar shows Claude session status and notifications.") + : String(localized: "settings.automation.claudeCode.subtitleOff", defaultValue: "Claude Code runs without cmux integration.") ) { Toggle("", isOn: $claudeCodeHooksEnabled) .labelsHidden() @@ -3299,11 +3299,11 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("When enabled, cmux wraps the claude command to inject session tracking and notification hooks. Disable if you prefer to manage Claude Code hooks yourself.") + SettingsCardNote(String(localized: "settings.automation.claudeCode.note", defaultValue: "When enabled, cmux wraps the claude command to inject session tracking and notification hooks. Disable if you prefer to manage Claude Code hooks yourself.")) } SettingsCard { - SettingsCardRow("Port Base", subtitle: "Starting port for CMUX_PORT env var.", controlWidth: pickerColumnWidth) { + SettingsCardRow(String(localized: "settings.automation.portBase", defaultValue: "Port Base"), subtitle: String(localized: "settings.automation.portBase.subtitle", defaultValue: "Starting port for CMUX_PORT env var."), controlWidth: pickerColumnWidth) { TextField("", value: $cmuxPortBase, format: .number) .textFieldStyle(.roundedBorder) .multilineTextAlignment(.trailing) @@ -3311,7 +3311,7 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow("Port Range Size", subtitle: "Number of ports per workspace.", controlWidth: pickerColumnWidth) { + SettingsCardRow(String(localized: "settings.automation.portRange", defaultValue: "Port Range Size"), subtitle: String(localized: "settings.automation.portRange.subtitle", defaultValue: "Number of ports per workspace."), controlWidth: pickerColumnWidth) { TextField("", value: $cmuxPortRange, format: .number) .textFieldStyle(.roundedBorder) .multilineTextAlignment(.trailing) @@ -3319,14 +3319,14 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardNote("Each workspace gets CMUX_PORT and CMUX_PORT_END env vars with a dedicated port range. New terminals inherit these values.") + SettingsCardNote(String(localized: "settings.automation.port.note", defaultValue: "Each workspace gets CMUX_PORT and CMUX_PORT_END env vars with a dedicated port range. New terminals inherit these values.")) } - SettingsSectionHeader(title: "Browser") + SettingsSectionHeader(title: String(localized: "settings.section.browser", defaultValue: "Browser")) SettingsCard { SettingsCardRow( - "Default Search Engine", - subtitle: "Used by the browser address bar when input is not a URL.", + String(localized: "settings.browser.searchEngine", defaultValue: "Default Search Engine"), + subtitle: String(localized: "settings.browser.searchEngine.subtitle", defaultValue: "Used by the browser address bar when input is not a URL."), controlWidth: pickerColumnWidth ) { Picker("", selection: $browserSearchEngine) { @@ -3340,7 +3340,7 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow("Show Search Suggestions") { + SettingsCardRow(String(localized: "settings.browser.searchSuggestions", defaultValue: "Show Search Suggestions")) { Toggle("", isOn: $browserSearchSuggestionsEnabled) .labelsHidden() .controlSize(.small) @@ -3349,10 +3349,10 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Browser Theme", + String(localized: "settings.browser.theme", defaultValue: "Browser Theme"), subtitle: selectedBrowserThemeMode == .system - ? "System follows app and macOS appearance." - : "\(selectedBrowserThemeMode.displayName) forces that color scheme for compatible pages.", + ? String(localized: "settings.browser.theme.subtitleSystem", defaultValue: "System follows app and macOS appearance.") + : String(localized: "settings.browser.theme.subtitleForced", defaultValue: "\(selectedBrowserThemeMode.displayName) forces that color scheme for compatible pages."), controlWidth: pickerColumnWidth ) { Picker("", selection: browserThemeModeSelection) { @@ -3367,8 +3367,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Open Terminal Links in cmux Browser", - subtitle: "When off, links clicked in terminal output open in your default browser." + String(localized: "settings.browser.openTerminalLinks", defaultValue: "Open Terminal Links in cmux Browser"), + subtitle: String(localized: "settings.browser.openTerminalLinks.subtitle", defaultValue: "When off, links clicked in terminal output open in your default browser.") ) { Toggle("", isOn: $openTerminalLinksInCmuxBrowser) .labelsHidden() @@ -3378,8 +3378,8 @@ struct SettingsView: View { SettingsCardDivider() SettingsCardRow( - "Intercept open http(s) in Terminal", - subtitle: "When off, `open https://...` and `open http://...` always use your default browser." + String(localized: "settings.browser.interceptOpen", defaultValue: "Intercept open http(s) in Terminal"), + subtitle: String(localized: "settings.browser.interceptOpen.subtitle", defaultValue: "When off, `open https://...` and `open http://...` always use your default browser.") ) { Toggle("", isOn: $interceptTerminalOpenCommandInCmuxBrowser) .labelsHidden() @@ -3391,8 +3391,8 @@ struct SettingsView: View { VStack(alignment: .leading, spacing: 6) { SettingsCardRow( - "Hosts to Open in Embedded Browser", - subtitle: "Applies to terminal link clicks and intercepted `open https://...` calls. Only these hosts open in cmux. Others open in your default browser. One host or wildcard per line (for example: example.com, *.internal.example). Leave empty to open all hosts in cmux." + String(localized: "settings.browser.hostWhitelist", defaultValue: "Hosts to Open in Embedded Browser"), + subtitle: String(localized: "settings.browser.hostWhitelist.subtitle", defaultValue: "Applies to terminal link clicks and intercepted `open https://...` calls. Only these hosts open in cmux. Others open in your default browser. One host or wildcard per line (for example: example.com, *.internal.example). Leave empty to open all hosts in cmux.") ) { EmptyView() } @@ -3416,8 +3416,8 @@ struct SettingsView: View { VStack(alignment: .leading, spacing: 6) { SettingsCardRow( - "URLs to Always Open Externally", - subtitle: "Applies to terminal link clicks and intercepted `open https://...` calls. One rule per line. Plain text matches any URL substring, or prefix with `re:` for regex (for example: openai.com/usage, re:^https?://[^/]*\\.example\\.com/(billing|usage))." + String(localized: "settings.browser.externalPatterns", defaultValue: "URLs to Always Open Externally"), + subtitle: String(localized: "settings.browser.externalPatterns.subtitle", defaultValue: "Applies to terminal link clicks and intercepted `open https://...` calls. One rule per line. Plain text matches any URL substring, or prefix with `re:` for regex (for example: openai.com/usage, re:^https?://[^/]*\\.example\\.com/(billing|usage)).") ) { EmptyView() } @@ -3441,10 +3441,10 @@ struct SettingsView: View { SettingsCardDivider() VStack(alignment: .leading, spacing: 8) { - Text("HTTP Hosts Allowed in Embedded Browser") + Text(String(localized: "settings.browser.httpAllowlist", defaultValue: "HTTP Hosts Allowed in Embedded Browser")) .font(.system(size: 13, weight: .semibold)) - Text("Controls which HTTP (non-HTTPS) hosts can open in cmux without a warning prompt. Defaults include localhost, 127.0.0.1, ::1, 0.0.0.0, and *.localtest.me.") + Text(String(localized: "settings.browser.httpAllowlist.description", defaultValue: "Controls which HTTP (non-HTTPS) hosts can open in cmux without a warning prompt. Defaults include localhost, 127.0.0.1, ::1, 0.0.0.0, and *.localtest.me.")) .font(.caption) .foregroundStyle(.secondary) @@ -3464,14 +3464,14 @@ struct SettingsView: View { ViewThatFits(in: .horizontal) { HStack(alignment: .center, spacing: 10) { - Text("One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).") + Text(String(localized: "settings.browser.httpAllowlist.hint", defaultValue: "One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).")) .font(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) Spacer(minLength: 0) - Button("Save") { + Button(String(localized: "settings.browser.httpAllowlist.save", defaultValue: "Save")) { saveBrowserInsecureHTTPAllowlist() } .buttonStyle(.bordered) @@ -3481,13 +3481,13 @@ struct SettingsView: View { } VStack(alignment: .leading, spacing: 8) { - Text("One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).") + Text(String(localized: "settings.browser.httpAllowlist.hint", defaultValue: "One host or wildcard per line (for example: localhost, 127.0.0.1, ::1, 0.0.0.0, *.localtest.me).")) .font(.caption) .foregroundStyle(.secondary) HStack { Spacer(minLength: 0) - Button("Save") { + Button(String(localized: "settings.browser.httpAllowlist.save", defaultValue: "Save")) { saveBrowserInsecureHTTPAllowlist() } .buttonStyle(.bordered) @@ -3503,8 +3503,8 @@ struct SettingsView: View { SettingsCardDivider() - SettingsCardRow("Browsing History", subtitle: browserHistorySubtitle) { - Button("Clear History…") { + SettingsCardRow(String(localized: "settings.browser.history", defaultValue: "Browsing History"), subtitle: browserHistorySubtitle) { + Button(String(localized: "settings.browser.history.clearButton", defaultValue: "Clear History…")) { showClearBrowserHistoryConfirmation = true } .buttonStyle(.bordered) @@ -3513,13 +3513,13 @@ struct SettingsView: View { } } - SettingsSectionHeader(title: "Keyboard Shortcuts") + SettingsSectionHeader(title: String(localized: "settings.section.keyboardShortcuts", defaultValue: "Keyboard Shortcuts")) SettingsCard { SettingsCardRow( - "Show Cmd/Ctrl-Hold Shortcut Hints", + String(localized: "settings.shortcuts.showHints", defaultValue: "Show Cmd/Ctrl-Hold Shortcut Hints"), subtitle: showShortcutHintsOnCommandHold - ? "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills." - : "Holding Cmd or Ctrl keeps shortcut hint pills hidden." + ? String(localized: "settings.shortcuts.showHints.subtitleOn", defaultValue: "Holding Cmd (sidebar/titlebar) or Ctrl/Cmd (pane tabs) shows shortcut hint pills.") + : String(localized: "settings.shortcuts.showHints.subtitleOff", defaultValue: "Holding Cmd or Ctrl keeps shortcut hint pills hidden.") ) { Toggle("", isOn: $showShortcutHintsOnCommandHold) .labelsHidden() @@ -3540,16 +3540,16 @@ struct SettingsView: View { } .id(shortcutResetToken) - Text("Click a shortcut value to record a new shortcut.") + Text(String(localized: "settings.shortcuts.recordHint", defaultValue: "Click a shortcut value to record a new shortcut.")) .font(.caption) .foregroundColor(.secondary) .padding(.leading, 2) - SettingsSectionHeader(title: "Reset") + SettingsSectionHeader(title: String(localized: "settings.section.reset", defaultValue: "Reset")) SettingsCard { HStack { Spacer(minLength: 0) - Button("Reset All Settings") { + Button(String(localized: "settings.reset.resetAll", defaultValue: "Reset All Settings")) { resetAllSettings() } .buttonStyle(.bordered) @@ -3615,7 +3615,7 @@ struct SettingsView: View { .opacity(0.14 + (topBlurOpacity * 0.86)) HStack { - Text("Settings") + Text(String(localized: "settings.title", defaultValue: "Settings")) .font(.system(size: 16, weight: .semibold)) .foregroundColor(.primary.opacity(0.92)) Spacer(minLength: 0) @@ -3656,31 +3656,31 @@ struct SettingsView: View { reloadWorkspaceTabColorSettings() } .confirmationDialog( - "Clear browser history?", + String(localized: "settings.browser.history.clearDialog.title", defaultValue: "Clear browser history?"), isPresented: $showClearBrowserHistoryConfirmation, titleVisibility: .visible ) { - Button("Clear History", role: .destructive) { + Button(String(localized: "settings.browser.history.clearDialog.confirm", defaultValue: "Clear History"), role: .destructive) { BrowserHistoryStore.shared.clearHistory() } - Button("Cancel", role: .cancel) {} + Button(String(localized: "settings.browser.history.clearDialog.cancel", defaultValue: "Cancel"), role: .cancel) {} } message: { - Text("This removes visited-page suggestions from the browser omnibar.") + Text(String(localized: "settings.browser.history.clearDialog.message", defaultValue: "This removes visited-page suggestions from the browser omnibar.")) } .confirmationDialog( - "Enable full open access?", + String(localized: "settings.automation.openAccess.dialog.title", defaultValue: "Enable full open access?"), isPresented: $showOpenAccessConfirmation, titleVisibility: .visible ) { - Button("Enable Full Open Access", role: .destructive) { + Button(String(localized: "settings.automation.openAccess.dialog.confirm", defaultValue: "Enable Full Open Access"), role: .destructive) { socketControlMode = (pendingOpenAccessMode ?? .allowAll).rawValue pendingOpenAccessMode = nil } - Button("Cancel", role: .cancel) { + Button(String(localized: "settings.automation.openAccess.dialog.cancel", defaultValue: "Cancel"), role: .cancel) { pendingOpenAccessMode = nil } } message: { - Text("This disables ancestry and password checks and opens the socket to all local users. Only enable when you understand the risk.") + Text(String(localized: "settings.automation.openAccess.dialog.message", defaultValue: "This disables ancestry and password checks and opens the socket to all local users. Only enable when you understand the risk.")) } } @@ -3915,7 +3915,7 @@ private struct AppIconPickerRow: View { var body: some View { VStack(alignment: .leading, spacing: 10) { - Text("App Icon") + Text(String(localized: "settings.app.appIcon", defaultValue: "App Icon")) .font(.system(size: 13, weight: .medium)) HStack(spacing: 12) {