feat: add Japanese localization with String Catalog (#819)
* 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
This commit is contained in:
parent
422c86e822
commit
2c330efb8a
20 changed files with 11643 additions and 789 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue