diff --git a/src/main/linux-package-downloaded-status.ts b/src/main/linux-package-downloaded-status.ts index df0c9b32e6d..887b529c62c 100644 --- a/src/main/linux-package-downloaded-status.ts +++ b/src/main/linux-package-downloaded-status.ts @@ -6,22 +6,14 @@ import { } from './linux-package-update-recovery' import { getLinuxPackageType } from './linux-update-package-type' import type { LinuxPackageArtifact } from './linux-package-update-recovery' - -export const LINUX_PACKAGE_MARKER_UNUSABLE_MESSAGE = - 'Orca could not verify the installed Linux package format, so it will not install this update automatically. Download the update from the official release page and install it manually.' -export const LINUX_PACKAGE_EXTERNALLY_MANAGED_MESSAGE = - 'This copy of Orca is managed by your system package manager, so Orca cannot install updates itself. Update Orca through your distribution instead.' -export const LINUX_PACKAGE_MANUAL_INSTALL_MESSAGE = - 'Quit Orca before running the system package install command.' -const PACKAGE_METADATA_UNUSABLE_MESSAGE = - 'The downloaded package metadata could not be verified. Quit Orca before downloading and installing the update from the official release page.' +import { updaterCodedMessage } from '../shared/updater-failure-copy' export function createLinuxPackageManualInstallStatus( artifact: Pick ): UpdateStatus { return { state: 'error', - message: LINUX_PACKAGE_MANUAL_INSTALL_MESSAGE, + ...updaterCodedMessage('linuxPackageManualInstall'), recovery: { kind: 'linux-package-install', packageType: artifact.packageType, @@ -70,7 +62,7 @@ export function resolveLinuxPackageDownloadedStatus(info: { clearTrackedLinuxPackageArtifact() return { state: 'error', - message: LINUX_PACKAGE_MARKER_UNUSABLE_MESSAGE, + ...updaterCodedMessage('linuxPackageMarkerUnusable'), version: info.version, retryable: false } @@ -79,7 +71,7 @@ export function resolveLinuxPackageDownloadedStatus(info: { if (!artifact) { return { state: 'error', - message: PACKAGE_METADATA_UNUSABLE_MESSAGE, + ...updaterCodedMessage('linuxPackageMetadataUnusable'), version: info.version, retryable: false } diff --git a/src/main/updater-events.test.ts b/src/main/updater-events.test.ts index ed9452bd2ea..5d3251f6259 100644 --- a/src/main/updater-events.test.ts +++ b/src/main/updater-events.test.ts @@ -198,6 +198,7 @@ describe('registerAutoUpdaterHandlers linux package artifact tracking', () => { expect(context.sendStatus).toHaveBeenLastCalledWith({ state: 'error', message: 'Quit Orca before running the system package install command.', + messageCode: 'linuxPackageManualInstall', recovery: { kind: 'linux-package-install', packageType, @@ -220,6 +221,7 @@ describe('registerAutoUpdaterHandlers linux package artifact tracking', () => { state: 'error', message: 'The downloaded package metadata could not be verified. Quit Orca before downloading and installing the update from the official release page.', + messageCode: 'linuxPackageMetadataUnusable', version: '1.0.61', retryable: false } @@ -261,6 +263,7 @@ describe('registerAutoUpdaterHandlers linux package artifact tracking', () => { state: 'error', message: 'Orca could not verify the installed Linux package format, so it will not install this update automatically. Download the update from the official release page and install it manually.', + messageCode: 'linuxPackageMarkerUnusable', version: '1.0.61', retryable: false }) @@ -292,6 +295,7 @@ describe('registerAutoUpdaterHandlers linux package artifact tracking', () => { expect(context.sendStatus).toHaveBeenLastCalledWith({ state: 'error', message: 'Quit Orca before running the system package install command.', + messageCode: 'linuxPackageManualInstall', recovery: { kind: 'linux-package-install', packageType: 'deb', @@ -314,6 +318,7 @@ describe('registerAutoUpdaterHandlers linux package artifact tracking', () => { expect(context.sendStatus).toHaveBeenLastCalledWith({ state: 'error', message: 'Quit Orca before running the system package install command.', + messageCode: 'linuxPackageManualInstall', recovery: { kind: 'linux-package-install', packageType: 'deb', @@ -391,6 +396,7 @@ describe('registerAutoUpdaterHandlers linux package artifact tracking', () => { expect(context.sendStatus).toHaveBeenLastCalledWith({ state: 'error', message: 'Quit Orca before running the system package install command.', + messageCode: 'linuxPackageManualInstall', recovery: { kind: 'linux-package-install', packageType: 'deb', @@ -446,6 +452,7 @@ describe('registerAutoUpdaterHandlers linux package artifact tracking', () => { expect(context.sendStatus).toHaveBeenLastCalledWith({ state: 'error', message: 'Quit Orca before running the system package install command.', + messageCode: 'linuxPackageManualInstall', recovery: { kind: 'linux-package-install', packageType: 'deb', diff --git a/src/main/updater-linux-package-recovery-actions.test.ts b/src/main/updater-linux-package-recovery-actions.test.ts index a8dd95d6318..9e34fcdb76a 100644 --- a/src/main/updater-linux-package-recovery-actions.test.ts +++ b/src/main/updater-linux-package-recovery-actions.test.ts @@ -108,6 +108,7 @@ const ARTIFACT = { const MANUAL_INSTALL_STATUS = { state: 'error', message: 'Quit Orca before running the system package install command.', + messageCode: 'linuxPackageManualInstall', recovery: { kind: 'linux-package-install', packageType: 'deb', diff --git a/src/main/updater-release-builds.test.ts b/src/main/updater-release-builds.test.ts index 9ff4c5e81e5..1c97fbe1844 100644 --- a/src/main/updater-release-builds.test.ts +++ b/src/main/updater-release-builds.test.ts @@ -22,7 +22,7 @@ vi.mock('./git/gh-rate-limit-breaker', () => ({ recordGhPrimaryRateLimit: (...args: unknown[]) => recordRateLimitMock(...args) })) -const { describeRateLimitReset, listReleaseBuilds, rateLimitResetAtMs, resolveTargetBuild } = +const { listReleaseBuilds, rateLimitResetAtMs, resolveTargetBuild } = await import('./updater-release-builds') function jsonResponse( @@ -396,9 +396,13 @@ describe('listReleaseBuilds', () => { }) ) - await expect(listReleaseBuilds('hourly', 'darwin')).rejects.toThrow( + const failure = listReleaseBuilds('hourly', 'darwin') + await expect(failure).rejects.toThrow( "GitHub rate limit reached. Try again in about 28 minutes, or run `gh auth login` so Orca can use your account's higher limit." ) + await expect(failure).rejects.toMatchObject({ + coded: { messageCode: 'rateLimitedInMinutesSignedOut', messageParams: { count: 28 } } + }) }) it('omits the sign-in hint when the rate-limited request was authenticated', async () => { @@ -407,9 +411,9 @@ describe('listReleaseBuilds', () => { jsonResponse(null, { ok: false, status: 403, headers: { 'x-ratelimit-remaining': '0' } }) ) - const failure = listReleaseBuilds('hourly', 'darwin') - await expect(failure).rejects.toThrow(/rate limit reached/) - await expect(failure).rejects.not.toThrow(/gh auth login/) + await expect(listReleaseBuilds('hourly', 'darwin')).rejects.toThrow( + /^GitHub rate limit reached\. Try again in a few minutes\.$/ + ) }) it('treats 429 as a rate limit', async () => { @@ -464,22 +468,6 @@ describe('rateLimitResetAtMs', () => { }) }) -describe('describeRateLimitReset', () => { - const nowMs = 1_800_000_000_000 - - it('falls back to a vague wait when the reset is unknown', () => { - expect(describeRateLimitReset(null, nowMs)).toBe('in a few minutes') - }) - - it('rounds a sub-minute reset up to a minute', () => { - expect(describeRateLimitReset(nowMs + 20_000, nowMs)).toBe('in about a minute') - }) - - it('rounds a partial minute up', () => { - expect(describeRateLimitReset(nowMs + 9 * 60_000 + 1, nowMs)).toBe('in about 10 minutes') - }) -}) - describe('resolveTargetBuild', () => { it('pins an hourly tag at the hourly repo download path', () => { expect(resolveTargetBuild('hourly', 'v1.4.160-hourly.202607281400')).toEqual({ diff --git a/src/main/updater-release-builds.ts b/src/main/updater-release-builds.ts index 326f0544976..12b87770300 100644 --- a/src/main/updater-release-builds.ts +++ b/src/main/updater-release-builds.ts @@ -13,6 +13,7 @@ import { parseRetryAfterHeaderMs } from '../shared/retry-after-header' import { getGhRateLimitBlockedUntilMs, recordGhPrimaryRateLimit } from './git/gh-rate-limit-breaker' import { isValidVersion } from './updater-fallback' import { rejectReleaseApiToken, resolveReleaseApiToken } from './updater-release-api-token' +import { UpdaterCodedError } from './updater/updater-coded-error' const FETCH_TIMEOUT_MS = 8000 const MAX_LISTED_BUILDS = 100 @@ -60,12 +61,18 @@ export function rateLimitResetAtMs(headers: Headers, nowMs: number): number | nu return resetEpochSeconds > 0 ? resetEpochSeconds * 1000 : null } -export function describeRateLimitReset(resetAtMs: number | null, nowMs: number): string { +function rateLimitError(resetAtMs: number | null, nowMs: number, signedIn: boolean): Error { if (resetAtMs === null) { - return 'in a few minutes' + return new UpdaterCodedError(signedIn ? 'rateLimitedSoon' : 'rateLimitedSoonSignedOut') } const minutes = Math.ceil((resetAtMs - nowMs) / 60_000) - return minutes <= 1 ? 'in about a minute' : `in about ${minutes} minutes` + if (minutes <= 1) { + return new UpdaterCodedError(signedIn ? 'rateLimitedInMinute' : 'rateLimitedInMinuteSignedOut') + } + return new UpdaterCodedError( + signedIn ? 'rateLimitedInMinutes' : 'rateLimitedInMinutesSignedOut', + { count: minutes } + ) } function releaseListError( @@ -75,18 +82,13 @@ function releaseListError( signedIn: boolean ): Error { if (res.status === 404) { - return new Error(`No releases repository found at ${repo}.`) + return new UpdaterCodedError('releaseRepoNotFound', { repo }) } if (isRateLimited(res)) { const nowMs = Date.now() - const retry = describeRateLimitReset(rateLimitResetAtMs(res.headers, nowMs), nowMs) - return new Error( - signedIn - ? `GitHub rate limit reached. Try again ${retry}.` - : `GitHub rate limit reached. Try again ${retry}, or run \`gh auth login\` so Orca can use your account's higher limit.` - ) + return rateLimitError(rateLimitResetAtMs(res.headers, nowMs), nowMs, signedIn) } - return new Error(`Could not list ${channel} builds (HTTP ${res.status}).`) + return new UpdaterCodedError('releaseListFailed', { channel, status: res.status }) } export function getReleaseDownloadUrlForRepo(repo: string, tag: string): string { @@ -180,7 +182,7 @@ export async function listReleaseBuilds( } const payload: unknown = await res.json() if (!Array.isArray(payload)) { - throw new Error(`Could not read the ${channel} release list.`) + throw new UpdaterCodedError('releaseListUnreadable', { channel }) } const builds = payload .map((entry) => parseReleaseEntry(entry as GitHubReleaseEntry, repo, platform)) @@ -198,7 +200,7 @@ export type ResolvedTargetBuild = { export function resolveTargetBuild(channel: ReleaseChannel, tag: string): ResolvedTargetBuild { const version = normalizeTagToVersion(tag) if (!isValidVersion(version)) { - throw new Error(`"${tag}" is not a valid release tag.`) + throw new UpdaterCodedError('invalidReleaseTag', { tag }) } const repo = getReleaseRepoForChannel(channel) return { tag, version, feedUrl: getReleaseDownloadUrlForRepo(repo, tag) } diff --git a/src/main/updater.build-channel-selection.test.ts b/src/main/updater.build-channel-selection.test.ts index b7307bda0e1..1a5053d8be5 100644 --- a/src/main/updater.build-channel-selection.test.ts +++ b/src/main/updater.build-channel-selection.test.ts @@ -67,6 +67,8 @@ describe('updater', () => { expect(send).toHaveBeenCalledWith('updater:status', { state: 'error', message, + messageCode: 'channelUnsupportedPlatform', + messageParams: { channel: expect.any(String) }, userInitiated: true }) expect(autoUpdaterMock.checkForUpdates).not.toHaveBeenCalled() @@ -92,6 +94,8 @@ describe('updater', () => { expect(send).toHaveBeenCalledWith('updater:status', { state: 'error', message: expect.stringContaining('Download the installer from the release page'), + messageCode: 'unsignedDevChannel', + messageParams: { channel: 'Adhoc' }, userInitiated: true }) expect(autoUpdaterMock.checkForUpdates).not.toHaveBeenCalled() @@ -118,11 +122,13 @@ describe('updater', () => { checkForUpdatesFromMenu({ channel, targetTag }) - expect(send).not.toHaveBeenCalledWith('updater:status', { - state: 'error', - message: expect.stringContaining('Download the installer'), - userInitiated: true - }) + expect(send).not.toHaveBeenCalledWith( + 'updater:status', + expect.objectContaining({ + state: 'error', + message: expect.stringContaining('Download the installer') + }) + ) expect(autoUpdaterMock.allowDowngrade).toBe(true) } finally { platformSpy.mockRestore() @@ -553,6 +559,7 @@ describe('updater', () => { expect(sendMock).toHaveBeenCalledWith('updater:status', { state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) @@ -586,6 +593,7 @@ describe('updater', () => { expect(sendMock).toHaveBeenCalledWith('updater:status', { state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) diff --git a/src/main/updater.check-failure.test.ts b/src/main/updater.check-failure.test.ts index 121efc6baa9..11f37827c17 100644 --- a/src/main/updater.check-failure.test.ts +++ b/src/main/updater.check-failure.test.ts @@ -288,6 +288,7 @@ describe('updater check failure handling', () => { expect(sendMock).toHaveBeenCalledWith('updater:status', { state: 'error', message: RELEASE_NOT_READY_MESSAGE, + messageCode: 'releaseNotReady', userInitiated: true }) }) diff --git a/src/main/updater.linux-externally-managed.test.ts b/src/main/updater.linux-externally-managed.test.ts index 3ca68bc6606..ec05ad0e48c 100644 --- a/src/main/updater.linux-externally-managed.test.ts +++ b/src/main/updater.linux-externally-managed.test.ts @@ -99,6 +99,7 @@ describe('updater externally managed Linux installs', () => { expect(lastStatus(send)).toEqual({ state: 'error', message: EXTERNALLY_MANAGED_MESSAGE, + messageCode: 'linuxPackageExternallyManaged', version: '1.0.61', retryable: false }) diff --git a/src/main/updater.linux-root-package-install.test.ts b/src/main/updater.linux-root-package-install.test.ts index 028e9002c1e..411df6ad160 100644 --- a/src/main/updater.linux-root-package-install.test.ts +++ b/src/main/updater.linux-root-package-install.test.ts @@ -114,6 +114,7 @@ describe('updater Linux root packages', () => { expect(lastStatus(send)).toEqual({ state: 'error', message: 'Quit Orca before running the system package install command.', + messageCode: 'linuxPackageManualInstall', recovery: { kind: 'linux-package-install', packageType, @@ -229,6 +230,7 @@ describe('updater Linux root packages', () => { state: 'error', message: 'Orca could not verify the installed Linux package format, so it will not install this update automatically. Download the update from the official release page and install it manually.', + messageCode: 'linuxPackageMarkerUnusable', version: '1.0.61', retryable: false }) diff --git a/src/main/updater.prerelease-fallback.test.ts b/src/main/updater.prerelease-fallback.test.ts index 9fa0c2129d7..654aad5b6f0 100644 --- a/src/main/updater.prerelease-fallback.test.ts +++ b/src/main/updater.prerelease-fallback.test.ts @@ -105,6 +105,7 @@ describe('updater', () => { expect(statuses).toContainEqual({ state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) @@ -196,6 +197,7 @@ describe('updater', () => { expect(statuses).toContainEqual({ state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) @@ -347,6 +349,7 @@ describe('updater', () => { expect(statuses.at(-1)).toEqual({ state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) @@ -447,6 +450,7 @@ describe('updater', () => { expect(statuses).toContainEqual({ state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) @@ -661,6 +665,7 @@ describe('updater', () => { expect(statuses).toContainEqual({ state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) diff --git a/src/main/updater.publishing-window-feed.test.ts b/src/main/updater.publishing-window-feed.test.ts index e0a2228d254..81ce165c184 100644 --- a/src/main/updater.publishing-window-feed.test.ts +++ b/src/main/updater.publishing-window-feed.test.ts @@ -127,6 +127,7 @@ describe('updater', () => { expect(sendMock).toHaveBeenCalledWith('updater:status', { state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) @@ -182,6 +183,7 @@ describe('updater', () => { expect(sendMock).toHaveBeenCalledWith('updater:status', { state: 'error', message: "Couldn't reach the update server. Try again in a few minutes.", + messageCode: 'updateServerUnreachable', userInitiated: true }) }) diff --git a/src/main/updater/updater-build-selection.ts b/src/main/updater/updater-build-selection.ts index 67b39e548d7..a810d9a38cf 100644 --- a/src/main/updater/updater-build-selection.ts +++ b/src/main/updater/updater-build-selection.ts @@ -1,6 +1,5 @@ import { app } from 'electron' import { - DEV_CHANNEL_PLATFORM_LABEL, getVersionChannel, hasDedicatedReleaseRepo, isChannelSupportedOnPlatform, @@ -14,6 +13,8 @@ import { profileStateBuildCompatibilityError } from '../../shared/profile-state- import { listReleaseBuilds, resolveTargetBuild } from '../updater-release-builds' import { ReleaseBuildListCache, type ReleaseBuildListOptions } from '../updater-release-build-cache' import { UpdaterMenuChecks } from './updater-menu-checks' +import { updaterCodedMessage } from '../../shared/updater-failure-copy' +import { updaterErrorMessage } from './updater-coded-error' export abstract class UpdaterBuildSelection extends UpdaterMenuChecks { private readonly releaseBuildCache = new ReleaseBuildListCache((channel) => @@ -96,7 +97,9 @@ export abstract class UpdaterBuildSelection extends UpdaterMenuChecks { if (!isChannelSupportedOnPlatform(channel, process.platform)) { this.sendStatus({ state: 'error', - message: `${RELEASE_CHANNEL_LABELS[channel]} builds are produced only for ${DEV_CHANNEL_PLATFORM_LABEL}.`, + ...updaterCodedMessage('channelUnsupportedPlatform', { + channel: RELEASE_CHANNEL_LABELS[channel] + }), userInitiated: true }) return @@ -111,7 +114,7 @@ export abstract class UpdaterBuildSelection extends UpdaterMenuChecks { ) { this.sendStatus({ state: 'error', - message: `${RELEASE_CHANNEL_LABELS[channel]} builds are unsigned, and this signed build only installs updates signed by Orca's publisher. Download the installer from the release page and run it once — updates work normally from there, including back to Stable.`, + ...updaterCodedMessage('unsignedDevChannel', { channel: RELEASE_CHANNEL_LABELS[channel] }), userInitiated: true }) return @@ -164,7 +167,7 @@ export abstract class UpdaterBuildSelection extends UpdaterMenuChecks { this.restoreReleaseUpdateSource() this.sendSettledCheckStatus({ state: 'error', - message: String((error as Error)?.message ?? error), + ...updaterErrorMessage(error), userInitiated: true }) } finally { diff --git a/src/main/updater/updater-check-failure.ts b/src/main/updater/updater-check-failure.ts index e5b5b047d44..a0638c4cf06 100644 --- a/src/main/updater/updater-check-failure.ts +++ b/src/main/updater/updater-check-failure.ts @@ -2,6 +2,7 @@ import { isBenignCheckFailure } from '../updater-fallback' import { ReleaseFeedPreflightError } from './updater-state' import type { CheckFailureSource } from './updater-state' import { UpdaterReleaseFeed } from './updater-release-feed' +import { updaterCodedMessage } from '../../shared/updater-failure-copy' export abstract class UpdaterCheckFailure extends UpdaterReleaseFeed { protected isRetryableReleaseFeedPreflightFailure(sourceError: unknown): boolean { @@ -81,9 +82,11 @@ export abstract class UpdaterCheckFailure extends UpdaterReleaseFeed { // User clicks need visible feedback distinguishing incomplete releases from transport failure. this.sendSettledCheckStatus({ state: 'error', - message: this.isStableReleaseNotReadyFailure(sourceError) - ? "A newer release isn't available for this device yet. Check again later." - : "Couldn't reach the update server. Try again in a few minutes.", + ...updaterCodedMessage( + this.isStableReleaseNotReadyFailure(sourceError) + ? 'releaseNotReady' + : 'updateServerUnreachable' + ), userInitiated: true }) } else { diff --git a/src/main/updater/updater-check-state.ts b/src/main/updater/updater-check-state.ts index 26b7a89f9bd..ee2982172a9 100644 --- a/src/main/updater/updater-check-state.ts +++ b/src/main/updater/updater-check-state.ts @@ -5,6 +5,7 @@ import { getRetainedLinuxPackageManualInstallStatus } from '../linux-package-dow import type { UpdateCheckOptions, UpdateStatus } from '../../shared/update-status-types' import type { UpdateCheckVariant } from './updater-types' import { UpdaterStatus } from './updater-status' +import type { UpdaterCodedMessage } from '../../shared/updater-failure-copy' import { AUTO_UPDATE_CHECK_INTERVAL_MS, AUTO_UPDATE_RETRY_INTERVAL_MS, @@ -265,7 +266,9 @@ export abstract class UpdaterCheckState extends UpdaterStatus { ) } - protected sendErrorStatus(message: string, userInitiated?: boolean): void { + protected sendErrorStatus(failure: string | UpdaterCodedMessage, userInitiated?: boolean): void { + const coded = typeof failure === 'string' ? { message: failure } : failure + const { message } = coded if ( this.currentStatus.state === 'error' && this.currentStatus.message === message && @@ -280,7 +283,7 @@ export abstract class UpdaterCheckState extends UpdaterStatus { message: 'Windows update signature check could not run' }) } - this.sendStatus({ state: 'error', message, userInitiated }) + this.sendStatus({ state: 'error', ...coded, userInitiated }) } /** Retain the distro-package manual-install card when settling a check; ordinary status would erase it. */ @@ -289,7 +292,15 @@ export abstract class UpdaterCheckState extends UpdaterStatus { if (retainedStatus) { this.sendStatus(retainedStatus) } else if (status.state === 'error') { - this.sendErrorStatus(status.message, status.userInitiated) + const { message, messageCode, messageParams } = status + this.sendErrorStatus( + { + message, + ...(messageCode === undefined ? {} : { messageCode }), + ...(messageParams === undefined ? {} : { messageParams }) + }, + status.userInitiated + ) } else { this.sendStatus(status) } diff --git a/src/main/updater/updater-coded-error.ts b/src/main/updater/updater-coded-error.ts new file mode 100644 index 00000000000..5e403a9d9ec --- /dev/null +++ b/src/main/updater/updater-coded-error.ts @@ -0,0 +1,22 @@ +import { + updaterCodedMessage, + type UpdaterCodedMessage, + type UpdaterFailureCode, + type UpdaterFailureParams +} from '../../shared/updater-failure-copy' +import { errorMessage } from '../../shared/error-message' + +/** Thrown inside main only; handlers copy its code onto the status or IPC result. */ +export class UpdaterCodedError extends Error { + readonly coded: UpdaterCodedMessage + + constructor(code: UpdaterFailureCode, params?: UpdaterFailureParams) { + const coded = updaterCodedMessage(code, params) + super(coded.message) + this.coded = coded + } +} + +export function updaterErrorMessage(error: unknown): UpdaterCodedMessage { + return error instanceof UpdaterCodedError ? error.coded : { message: errorMessage(error) } +} diff --git a/src/main/updater/updater-download-install.ts b/src/main/updater/updater-download-install.ts index 27f8c92f754..b972682327d 100644 --- a/src/main/updater/updater-download-install.ts +++ b/src/main/updater/updater-download-install.ts @@ -6,7 +6,7 @@ import { } from '../updater-mac-install' import { recordUpdaterLifecycle } from '../updater-lifecycle-diagnostics' import { isExternallyManagedLinuxInstall } from '../linux-update-package-type' -import { LINUX_PACKAGE_EXTERNALLY_MANAGED_MESSAGE } from '../linux-package-downloaded-status' +import { updaterCodedMessage } from '../../shared/updater-failure-copy' import { QUIT_AND_INSTALL_DELAY_MS } from './updater-state' import { UpdaterRemoteStatus } from './updater-remote-status' import { prepareThisProcessShipItStaging } from './shipit-leftover-recovery' @@ -83,7 +83,7 @@ export abstract class UpdaterDownloadInstall extends UpdaterRemoteStatus { this.restoreReleaseUpdateSource() this.sendStatus({ state: 'error', - message: LINUX_PACKAGE_EXTERNALLY_MANAGED_MESSAGE, + ...updaterCodedMessage('linuxPackageExternallyManaged'), version, retryable: false }) diff --git a/src/main/updater/updater-install-execution.ts b/src/main/updater/updater-install-execution.ts index f021eabb4ad..d791ab3b9ac 100644 --- a/src/main/updater/updater-install-execution.ts +++ b/src/main/updater/updater-install-execution.ts @@ -10,7 +10,7 @@ import { getMacUpdateRunningInstances } from '../macos-update-running-instances' import { commitServeSupervisorPark } from './serve-supervisor-install-park' import { armUpdateInstallExitWatchdog } from '../update-install-exit-watchdog' import { getLinuxPackageType } from '../linux-update-package-type' -import { LINUX_PACKAGE_MARKER_UNUSABLE_MESSAGE } from '../linux-package-downloaded-status' +import { updaterCodedMessage } from '../../shared/updater-failure-copy' import { recordUpdaterLifecycle } from '../updater-lifecycle-diagnostics' import { requestServeUpdateHandoff, failServeUpdateHandoff } from '../serve-update-handoff' import { UpdaterMacInstallPreflight } from './updater-mac-install-preflight' @@ -54,7 +54,7 @@ export abstract class UpdaterInstallExecution extends UpdaterMacInstallPreflight this.sendToMainWindow('updater:quitAndInstallAborted') this.sendInstallFailureStatus({ state: 'error', - message: LINUX_PACKAGE_MARKER_UNUSABLE_MESSAGE, + ...updaterCodedMessage('linuxPackageMarkerUnusable'), ...(pendingVersion ? { version: pendingVersion } : {}) }) return diff --git a/src/main/updater/updater-package-recovery.ts b/src/main/updater/updater-package-recovery.ts index 12c77bdeecd..cbc81a71785 100644 --- a/src/main/updater/updater-package-recovery.ts +++ b/src/main/updater/updater-package-recovery.ts @@ -13,24 +13,25 @@ import type { LinuxPackageInstallRecovery } from '../../shared/update-status-types' import { UpdaterInstallSupport } from './updater-install-support' +import { + updaterCodedMessage, + updaterFailureText, + type UpdaterFailureCode +} from '../../shared/updater-failure-copy' -const LINUX_PACKAGE_RECOVERY_MESSAGES: Record = { - missing: - 'The downloaded package is no longer in the update cache. Download the update again, or get it from the official release page.', +const LINUX_PACKAGE_RECOVERY_CODES: Record< + LinuxPackageRecoveryUnavailableReason, + UpdaterFailureCode +> = { + missing: 'linuxPackageMissing', // Also covers traversal/symlink escape from cache; do not claim only file type changed. - 'not-regular': - 'The downloaded package is no longer a valid file in the update cache. Download the update again, or get it from the official release page.', - 'hash-mismatch': - 'The downloaded package no longer matches the verified release, so Orca will not hand it to a package manager. Download the update again, or get it from the official release page.', - 'read-failed': - 'Orca could not read the downloaded package. Download the update again, or get it from the official release page.', - 'no-sudo': - 'No sudo command was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.', - 'no-package-manager': - 'No supported package manager was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.', + 'not-regular': 'linuxPackageNotRegular', + 'hash-mismatch': 'linuxPackageHashMismatch', + 'read-failed': 'linuxPackageReadFailed', + 'no-sudo': 'linuxPackageNoSudo', + 'no-package-manager': 'linuxPackageNoPackageManager', // Capture tracks absolute cache paths; invalid paths indicate a bug. - 'invalid-package-path': - 'The downloaded package is not at a usable path, so Orca cannot build a safe install command. Show the package and install it with your package manager.' + 'invalid-package-path': 'linuxPackageInvalidPath' } // Clear status recovery with the artifact or renderer actions stay enabled. @@ -88,12 +89,12 @@ export abstract class UpdaterPackageRecovery extends UpdaterInstallSupport { ): never { this.assertCurrentLinuxPackageRecovery(recovery, artifact) this.recordLinuxPackageRecoveryUnavailable(recovery, reason) - const message = LINUX_PACKAGE_RECOVERY_MESSAGES[reason] + const coded = updaterCodedMessage(LINUX_PACKAGE_RECOVERY_CODES[reason]) if (RECOVERY_CLEARING_REASONS.includes(reason)) { clearTrackedLinuxPackageArtifact() - this.sendStatus({ state: 'error', message, version: recovery.version }) + this.sendStatus({ state: 'error', ...coded, version: recovery.version }) } - throw new Error(message) + throw new Error(coded.message) } protected async getLinuxPackageInstallInstructions(): Promise { @@ -116,7 +117,7 @@ export abstract class UpdaterPackageRecovery extends UpdaterInstallSupport { return { ok: false, reason: result.reason, - message: LINUX_PACKAGE_RECOVERY_MESSAGES[result.reason] + message: updaterFailureText(LINUX_PACKAGE_RECOVERY_CODES[result.reason]) } } this.failLinuxPackageRecovery(recovery, artifact, result.reason) diff --git a/src/main/window/main-window-updater.ts b/src/main/window/main-window-updater.ts index db7b12a76b9..abd2797547c 100644 --- a/src/main/window/main-window-updater.ts +++ b/src/main/window/main-window-updater.ts @@ -6,6 +6,8 @@ import { isTrustedUIRenderer } from '../ipc/ui' import type { Store } from '../persistence' import { logStartupMilestone } from '../startup/startup-diagnostics' import { applyProxyToElectronUpdaterSession } from '../updater/electron-updater-proxy-session' +import { updaterErrorMessage } from '../updater/updater-coded-error' +import { updaterCodedMessage } from '../../shared/updater-failure-copy' import { checkForUpdatesFromMenu, dismissAvailableUpdate, @@ -174,7 +176,7 @@ export function registerUpdaterHandlers(_store: Store): void { options?: { force?: boolean } ): Promise => { if (!RELEASE_CHANNELS.includes(channel)) { - return { ok: false, channel, message: `Unknown release channel "${channel}".` } + return { ok: false, channel, ...updaterCodedMessage('unknownReleaseChannel', { channel }) } } try { return { @@ -184,7 +186,7 @@ export function registerUpdaterHandlers(_store: Store): void { } } catch (error) { // Return expected network/rate-limit failures as data for the build picker. - return { ok: false, channel, message: String((error as Error)?.message ?? error) } + return { ok: false, channel, ...updaterErrorMessage(error) } } } ) diff --git a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx index cc3cd4c1ad4..54b1625b809 100644 --- a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx +++ b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.test.tsx @@ -8,6 +8,7 @@ const { toastSuccess } = vi.hoisted(() => ({ toastSuccess: vi.fn() })) vi.mock('sonner', () => ({ toast: { success: toastSuccess } })) import { LinuxPackageInstallRecoveryCard } from './LinuxPackageInstallRecoveryCard' +import { UPDATER_FAILURE_COPY } from '../../../shared/updater-failure-copy' const RELEASE_URL = 'https://github.com/stablyai/orca/releases/tag/v1.4.200' const DIAGNOSTIC = 'pkexec: no polkit authentication agent found' @@ -516,7 +517,7 @@ describe('LinuxPackageInstallRecoveryCard without a usable command', () => { expect(screen.queryByRole('button', { name: 'Copy Install Command' })).toBeNull() expect(button('Show Package').dataset.variant).toBe('default') - expect(footnoteText()).toBe(NO_PACKAGE_MANAGER.message) + expect(footnoteText()).toBe(UPDATER_FAILURE_COPY.linuxPackageNoPackageManager) expect(screen.getByText(/Quit Orca before finishing the update/)).toBeTruthy() fireEvent.click(button('Download Manually')) @@ -534,7 +535,7 @@ describe('LinuxPackageInstallRecoveryCard without a usable command', () => { fireEvent.click(button('Copy Install Command')) await flushActions() - expect(footnoteText()).toBe('No sudo binary is available on this machine.') + expect(footnoteText()).toBe(UPDATER_FAILURE_COPY.linuxPackageNoSudo) expect(button('Show Package').dataset.variant).toBe('default') expect(button('Download Manually')).toBeTruthy() }) diff --git a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx index 9c22fccd16f..71b9e508e92 100644 --- a/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx +++ b/src/renderer/src/components/LinuxPackageInstallRecoveryCard.tsx @@ -6,6 +6,7 @@ import type { } from '../../../shared/update-status-types' import { UpdateErrorCardContent } from './UpdateErrorCardContent' import { translate } from '@/i18n/i18n' +import { linuxPackageCommandUnavailableText } from '@/lib/updater-message-copy' import { useMountedRef } from '@/hooks/useMountedRef' function copiedNote(packageFileName: string): string { @@ -91,7 +92,7 @@ export function LinuxPackageInstallRecoveryCard({ if (!instructions.ok) { if (isCurrentRecovery()) { setCommandUnavailable(true) - setActionError(instructions.message) + setActionError(linuxPackageCommandUnavailableText(instructions)) } return } diff --git a/src/renderer/src/components/UpdateCard.tsx b/src/renderer/src/components/UpdateCard.tsx index 701aa2d2c20..8c74b1dd690 100644 --- a/src/renderer/src/components/UpdateCard.tsx +++ b/src/renderer/src/components/UpdateCard.tsx @@ -6,6 +6,7 @@ import { useAppStore } from '../store' import { Card } from './ui/card' import { Button } from './ui/button' import { translate } from '@/i18n/i18n' +import { updaterMessageText } from '@/lib/updater-message-copy' import { UpdateCardStateContent } from './maintenance/update-card/UpdateCardStateContent' import { buildUpdateCardErrorModel } from './maintenance/update-card/update-card-error-model' import { @@ -168,7 +169,7 @@ export function UpdateCard(): React.JSX.Element | null { }) const linuxPackageRecovery = status.state === 'error' && status.recovery?.kind === 'linux-package-install' - ? { recovery: status.recovery, diagnostic: status.message } + ? { recovery: status.recovery, diagnostic: updaterMessageText(status) } : null const handleDismissWithAnimation = (): void => { if (prefersReducedMotion) { diff --git a/src/renderer/src/components/maintenance/update-card/update-card-error-model.ts b/src/renderer/src/components/maintenance/update-card/update-card-error-model.ts index d3e99ee4243..e2855699b48 100644 --- a/src/renderer/src/components/maintenance/update-card/update-card-error-model.ts +++ b/src/renderer/src/components/maintenance/update-card/update-card-error-model.ts @@ -7,6 +7,7 @@ import { } from '../../../../../shared/updater-windows-signature-check' import { getReleaseNotesUrlForVersion } from '../../../../../shared/release-channel' import { translate } from '@/i18n/i18n' +import { updaterMessageText } from '@/lib/updater-message-copy' import { isHttp2ProtocolError } from './update-card-visibility' export function buildUpdateCardErrorModel({ @@ -51,12 +52,14 @@ export function buildUpdateCardErrorModel({ } : null } + // Raw checks below match the host's English; display text translates known codes. + const message = updaterMessageText(status) if (status.installBlock && status.retryAction === 'install' && status.retryable !== false) { return { variant: 'waiting', ...getUpdateInstallBlockGuidance(status.installBlock), blockers: status.installBlock.instances, - detail: status.message, + detail: message, deferLabel: translate('updates.installBlock.later', 'Later'), footnote: { text: translate( @@ -73,8 +76,8 @@ export function buildUpdateCardErrorModel({ if (status.retryAction === 'install' && status.retryable !== false) { return { title: translate('auto.components.UpdateCard.4cf109845a', 'Update Error'), - summary: status.message, - detail: status.message, + summary: message, + detail: message, releaseUrl: getReleaseNotesUrlForVersion(cachedVersion), primaryAction: { label: translate('auto.components.UpdateCard.2c2d3e03ca', 'Try Again'), @@ -96,7 +99,7 @@ export function buildUpdateCardErrorModel({ 'auto.components.UpdateCard.d29740d175', 'The selected build could not be used.' ), - detail: status.message, + detail: message, primaryAction: { label: translate('auto.components.UpdateCard.37d45c9ec1', 'Choose Another Build'), onClick: onChooseLocalBuild @@ -115,7 +118,7 @@ export function buildUpdateCardErrorModel({ 'auto.components.UpdateCard.90559b14e3', 'This turns on a process-wide Electron networking switch after restart. Use it for corporate VPNs or proxies that reject HTTP/2 update downloads.' ), - detail: compatibilitySetupError ?? status.message, + detail: compatibilitySetupError ?? message, releaseUrl: getReleaseNotesUrlForVersion(cachedVersion), primaryAction: { label: translate('auto.components.UpdateCard.933c6fdf5b', 'Enable & Restart'), @@ -133,7 +136,7 @@ export function buildUpdateCardErrorModel({ 'auto.components.UpdateCard.092f09fc14', "The installer's publisher doesn't match Orca, so we stopped the update. Don't install this download; check official releases for a corrected version." ), - detail: status.message, + detail: message, releaseUrl: getReleaseNotesUrlForVersion(null), manualLabel: translate('auto.components.UpdateCard.c9ff9b9ec2', 'Check official releases') } @@ -145,7 +148,7 @@ export function buildUpdateCardErrorModel({ 'auto.components.UpdateCard.a05992a26b', "The signature check couldn't run — usually because antivirus software blocked it. Retry the download, or get the installer from our official releases." ), - detail: status.message, + detail: message, releaseUrl: getReleaseNotesUrlForVersion(cachedVersion), primaryAction: { label: translate('auto.components.UpdateCard.48565a32bc', 'Retry Download'), @@ -159,14 +162,14 @@ export function buildUpdateCardErrorModel({ : translate('auto.components.UpdateCard.updateCheckFailed', 'Update Check Failed'), summary: cachedVersion && status.retryable === false - ? status.message + ? message : cachedVersion ? translate( 'auto.components.UpdateCard.updateIncomplete', 'Could not complete the update.' ) : translate('auto.components.UpdateCard.checkFailed', 'Could not check for updates.'), - detail: status.message, + detail: message, releaseUrl: getReleaseNotesUrlForVersion(cachedVersion), primaryAction: cachedVersion && status.retryable !== false diff --git a/src/renderer/src/components/settings/GeneralUpdateSettingsSection.tsx b/src/renderer/src/components/settings/GeneralUpdateSettingsSection.tsx index 98df4c94667..b9f01cd5a62 100644 --- a/src/renderer/src/components/settings/GeneralUpdateSettingsSection.tsx +++ b/src/renderer/src/components/settings/GeneralUpdateSettingsSection.tsx @@ -8,6 +8,7 @@ import { Button } from '../ui/button' import { SearchableSetting } from './SearchableSetting' import { SettingsSubsectionHeader } from './SettingsFormControls' import { translate } from '@/i18n/i18n' +import { updaterMessageText } from '@/lib/updater-message-copy' import { getUpdateCheckClickOptions, getUpdateCheckHint } from '@/lib/update-check-click-options' import { GeneralRemoteServerUpdates } from './GeneralRemoteServerUpdates' import { ReleaseChannelSection } from './ReleaseChannelSection' @@ -243,17 +244,17 @@ export function GeneralUpdateSettingsSection(): React.JSX.Element { (updateStatus.installBlock ? getUpdateInstallBlockGuidance(updateStatus.installBlock).explainer : updateStatus.recovery?.kind === 'linux-package-install' - ? updateStatus.message + ? updaterMessageText(updateStatus) : updateVersionRef.current ? translate( 'auto.components.settings.GeneralUpdateSettingsSection.b9ad70c30d', 'Update error. {{value0}}', - { value0: updateStatus.message } + { value0: updaterMessageText(updateStatus) } ) : translate( 'auto.components.settings.GeneralUpdateSettingsSection.bd79d412f0', 'Update check failed. {{value0}}', - { value0: updateStatus.message } + { value0: updaterMessageText(updateStatus) } ))}

diff --git a/src/renderer/src/components/settings/ReleaseChannelSection.tsx b/src/renderer/src/components/settings/ReleaseChannelSection.tsx index 8f26908db67..06a5876ac40 100644 --- a/src/renderer/src/components/settings/ReleaseChannelSection.tsx +++ b/src/renderer/src/components/settings/ReleaseChannelSection.tsx @@ -8,6 +8,9 @@ import { ReleaseBuildPicker } from './ReleaseBuildPicker' import { SettingsSegmentedControl, SettingsSubsectionHeader } from './SettingsFormControls' import { Badge } from '../ui/badge' import { translate } from '@/i18n/i18n' +import { updaterMessageText } from '@/lib/updater-message-copy' +import { errorMessage } from '../../../../shared/error-message' +import type { UpdaterCodedMessage } from '../../../../shared/updater-failure-copy' import { getShortcutPlatform } from '@/lib/shortcut-platform' import { DEV_CHANNEL_PLATFORM_LABEL, @@ -57,7 +60,7 @@ export function ReleaseChannelSection(): React.JSX.Element { const [appVersion, setAppVersion] = useState(null) const [builds, setBuilds] = useState(null) - const [loadError, setLoadError] = useState(null) + const [loadError, setLoadError] = useState(null) const [loading, setLoading] = useState(false) const [selectedTag, setSelectedTag] = useState(null) @@ -102,14 +105,14 @@ export function ReleaseChannelSection(): React.JSX.Element { setSelectedTag(result.builds[0]?.tag ?? null) } else { setBuilds(null) - setLoadError(result.message) + setLoadError(result) } } catch (error) { if (isStale()) { return } setBuilds(null) - setLoadError(String((error as Error)?.message ?? error)) + setLoadError({ message: errorMessage(error) }) } finally { // Only the newest request may clear loading; old completions must not flash No builds. if (!isStale()) { @@ -312,7 +315,7 @@ export function ReleaseChannelSection(): React.JSX.Element {

{loadError ? ( -

{loadError}

+

{updaterMessageText(loadError)}

) : needsManualInstall && selectedBuild ? (

{translate( diff --git a/src/renderer/src/i18n/en-runtime-required.json b/src/renderer/src/i18n/en-runtime-required.json index 0dd2faebfc6..c16f1835250 100644 --- a/src/renderer/src/i18n/en-runtime-required.json +++ b/src/renderer/src/i18n/en-runtime-required.json @@ -2346,6 +2346,38 @@ "count_other": "{{count}} running terminals" } }, + "updates": { + "failure": { + "channelUnsupportedPlatform": "{{channel}} builds are produced only for macOS and Windows.", + "invalidReleaseTag": "\"{{tag}}\" is not a valid release tag.", + "linuxPackageExternallyManaged": "This copy of Orca is managed by your system package manager, so Orca cannot install updates itself. Update Orca through your distribution instead.", + "linuxPackageHashMismatch": "The downloaded package no longer matches the verified release, so Orca will not hand it to a package manager. Download the update again, or get it from the official release page.", + "linuxPackageInvalidPath": "The downloaded package is not at a usable path, so Orca cannot build a safe install command. Show the package and install it with your package manager.", + "linuxPackageManualInstall": "Quit Orca before running the system package install command.", + "linuxPackageMarkerUnusable": "Orca could not verify the installed Linux package format, so it will not install this update automatically. Download the update from the official release page and install it manually.", + "linuxPackageMetadataUnusable": "The downloaded package metadata could not be verified. Quit Orca before downloading and installing the update from the official release page.", + "linuxPackageMissing": "The downloaded package is no longer in the update cache. Download the update again, or get it from the official release page.", + "linuxPackageNoPackageManager": "No supported package manager was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.", + "linuxPackageNoSudo": "No sudo command was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.", + "linuxPackageNotRegular": "The downloaded package is no longer a valid file in the update cache. Download the update again, or get it from the official release page.", + "linuxPackageReadFailed": "Orca could not read the downloaded package. Download the update again, or get it from the official release page.", + "rateLimitedInMinute": "GitHub rate limit reached. Try again in about a minute.", + "rateLimitedInMinuteSignedOut": "GitHub rate limit reached. Try again in about a minute, or run `gh auth login` so Orca can use your account's higher limit.", + "rateLimitedInMinutesSignedOut_one": "GitHub rate limit reached. Try again in about {{count}} minute, or run `gh auth login` so Orca can use your account's higher limit.", + "rateLimitedInMinutesSignedOut_other": "GitHub rate limit reached. Try again in about {{count}} minutes, or run `gh auth login` so Orca can use your account's higher limit.", + "rateLimitedInMinutes_one": "GitHub rate limit reached. Try again in about {{count}} minute.", + "rateLimitedInMinutes_other": "GitHub rate limit reached. Try again in about {{count}} minutes.", + "rateLimitedSoon": "GitHub rate limit reached. Try again in a few minutes.", + "rateLimitedSoonSignedOut": "GitHub rate limit reached. Try again in a few minutes, or run `gh auth login` so Orca can use your account's higher limit.", + "releaseListFailed": "Could not list {{channel}} builds (HTTP {{status}}).", + "releaseListUnreadable": "Could not read the {{channel}} release list.", + "releaseNotReady": "A newer release isn't available for this device yet. Check again later.", + "releaseRepoNotFound": "No releases repository found at {{repo}}.", + "unknownReleaseChannel": "Unknown release channel \"{{channel}}\".", + "unsignedDevChannel": "{{channel}} builds are unsigned, and this signed build only installs updates signed by Orca's publisher. Download the installer from the release page and run it once — updates work normally from there, including back to Stable.", + "updateServerUnreachable": "Couldn't reach the update server. Try again in a few minutes." + } + }, "workspace": { "links": { "linkedCount_one": "{{count}} linked", diff --git a/src/renderer/src/i18n/locales/de.json b/src/renderer/src/i18n/locales/de.json index 027821a95cc..c1ea10632b6 100644 --- a/src/renderer/src/i18n/locales/de.json +++ b/src/renderer/src/i18n/locales/de.json @@ -21142,7 +21142,37 @@ "preparingAriaLabel": "Das Update wird vorbereitet. Zum Aufklappen klicken." }, "wslMoveInstallHeld": "Ein Projekt wird auf seinen WSL-Host verschoben. Das Update nach Abschluss des Verschiebens installieren.", - "wslMoveUndoInstallHeld": "Ein Projekt wird von seinem WSL-Host zurückgeholt. Das Update danach installieren." + "wslMoveUndoInstallHeld": "Ein Projekt wird von seinem WSL-Host zurückgeholt. Das Update danach installieren.", + "failure": { + "releaseNotReady": "Für dieses Gerät ist noch keine neuere Version verfügbar. Später erneut prüfen.", + "updateServerUnreachable": "Der Update-Server konnte nicht erreicht werden. In einigen Minuten erneut versuchen.", + "channelUnsupportedPlatform": "{{channel}}-Builds werden nur für macOS und Windows erstellt.", + "unsignedDevChannel": "{{channel}}-Builds sind nicht signiert. Dieser signierte Build installiert nur Updates, die vom Herausgeber von Orca signiert wurden. Das Installationsprogramm von der Release-Seite herunterladen und einmal ausführen – danach funktionieren Updates wie gewohnt, auch zurück zu Stable.", + "linuxPackageMissing": "Das heruntergeladene Paket ist nicht mehr im Update-Cache vorhanden. Das Update erneut herunterladen oder von der offiziellen Release-Seite beziehen.", + "linuxPackageNotRegular": "Das heruntergeladene Paket ist im Update-Cache keine gültige Datei mehr. Das Update erneut herunterladen oder von der offiziellen Release-Seite beziehen.", + "linuxPackageHashMismatch": "Das heruntergeladene Paket stimmt nicht mehr mit der überprüften Version überein. Orca übergibt es daher nicht an eine Paketverwaltung. Das Update erneut herunterladen oder von der offiziellen Release-Seite beziehen.", + "linuxPackageReadFailed": "Orca konnte das heruntergeladene Paket nicht lesen. Das Update erneut herunterladen oder von der offiziellen Release-Seite beziehen.", + "linuxPackageNoSudo": "In den Systemverzeichnissen wurde kein sudo-Befehl gefunden. Orca kann daher keinen sicheren Installationsbefehl erstellen. Das Paket anzeigen und über die Paketverwaltung installieren.", + "linuxPackageNoPackageManager": "In den Systemverzeichnissen wurde keine unterstützte Paketverwaltung gefunden. Orca kann daher keinen sicheren Installationsbefehl erstellen. Das Paket anzeigen und über die Paketverwaltung installieren.", + "linuxPackageInvalidPath": "Das heruntergeladene Paket liegt nicht unter einem verwendbaren Pfad. Orca kann daher keinen sicheren Installationsbefehl erstellen. Das Paket anzeigen und über die Paketverwaltung installieren.", + "linuxPackageMarkerUnusable": "Orca konnte das Format des installierten Linux-Pakets nicht überprüfen und installiert dieses Update daher nicht automatisch. Das Update von der offiziellen Release-Seite herunterladen und manuell installieren.", + "linuxPackageExternallyManaged": "Diese Orca-Installation wird von der Paketverwaltung des Systems verwaltet. Orca kann Updates daher nicht selbst installieren. Orca stattdessen über die Distribution aktualisieren.", + "linuxPackageManualInstall": "Orca beenden, bevor der Systembefehl zur Paketinstallation ausgeführt wird.", + "linuxPackageMetadataUnusable": "Die Metadaten des heruntergeladenen Pakets konnten nicht überprüft werden. Orca beenden, bevor das Update von der offiziellen Release-Seite heruntergeladen und installiert wird.", + "unknownReleaseChannel": "Unbekannter Veröffentlichungskanal „{{channel}}“.", + "invalidReleaseTag": "„{{tag}}“ ist kein gültiges Release-Tag.", + "releaseRepoNotFound": "Unter {{repo}} wurde kein Release-Repository gefunden.", + "releaseListFailed": "{{channel}}-Builds konnten nicht aufgelistet werden (HTTP {{status}}).", + "releaseListUnreadable": "Die Release-Liste für {{channel}} konnte nicht gelesen werden.", + "rateLimitedSoon": "GitHub-Ratenlimit erreicht. In einigen Minuten erneut versuchen.", + "rateLimitedSoonSignedOut": "GitHub-Ratenlimit erreicht. In einigen Minuten erneut versuchen oder `gh auth login` ausführen, damit Orca das höhere Limit deines Kontos nutzen kann.", + "rateLimitedInMinute": "GitHub-Ratenlimit erreicht. In etwa einer Minute erneut versuchen.", + "rateLimitedInMinuteSignedOut": "GitHub-Ratenlimit erreicht. In etwa einer Minute erneut versuchen oder `gh auth login` ausführen, damit Orca das höhere Limit deines Kontos nutzen kann.", + "rateLimitedInMinutes_one": "GitHub-Ratenlimit erreicht. In etwa {{count}} Minute erneut versuchen.", + "rateLimitedInMinutes_other": "GitHub-Ratenlimit erreicht. In etwa {{count}} Minuten erneut versuchen.", + "rateLimitedInMinutesSignedOut_one": "GitHub-Ratenlimit erreicht. In etwa {{count}} Minute erneut versuchen oder `gh auth login` ausführen, damit Orca das höhere Limit deines Kontos nutzen kann.", + "rateLimitedInMinutesSignedOut_other": "GitHub-Ratenlimit erreicht. In etwa {{count}} Minuten erneut versuchen oder `gh auth login` ausführen, damit Orca das höhere Limit deines Kontos nutzen kann." + } }, "jira": { "connect": { diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index c30c2cc3e47..f5d83eaf8d7 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -21563,7 +21563,37 @@ "preparingAriaLabel": "Preparing update. Click to expand." }, "wslMoveInstallHeld": "A project is moving to its WSL host. Install the update once the move finishes.", - "wslMoveUndoInstallHeld": "A project is coming back from its WSL host. Install the update once that finishes." + "wslMoveUndoInstallHeld": "A project is coming back from its WSL host. Install the update once that finishes.", + "failure": { + "releaseNotReady": "A newer release isn't available for this device yet. Check again later.", + "updateServerUnreachable": "Couldn't reach the update server. Try again in a few minutes.", + "channelUnsupportedPlatform": "{{channel}} builds are produced only for macOS and Windows.", + "unsignedDevChannel": "{{channel}} builds are unsigned, and this signed build only installs updates signed by Orca's publisher. Download the installer from the release page and run it once — updates work normally from there, including back to Stable.", + "linuxPackageMissing": "The downloaded package is no longer in the update cache. Download the update again, or get it from the official release page.", + "linuxPackageNotRegular": "The downloaded package is no longer a valid file in the update cache. Download the update again, or get it from the official release page.", + "linuxPackageHashMismatch": "The downloaded package no longer matches the verified release, so Orca will not hand it to a package manager. Download the update again, or get it from the official release page.", + "linuxPackageReadFailed": "Orca could not read the downloaded package. Download the update again, or get it from the official release page.", + "linuxPackageNoSudo": "No sudo command was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.", + "linuxPackageNoPackageManager": "No supported package manager was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.", + "linuxPackageInvalidPath": "The downloaded package is not at a usable path, so Orca cannot build a safe install command. Show the package and install it with your package manager.", + "linuxPackageMarkerUnusable": "Orca could not verify the installed Linux package format, so it will not install this update automatically. Download the update from the official release page and install it manually.", + "linuxPackageExternallyManaged": "This copy of Orca is managed by your system package manager, so Orca cannot install updates itself. Update Orca through your distribution instead.", + "linuxPackageManualInstall": "Quit Orca before running the system package install command.", + "linuxPackageMetadataUnusable": "The downloaded package metadata could not be verified. Quit Orca before downloading and installing the update from the official release page.", + "unknownReleaseChannel": "Unknown release channel \"{{channel}}\".", + "invalidReleaseTag": "\"{{tag}}\" is not a valid release tag.", + "releaseRepoNotFound": "No releases repository found at {{repo}}.", + "releaseListFailed": "Could not list {{channel}} builds (HTTP {{status}}).", + "releaseListUnreadable": "Could not read the {{channel}} release list.", + "rateLimitedSoon": "GitHub rate limit reached. Try again in a few minutes.", + "rateLimitedSoonSignedOut": "GitHub rate limit reached. Try again in a few minutes, or run `gh auth login` so Orca can use your account's higher limit.", + "rateLimitedInMinute": "GitHub rate limit reached. Try again in about a minute.", + "rateLimitedInMinuteSignedOut": "GitHub rate limit reached. Try again in about a minute, or run `gh auth login` so Orca can use your account's higher limit.", + "rateLimitedInMinutes_one": "GitHub rate limit reached. Try again in about {{count}} minute.", + "rateLimitedInMinutes_other": "GitHub rate limit reached. Try again in about {{count}} minutes.", + "rateLimitedInMinutesSignedOut_one": "GitHub rate limit reached. Try again in about {{count}} minute, or run `gh auth login` so Orca can use your account's higher limit.", + "rateLimitedInMinutesSignedOut_other": "GitHub rate limit reached. Try again in about {{count}} minutes, or run `gh auth login` so Orca can use your account's higher limit." + } }, "jira": { "connect": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index a1c85499876..a26be847c75 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -21805,7 +21805,39 @@ "browserGuidance": "Finaliza el proceso que aparece abajo y vuelve a intentarlo: su servidor seguirá funcionando sin navegador. Si otro equipo usa este Mac como host SSH, actualiza Orca en ese equipo para que el navegador pueda pausarse automáticamente durante las actualizaciones." }, "wslMoveInstallHeld": "Un proyecto se está moviendo a su host WSL. Instala la actualización cuando termine el movimiento.", - "wslMoveUndoInstallHeld": "Un proyecto está volviendo desde su host WSL. Instala la actualización cuando termine." + "wslMoveUndoInstallHeld": "Un proyecto está volviendo desde su host WSL. Instala la actualización cuando termine.", + "failure": { + "releaseNotReady": "Aún no hay una versión más reciente disponible para este dispositivo. Vuelve a comprobarlo más tarde.", + "updateServerUnreachable": "No se pudo contactar con el servidor de actualizaciones. Vuelve a intentarlo en unos minutos.", + "channelUnsupportedPlatform": "Las versiones de {{channel}} se generan solo para macOS y Windows.", + "unsignedDevChannel": "Las versiones de {{channel}} no están firmadas y esta versión firmada solo instala actualizaciones firmadas por el editor de Orca. Descarga el instalador desde la página de versiones y ejecútalo una vez; después, las actualizaciones funcionarán con normalidad, incluso para volver a Stable.", + "linuxPackageMissing": "El paquete descargado ya no está en la caché de actualizaciones. Descarga la actualización de nuevo u obtenla en la página oficial de versiones.", + "linuxPackageNotRegular": "El paquete descargado ya no es un archivo válido en la caché de actualizaciones. Descarga la actualización de nuevo u obtenla en la página oficial de versiones.", + "linuxPackageHashMismatch": "El paquete descargado ya no coincide con la versión verificada, por lo que Orca no lo entregará a un gestor de paquetes. Descarga la actualización de nuevo u obtenla en la página oficial de versiones.", + "linuxPackageReadFailed": "Orca no pudo leer el paquete descargado. Descarga la actualización de nuevo u obtenla en la página oficial de versiones.", + "linuxPackageNoSudo": "No se encontró el comando sudo en los directorios del sistema, por lo que Orca no puede generar un comando de instalación seguro. Muestra el paquete e instálalo con tu gestor de paquetes.", + "linuxPackageNoPackageManager": "No se encontró ningún gestor de paquetes compatible en los directorios del sistema, por lo que Orca no puede generar un comando de instalación seguro. Muestra el paquete e instálalo con tu gestor de paquetes.", + "linuxPackageInvalidPath": "El paquete descargado no está en una ruta utilizable, por lo que Orca no puede generar un comando de instalación seguro. Muestra el paquete e instálalo con tu gestor de paquetes.", + "linuxPackageMarkerUnusable": "Orca no pudo verificar el formato del paquete de Linux instalado, por lo que no instalará esta actualización automáticamente. Descarga la actualización desde la página oficial de versiones e instálala manualmente.", + "linuxPackageExternallyManaged": "El gestor de paquetes del sistema administra esta copia de Orca, por lo que Orca no puede instalar actualizaciones por sí mismo. Actualiza Orca a través de tu distribución.", + "linuxPackageManualInstall": "Sal de Orca antes de ejecutar el comando de instalación de paquetes del sistema.", + "linuxPackageMetadataUnusable": "No se pudieron verificar los metadatos del paquete descargado. Sal de Orca antes de descargar e instalar la actualización desde la página oficial de versiones.", + "unknownReleaseChannel": "Canal de lanzamiento desconocido «{{channel}}».", + "invalidReleaseTag": "«{{tag}}» no es una etiqueta de versión válida.", + "releaseRepoNotFound": "No se encontró ningún repositorio de versiones en {{repo}}.", + "releaseListFailed": "No se pudieron listar las versiones de {{channel}} (HTTP {{status}}).", + "releaseListUnreadable": "No se pudo leer la lista de versiones de {{channel}}.", + "rateLimitedSoon": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en unos minutos.", + "rateLimitedSoonSignedOut": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en unos minutos o ejecuta `gh auth login` para que Orca pueda usar el límite más alto de tu cuenta.", + "rateLimitedInMinute": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente un minuto.", + "rateLimitedInMinuteSignedOut": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente un minuto o ejecuta `gh auth login` para que Orca pueda usar el límite más alto de tu cuenta.", + "rateLimitedInMinutes_one": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente {{count}} minuto.", + "rateLimitedInMinutes_other": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente {{count}} minutos.", + "rateLimitedInMinutes_many": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente {{count}} minutos.", + "rateLimitedInMinutesSignedOut_one": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente {{count}} minuto o ejecuta `gh auth login` para que Orca pueda usar el límite más alto de tu cuenta.", + "rateLimitedInMinutesSignedOut_other": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente {{count}} minutos o ejecuta `gh auth login` para que Orca pueda usar el límite más alto de tu cuenta.", + "rateLimitedInMinutesSignedOut_many": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en aproximadamente {{count}} minutos o ejecuta `gh auth login` para que Orca pueda usar el límite más alto de tu cuenta." + } }, "jira": { "savedFilters": { diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index 94f889fa32a..c184c3de042 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -21899,7 +21899,39 @@ "browserGuidance": "Terminez le processus ci-dessous, puis réessayez : son serveur continue de fonctionner sans navigateur. Si un autre ordinateur utilise ce Mac comme hôte SSH, la mise à jour d’Orca sur cet ordinateur permet au navigateur de se mettre automatiquement en pause lors des mises à jour." }, "wslMoveInstallHeld": "Un projet est en cours de déplacement vers son hôte WSL. Installez la mise à jour une fois le déplacement terminé.", - "wslMoveUndoInstallHeld": "Un projet est en train de revenir de son hôte WSL. Installez la mise à jour une fois l'opération terminée." + "wslMoveUndoInstallHeld": "Un projet est en train de revenir de son hôte WSL. Installez la mise à jour une fois l'opération terminée.", + "failure": { + "releaseNotReady": "Aucune version plus récente n'est encore disponible pour cet appareil. Vérifiez à nouveau plus tard.", + "updateServerUnreachable": "Impossible de joindre le serveur de mise à jour. Réessayez dans quelques minutes.", + "channelUnsupportedPlatform": "Les versions {{channel}} sont produites uniquement pour macOS et Windows.", + "unsignedDevChannel": "Les versions {{channel}} ne sont pas signées, et cette version signée n'installe que les mises à jour signées par l'éditeur d'Orca. Téléchargez le programme d'installation depuis la page des versions et exécutez-le une fois. Les mises à jour fonctionneront ensuite normalement, y compris pour revenir à Stable.", + "linuxPackageMissing": "Le package téléchargé n'est plus dans le cache des mises à jour. Téléchargez à nouveau la mise à jour ou récupérez-la sur la page officielle des versions.", + "linuxPackageNotRegular": "Le package téléchargé n'est plus un fichier valide dans le cache des mises à jour. Téléchargez à nouveau la mise à jour ou récupérez-la sur la page officielle des versions.", + "linuxPackageHashMismatch": "Le package téléchargé ne correspond plus à la version vérifiée. Orca ne le transmettra donc pas à un gestionnaire de packages. Téléchargez à nouveau la mise à jour ou récupérez-la sur la page officielle des versions.", + "linuxPackageReadFailed": "Orca n'a pas pu lire le package téléchargé. Téléchargez à nouveau la mise à jour ou récupérez-la sur la page officielle des versions.", + "linuxPackageNoSudo": "La commande sudo est introuvable dans les répertoires système. Orca ne peut donc pas générer de commande d'installation sûre. Affichez le package et installez-le avec votre gestionnaire de packages.", + "linuxPackageNoPackageManager": "Aucun gestionnaire de packages pris en charge n'a été trouvé dans les répertoires système. Orca ne peut donc pas générer de commande d'installation sûre. Affichez le package et installez-le avec votre gestionnaire de packages.", + "linuxPackageInvalidPath": "Le chemin du package téléchargé n'est pas utilisable. Orca ne peut donc pas générer de commande d'installation sûre. Affichez le package et installez-le avec votre gestionnaire de packages.", + "linuxPackageMarkerUnusable": "Orca n'a pas pu vérifier le format du package Linux installé et n'installera donc pas cette mise à jour automatiquement. Téléchargez la mise à jour depuis la page officielle des versions et installez-la manuellement.", + "linuxPackageExternallyManaged": "Cette copie d'Orca est gérée par votre gestionnaire de packages système. Orca ne peut donc pas installer les mises à jour lui-même. Mettez plutôt Orca à jour via votre distribution.", + "linuxPackageManualInstall": "Quittez Orca avant d'exécuter la commande système d'installation de packages.", + "linuxPackageMetadataUnusable": "Les métadonnées du package téléchargé n'ont pas pu être vérifiées. Quittez Orca avant de télécharger et d'installer la mise à jour depuis la page officielle des versions.", + "unknownReleaseChannel": "Canal de publication inconnu « {{channel}} ».", + "invalidReleaseTag": "« {{tag}} » n'est pas un tag de version valide.", + "releaseRepoNotFound": "Aucun dépôt de versions trouvé à l'adresse {{repo}}.", + "releaseListFailed": "Impossible de lister les versions {{channel}} (HTTP {{status}}).", + "releaseListUnreadable": "Impossible de lire la liste des versions {{channel}}.", + "rateLimitedSoon": "Limite de débit GitHub atteinte. Réessayez dans quelques minutes.", + "rateLimitedSoonSignedOut": "Limite de débit GitHub atteinte. Réessayez dans quelques minutes ou exécutez `gh auth login` pour qu'Orca puisse utiliser la limite plus élevée de votre compte.", + "rateLimitedInMinute": "Limite de débit GitHub atteinte. Réessayez dans environ une minute.", + "rateLimitedInMinuteSignedOut": "Limite de débit GitHub atteinte. Réessayez dans environ une minute ou exécutez `gh auth login` pour qu'Orca puisse utiliser la limite plus élevée de votre compte.", + "rateLimitedInMinutes_one": "Limite de débit GitHub atteinte. Réessayez dans environ {{count}} minute.", + "rateLimitedInMinutes_other": "Limite de débit GitHub atteinte. Réessayez dans environ {{count}} minutes.", + "rateLimitedInMinutes_many": "Limite de débit GitHub atteinte. Réessayez dans environ {{count}} minutes.", + "rateLimitedInMinutesSignedOut_one": "Limite de débit GitHub atteinte. Réessayez dans environ {{count}} minute ou exécutez `gh auth login` pour qu'Orca puisse utiliser la limite plus élevée de votre compte.", + "rateLimitedInMinutesSignedOut_other": "Limite de débit GitHub atteinte. Réessayez dans environ {{count}} minutes ou exécutez `gh auth login` pour qu'Orca puisse utiliser la limite plus élevée de votre compte.", + "rateLimitedInMinutesSignedOut_many": "Limite de débit GitHub atteinte. Réessayez dans environ {{count}} minutes ou exécutez `gh auth login` pour qu'Orca puisse utiliser la limite plus élevée de votre compte." + } }, "onboarding": { "language": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 72dbd2f47e9..f75449a9228 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -21233,7 +21233,35 @@ "browserSummary": "バックグラウンド Orca サーバーのブラウザはまだこのアプリを使用しています。" }, "wslMoveInstallHeld": "プロジェクトを WSL ホストへ移動しています。移動が完了してからアップデートをインストールしてください。", - "wslMoveUndoInstallHeld": "プロジェクトを WSL ホストから戻しています。完了してからアップデートをインストールしてください。" + "wslMoveUndoInstallHeld": "プロジェクトを WSL ホストから戻しています。完了してからアップデートをインストールしてください。", + "failure": { + "releaseNotReady": "このデバイス向けの新しいリリースはまだ利用できません。後でもう一度確認してください。", + "updateServerUnreachable": "更新サーバーに接続できませんでした。数分後にもう一度お試しください。", + "channelUnsupportedPlatform": "{{channel}} ビルドは macOS と Windows 向けにのみ提供されます。", + "unsignedDevChannel": "{{channel}} ビルドは未署名で、この署名済みビルドでは Orca の発行元が署名した更新のみをインストールできます。リリースページからインストーラーをダウンロードして一度実行してください。その後は Stable に戻す場合も含め、通常どおり更新できます。", + "linuxPackageMissing": "ダウンロードしたパッケージが更新キャッシュにありません。更新を再ダウンロードするか、公式リリースページから入手してください。", + "linuxPackageNotRegular": "更新キャッシュ内のダウンロード済みパッケージが有効なファイルではなくなっています。更新を再ダウンロードするか、公式リリースページから入手してください。", + "linuxPackageHashMismatch": "ダウンロードしたパッケージが検証済みリリースと一致しなくなったため、Orca はパッケージマネージャーに渡しません。更新を再ダウンロードするか、公式リリースページから入手してください。", + "linuxPackageReadFailed": "Orca はダウンロードしたパッケージを読み取れませんでした。更新を再ダウンロードするか、公式リリースページから入手してください。", + "linuxPackageNoSudo": "システムディレクトリに sudo コマンドが見つからないため、Orca は安全なインストールコマンドを生成できません。パッケージを表示し、パッケージマネージャーでインストールしてください。", + "linuxPackageNoPackageManager": "システムディレクトリに対応するパッケージマネージャーが見つからないため、Orca は安全なインストールコマンドを生成できません。パッケージを表示し、パッケージマネージャーでインストールしてください。", + "linuxPackageInvalidPath": "ダウンロードしたパッケージのパスを使用できないため、Orca は安全なインストールコマンドを生成できません。パッケージを表示し、パッケージマネージャーでインストールしてください。", + "linuxPackageMarkerUnusable": "Orca はインストール済み Linux パッケージの形式を確認できなかったため、この更新を自動インストールしません。公式リリースページから更新をダウンロードし、手動でインストールしてください。", + "linuxPackageExternallyManaged": "この Orca はシステムのパッケージマネージャーで管理されているため、Orca 自体では更新をインストールできません。ディストリビューションの仕組みで Orca を更新してください。", + "linuxPackageManualInstall": "システムのパッケージインストールコマンドを実行する前に Orca を終了してください。", + "linuxPackageMetadataUnusable": "ダウンロードしたパッケージのメタデータを検証できませんでした。Orca を終了してから、公式リリースページから更新をダウンロードしてインストールしてください。", + "unknownReleaseChannel": "不明なリリースチャンネル「{{channel}}」。", + "invalidReleaseTag": "「{{tag}}」は有効なリリースタグではありません。", + "releaseRepoNotFound": "{{repo}} にリリース用リポジトリが見つかりません。", + "releaseListFailed": "{{channel}} ビルドの一覧を取得できませんでした(HTTP {{status}})。", + "releaseListUnreadable": "{{channel}} のリリース一覧を読み取れませんでした。", + "rateLimitedSoon": "GitHub のレート制限に達しました。数分後にもう一度お試しください。", + "rateLimitedSoonSignedOut": "GitHub のレート制限に達しました。数分後にもう一度試すか、`gh auth login` を実行して Orca がアカウントのより高い上限を利用できるようにしてください。", + "rateLimitedInMinute": "GitHub のレート制限に達しました。約 1 分後にもう一度お試しください。", + "rateLimitedInMinuteSignedOut": "GitHub のレート制限に達しました。約 1 分後にもう一度試すか、`gh auth login` を実行して Orca がアカウントのより高い上限を利用できるようにしてください。", + "rateLimitedInMinutes_other": "GitHub のレート制限に達しました。約 {{count}} 分後にもう一度お試しください。", + "rateLimitedInMinutesSignedOut_other": "GitHub のレート制限に達しました。約 {{count}} 分後にもう一度試すか、`gh auth login` を実行して Orca がアカウントのより高い上限を利用できるようにしてください。" + } }, "onboarding": { "language": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 16a4956779f..3aabe8d0d60 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -21233,7 +21233,35 @@ "browserGuidance": "아래 프로세스를 종료한 후 다시 시도하세요. 서버는 브라우저 없이 계속 실행됩니다. 다른 컴퓨터가 이 Mac을 SSH 호스트로 사용하는 경우 해당 컴퓨터의 Orca를 업데이트하면 업데이트 시 브라우저가 자동으로 일시 중지됩니다." }, "wslMoveInstallHeld": "프로젝트를 WSL 호스트로 옮기는 중입니다. 이동이 끝난 후 업데이트를 설치하세요.", - "wslMoveUndoInstallHeld": "프로젝트를 WSL 호스트에서 다시 가져오는 중입니다. 완료된 후 업데이트를 설치하세요." + "wslMoveUndoInstallHeld": "프로젝트를 WSL 호스트에서 다시 가져오는 중입니다. 완료된 후 업데이트를 설치하세요.", + "failure": { + "releaseNotReady": "이 기기에서는 아직 새 릴리스를 사용할 수 없습니다. 나중에 다시 확인하세요.", + "updateServerUnreachable": "업데이트 서버에 연결할 수 없습니다. 몇 분 후 다시 시도하세요.", + "channelUnsupportedPlatform": "{{channel}} 빌드는 macOS 및 Windows용으로만 제공됩니다.", + "unsignedDevChannel": "{{channel}} 빌드는 서명되지 않았으며, 현재 서명된 빌드는 Orca 게시자가 서명한 업데이트만 설치합니다. 릴리스 페이지에서 설치 프로그램을 다운로드하여 한 번 실행하세요. 이후에는 Stable로 돌아가는 경우를 포함해 업데이트가 정상적으로 작동합니다.", + "linuxPackageMissing": "다운로드한 패키지가 업데이트 캐시에 더 이상 없습니다. 업데이트를 다시 다운로드하거나 공식 릴리스 페이지에서 받으세요.", + "linuxPackageNotRegular": "다운로드한 패키지가 업데이트 캐시에서 더 이상 유효한 파일이 아닙니다. 업데이트를 다시 다운로드하거나 공식 릴리스 페이지에서 받으세요.", + "linuxPackageHashMismatch": "다운로드한 패키지가 검증된 릴리스와 더 이상 일치하지 않아 Orca가 패키지 관리자에 전달하지 않습니다. 업데이트를 다시 다운로드하거나 공식 릴리스 페이지에서 받으세요.", + "linuxPackageReadFailed": "Orca가 다운로드한 패키지를 읽지 못했습니다. 업데이트를 다시 다운로드하거나 공식 릴리스 페이지에서 받으세요.", + "linuxPackageNoSudo": "시스템 디렉터리에서 sudo 명령을 찾지 못해 Orca가 안전한 설치 명령을 생성할 수 없습니다. 패키지를 표시하고 패키지 관리자로 설치하세요.", + "linuxPackageNoPackageManager": "시스템 디렉터리에서 지원되는 패키지 관리자를 찾지 못해 Orca가 안전한 설치 명령을 생성할 수 없습니다. 패키지를 표시하고 패키지 관리자로 설치하세요.", + "linuxPackageInvalidPath": "다운로드한 패키지가 사용 가능한 경로에 있지 않아 Orca가 안전한 설치 명령을 생성할 수 없습니다. 패키지를 표시하고 패키지 관리자로 설치하세요.", + "linuxPackageMarkerUnusable": "Orca가 설치된 Linux 패키지 형식을 확인하지 못해 이 업데이트를 자동으로 설치하지 않습니다. 공식 릴리스 페이지에서 업데이트를 다운로드하고 수동으로 설치하세요.", + "linuxPackageExternallyManaged": "이 Orca는 시스템 패키지 관리자가 관리하므로 Orca가 직접 업데이트를 설치할 수 없습니다. 배포판을 통해 Orca를 업데이트하세요.", + "linuxPackageManualInstall": "시스템 패키지 설치 명령을 실행하기 전에 Orca를 종료하세요.", + "linuxPackageMetadataUnusable": "다운로드한 패키지의 메타데이터를 검증할 수 없습니다. Orca를 종료한 후 공식 릴리스 페이지에서 업데이트를 다운로드하고 설치하세요.", + "unknownReleaseChannel": "알 수 없는 출시 채널 \"{{channel}}\"입니다.", + "invalidReleaseTag": "\"{{tag}}\"은(는) 유효한 릴리스 태그가 아닙니다.", + "releaseRepoNotFound": "{{repo}}에서 릴리스 저장소를 찾을 수 없습니다.", + "releaseListFailed": "{{channel}} 빌드 목록을 가져올 수 없습니다(HTTP {{status}}).", + "releaseListUnreadable": "{{channel}} 릴리스 목록을 읽을 수 없습니다.", + "rateLimitedSoon": "GitHub 속도 제한에 도달했습니다. 몇 분 후 다시 시도하세요.", + "rateLimitedSoonSignedOut": "GitHub 속도 제한에 도달했습니다. 몇 분 후 다시 시도하거나 `gh auth login`을 실행하여 Orca가 계정의 더 높은 한도를 사용할 수 있도록 하세요.", + "rateLimitedInMinute": "GitHub 속도 제한에 도달했습니다. 약 1분 후 다시 시도하세요.", + "rateLimitedInMinuteSignedOut": "GitHub 속도 제한에 도달했습니다. 약 1분 후 다시 시도하거나 `gh auth login`을 실행하여 Orca가 계정의 더 높은 한도를 사용할 수 있도록 하세요.", + "rateLimitedInMinutes_other": "GitHub 속도 제한에 도달했습니다. 약 {{count}}분 후 다시 시도하세요.", + "rateLimitedInMinutesSignedOut_other": "GitHub 속도 제한에 도달했습니다. 약 {{count}}분 후 다시 시도하거나 `gh auth login`을 실행하여 Orca가 계정의 더 높은 한도를 사용할 수 있도록 하세요." + } }, "onboarding": { "language": { diff --git a/src/renderer/src/i18n/locales/pt.json b/src/renderer/src/i18n/locales/pt.json index 82d8746029b..e180b54407b 100644 --- a/src/renderer/src/i18n/locales/pt.json +++ b/src/renderer/src/i18n/locales/pt.json @@ -21559,7 +21559,39 @@ "preparingAriaLabel": "Preparando atualização. Clique para expandir." }, "wslMoveInstallHeld": "Um projeto está sendo movido para o host WSL dele. Instale a atualização quando a movimentação terminar.", - "wslMoveUndoInstallHeld": "Um projeto está voltando do host WSL dele. Instale a atualização quando isso terminar." + "wslMoveUndoInstallHeld": "Um projeto está voltando do host WSL dele. Instale a atualização quando isso terminar.", + "failure": { + "releaseNotReady": "Uma versão mais recente ainda não está disponível para este dispositivo. Verifique novamente mais tarde.", + "updateServerUnreachable": "Não foi possível acessar o servidor de atualização. Tente novamente em alguns minutos.", + "channelUnsupportedPlatform": "As builds {{channel}} são produzidas apenas para macOS e Windows.", + "unsignedDevChannel": "As builds {{channel}} não são assinadas, e esta build assinada só instala atualizações assinadas pelo publicador do Orca. Baixe o instalador na página de releases e execute-o uma vez — depois disso, as atualizações funcionam normalmente, inclusive para voltar ao Stable.", + "linuxPackageMissing": "O pacote baixado não está mais no cache de atualizações. Baixe a atualização novamente ou obtenha-a na página oficial de releases.", + "linuxPackageNotRegular": "O pacote baixado não é mais um arquivo válido no cache de atualizações. Baixe a atualização novamente ou obtenha-a na página oficial de releases.", + "linuxPackageHashMismatch": "O pacote baixado não corresponde mais ao release verificado, por isso o Orca não o encaminhará a um gerenciador de pacotes. Baixe a atualização novamente ou obtenha-a na página oficial de releases.", + "linuxPackageReadFailed": "O Orca não conseguiu ler o pacote baixado. Baixe a atualização novamente ou obtenha-a na página oficial de releases.", + "linuxPackageNoSudo": "O comando sudo não foi encontrado nos diretórios do sistema, por isso o Orca não pode gerar um comando de instalação seguro. Exiba o pacote e instale-o com seu gerenciador de pacotes.", + "linuxPackageNoPackageManager": "Nenhum gerenciador de pacotes compatível foi encontrado nos diretórios do sistema, por isso o Orca não pode gerar um comando de instalação seguro. Exiba o pacote e instale-o com seu gerenciador de pacotes.", + "linuxPackageInvalidPath": "O pacote baixado não está em um caminho utilizável, por isso o Orca não pode gerar um comando de instalação seguro. Exiba o pacote e instale-o com seu gerenciador de pacotes.", + "linuxPackageMarkerUnusable": "O Orca não conseguiu verificar o formato do pacote Linux instalado, por isso não instalará esta atualização automaticamente. Baixe a atualização na página oficial de releases e instale-a manualmente.", + "linuxPackageExternallyManaged": "Esta cópia do Orca é gerenciada pelo gerenciador de pacotes do sistema, por isso o Orca não pode instalar atualizações por conta própria. Atualize o Orca pela sua distribuição.", + "linuxPackageManualInstall": "Encerre o Orca antes de executar o comando de instalação de pacotes do sistema.", + "linuxPackageMetadataUnusable": "Não foi possível verificar os metadados do pacote baixado. Encerre o Orca antes de baixar e instalar a atualização pela página oficial de releases.", + "unknownReleaseChannel": "Canal de versão desconhecido \"{{channel}}\".", + "invalidReleaseTag": "\"{{tag}}\" não é uma tag de release válida.", + "releaseRepoNotFound": "Nenhum repositório de releases encontrado em {{repo}}.", + "releaseListFailed": "Não foi possível listar as builds {{channel}} (HTTP {{status}}).", + "releaseListUnreadable": "Não foi possível ler a lista de releases de {{channel}}.", + "rateLimitedSoon": "Limite de taxa do GitHub atingido. Tente novamente em alguns minutos.", + "rateLimitedSoonSignedOut": "Limite de taxa do GitHub atingido. Tente novamente em alguns minutos ou execute `gh auth login` para que o Orca possa usar o limite maior da sua conta.", + "rateLimitedInMinute": "Limite de taxa do GitHub atingido. Tente novamente em cerca de um minuto.", + "rateLimitedInMinuteSignedOut": "Limite de taxa do GitHub atingido. Tente novamente em cerca de um minuto ou execute `gh auth login` para que o Orca possa usar o limite maior da sua conta.", + "rateLimitedInMinutes_one": "Limite de taxa do GitHub atingido. Tente novamente em cerca de {{count}} minuto.", + "rateLimitedInMinutes_other": "Limite de taxa do GitHub atingido. Tente novamente em cerca de {{count}} minutos.", + "rateLimitedInMinutes_many": "Limite de taxa do GitHub atingido. Tente novamente em cerca de {{count}} minutos.", + "rateLimitedInMinutesSignedOut_one": "Limite de taxa do GitHub atingido. Tente novamente em cerca de {{count}} minuto ou execute `gh auth login` para que o Orca possa usar o limite maior da sua conta.", + "rateLimitedInMinutesSignedOut_other": "Limite de taxa do GitHub atingido. Tente novamente em cerca de {{count}} minutos ou execute `gh auth login` para que o Orca possa usar o limite maior da sua conta.", + "rateLimitedInMinutesSignedOut_many": "Limite de taxa do GitHub atingido. Tente novamente em cerca de {{count}} minutos ou execute `gh auth login` para que o Orca possa usar o limite maior da sua conta." + } }, "jira": { "connect": { diff --git a/src/renderer/src/i18n/locales/ru.json b/src/renderer/src/i18n/locales/ru.json index 294500cde20..2e02398fd55 100644 --- a/src/renderer/src/i18n/locales/ru.json +++ b/src/renderer/src/i18n/locales/ru.json @@ -21795,7 +21795,41 @@ "preparingAriaLabel": "Подготовка обновления. Нажмите, чтобы развернуть." }, "wslMoveInstallHeld": "Проект переносится на свой хост WSL. Установите обновление, когда перенос завершится.", - "wslMoveUndoInstallHeld": "Проект возвращается со своего хоста WSL. Установите обновление, когда это завершится." + "wslMoveUndoInstallHeld": "Проект возвращается со своего хоста WSL. Установите обновление, когда это завершится.", + "failure": { + "releaseNotReady": "Более новый выпуск пока недоступен для этого устройства. Проверьте позже.", + "updateServerUnreachable": "Не удалось связаться с сервером обновлений. Повторите попытку через несколько минут.", + "channelUnsupportedPlatform": "Сборки {{channel}} выпускаются только для macOS и Windows.", + "unsignedDevChannel": "Сборки {{channel}} не подписаны, а эта подписанная сборка устанавливает только обновления с подписью издателя Orca. Скачайте установщик со страницы выпусков и запустите его один раз — после этого обновления будут работать как обычно, включая возврат на Stable.", + "linuxPackageMissing": "Загруженного пакета больше нет в кэше обновлений. Загрузите обновление ещё раз или получите его на официальной странице выпусков.", + "linuxPackageNotRegular": "Загруженный пакет больше не является допустимым файлом в кэше обновлений. Загрузите обновление ещё раз или получите его на официальной странице выпусков.", + "linuxPackageHashMismatch": "Загруженный пакет больше не соответствует проверенному выпуску, поэтому Orca не передаст его менеджеру пакетов. Загрузите обновление ещё раз или получите его на официальной странице выпусков.", + "linuxPackageReadFailed": "Orca не удалось прочитать загруженный пакет. Загрузите обновление ещё раз или получите его на официальной странице выпусков.", + "linuxPackageNoSudo": "Команда sudo не найдена в системных каталогах, поэтому Orca не может сформировать безопасную команду установки. Покажите пакет и установите его через менеджер пакетов.", + "linuxPackageNoPackageManager": "В системных каталогах не найден поддерживаемый менеджер пакетов, поэтому Orca не может сформировать безопасную команду установки. Покажите пакет и установите его через менеджер пакетов.", + "linuxPackageInvalidPath": "Путь к загруженному пакету непригоден для использования, поэтому Orca не может сформировать безопасную команду установки. Покажите пакет и установите его через менеджер пакетов.", + "linuxPackageMarkerUnusable": "Orca не удалось проверить формат установленного пакета Linux, поэтому это обновление не будет установлено автоматически. Загрузите обновление с официальной страницы выпусков и установите его вручную.", + "linuxPackageExternallyManaged": "Эта копия Orca управляется системным менеджером пакетов, поэтому Orca не может устанавливать обновления самостоятельно. Обновите Orca средствами вашего дистрибутива.", + "linuxPackageManualInstall": "Завершите работу Orca перед выполнением системной команды установки пакета.", + "linuxPackageMetadataUnusable": "Не удалось проверить метаданные загруженного пакета. Завершите работу Orca перед загрузкой и установкой обновления с официальной страницы выпусков.", + "unknownReleaseChannel": "Неизвестный канал выпусков «{{channel}}».", + "invalidReleaseTag": "«{{tag}}» не является допустимым тегом выпуска.", + "releaseRepoNotFound": "Репозиторий выпусков по адресу {{repo}} не найден.", + "releaseListFailed": "Не удалось получить список сборок {{channel}} (HTTP {{status}}).", + "releaseListUnreadable": "Не удалось прочитать список выпусков {{channel}}.", + "rateLimitedSoon": "Достигнут лимит запросов GitHub. Повторите попытку через несколько минут.", + "rateLimitedSoonSignedOut": "Достигнут лимит запросов GitHub. Повторите попытку через несколько минут или выполните `gh auth login`, чтобы Orca могла использовать более высокий лимит вашей учётной записи.", + "rateLimitedInMinute": "Достигнут лимит запросов GitHub. Повторите попытку примерно через минуту.", + "rateLimitedInMinuteSignedOut": "Достигнут лимит запросов GitHub. Повторите попытку примерно через минуту или выполните `gh auth login`, чтобы Orca могла использовать более высокий лимит вашей учётной записи.", + "rateLimitedInMinutes_one": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минуту.", + "rateLimitedInMinutes_other": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минуты.", + "rateLimitedInMinutesSignedOut_one": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минуту или выполните `gh auth login`, чтобы Orca могла использовать более высокий лимит вашей учётной записи.", + "rateLimitedInMinutesSignedOut_other": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минуты или выполните `gh auth login`, чтобы Orca могла использовать более высокий лимит вашей учётной записи.", + "rateLimitedInMinutes_few": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минуты.", + "rateLimitedInMinutes_many": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минут.", + "rateLimitedInMinutesSignedOut_few": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минуты или выполните `gh auth login`, чтобы Orca могла использовать более высокий лимит вашей учётной записи.", + "rateLimitedInMinutesSignedOut_many": "Достигнут лимит запросов GitHub. Повторите попытку примерно через {{count}} минут или выполните `gh auth login`, чтобы Orca могла использовать более высокий лимит вашей учётной записи." + } }, "jira": { "connect": { diff --git a/src/renderer/src/i18n/locales/tr.json b/src/renderer/src/i18n/locales/tr.json index 191f685c005..c6f99ed30f2 100644 --- a/src/renderer/src/i18n/locales/tr.json +++ b/src/renderer/src/i18n/locales/tr.json @@ -21123,7 +21123,37 @@ "preparingAriaLabel": "Güncelleme hazırlanıyor. Genişletmek için tıklayın." }, "wslMoveInstallHeld": "Bir proje WSL ana makinesine taşınıyor. Taşıma bittiğinde güncellemeyi yükleyin.", - "wslMoveUndoInstallHeld": "Bir proje WSL ana makinesinden geri getiriliyor. Bu bittiğinde güncellemeyi yükleyin." + "wslMoveUndoInstallHeld": "Bir proje WSL ana makinesinden geri getiriliyor. Bu bittiğinde güncellemeyi yükleyin.", + "failure": { + "releaseNotReady": "Bu cihaz için daha yeni bir sürüm henüz kullanıma sunulmadı. Daha sonra tekrar kontrol edin.", + "updateServerUnreachable": "Güncelleme sunucusuna ulaşılamadı. Birkaç dakika sonra tekrar deneyin.", + "channelUnsupportedPlatform": "{{channel}} sürümleri yalnızca macOS ve Windows için üretilir.", + "unsignedDevChannel": "{{channel}} sürümleri imzasızdır ve bu imzalı sürüm yalnızca Orca'nın yayımcısının imzaladığı güncellemeleri yükler. Yükleyiciyi sürüm sayfasından indirip bir kez çalıştırın; bundan sonra Kararlı kanalına dönüş dahil güncellemeler normal şekilde çalışır.", + "linuxPackageMissing": "İndirilen paket artık güncelleme önbelleğinde değil. Güncellemeyi yeniden indirin veya resmi sürüm sayfasından edinin.", + "linuxPackageNotRegular": "İndirilen paket artık güncelleme önbelleğinde geçerli bir dosya değil. Güncellemeyi yeniden indirin veya resmi sürüm sayfasından edinin.", + "linuxPackageHashMismatch": "İndirilen paket artık doğrulanmış sürümle eşleşmediğinden Orca paketi bir paket yöneticisine aktarmayacak. Güncellemeyi yeniden indirin veya resmi sürüm sayfasından edinin.", + "linuxPackageReadFailed": "Orca indirilen paketi okuyamadı. Güncellemeyi yeniden indirin veya resmi sürüm sayfasından edinin.", + "linuxPackageNoSudo": "Sistem dizinlerinde sudo komutu bulunamadığından Orca güvenli bir kurulum komutu oluşturamıyor. Paketi gösterin ve paket yöneticinizle yükleyin.", + "linuxPackageNoPackageManager": "Sistem dizinlerinde desteklenen bir paket yöneticisi bulunamadığından Orca güvenli bir kurulum komutu oluşturamıyor. Paketi gösterin ve paket yöneticinizle yükleyin.", + "linuxPackageInvalidPath": "İndirilen paket kullanılabilir bir yolda olmadığından Orca güvenli bir kurulum komutu oluşturamıyor. Paketi gösterin ve paket yöneticinizle yükleyin.", + "linuxPackageMarkerUnusable": "Orca kurulu Linux paketinin biçimini doğrulayamadığından bu güncellemeyi otomatik yüklemeyecek. Güncellemeyi resmi sürüm sayfasından indirin ve elle yükleyin.", + "linuxPackageExternallyManaged": "Bu Orca kopyası sistem paket yöneticiniz tarafından yönetildiğinden Orca güncellemeleri kendisi yükleyemez. Orca'yı dağıtımınız üzerinden güncelleyin.", + "linuxPackageManualInstall": "Sistemin paket yükleme komutunu çalıştırmadan önce Orca'dan çıkın.", + "linuxPackageMetadataUnusable": "İndirilen paketin meta verileri doğrulanamadı. Güncellemeyi resmi sürüm sayfasından indirip yüklemeden önce Orca'dan çıkın.", + "unknownReleaseChannel": "Bilinmeyen yayın kanalı \"{{channel}}\".", + "invalidReleaseTag": "\"{{tag}}\" geçerli bir sürüm etiketi değil.", + "releaseRepoNotFound": "{{repo}} adresinde sürümlerin yayımlandığı bir repo bulunamadı.", + "releaseListFailed": "{{channel}} sürümleri listelenemedi (HTTP {{status}}).", + "releaseListUnreadable": "{{channel}} sürüm listesi okunamadı.", + "rateLimitedSoon": "GitHub istek sınırına ulaşıldı. Birkaç dakika sonra tekrar deneyin.", + "rateLimitedSoonSignedOut": "GitHub istek sınırına ulaşıldı. Birkaç dakika sonra tekrar deneyin veya Orca'nın hesabınızdaki daha yüksek sınırı kullanabilmesi için `gh auth login` komutunu çalıştırın.", + "rateLimitedInMinute": "GitHub istek sınırına ulaşıldı. Yaklaşık bir dakika sonra tekrar deneyin.", + "rateLimitedInMinuteSignedOut": "GitHub istek sınırına ulaşıldı. Yaklaşık bir dakika sonra tekrar deneyin veya Orca'nın hesabınızdaki daha yüksek sınırı kullanabilmesi için `gh auth login` komutunu çalıştırın.", + "rateLimitedInMinutes_one": "GitHub istek sınırına ulaşıldı. Yaklaşık {{count}} dakika sonra tekrar deneyin.", + "rateLimitedInMinutes_other": "GitHub istek sınırına ulaşıldı. Yaklaşık {{count}} dakika sonra tekrar deneyin.", + "rateLimitedInMinutesSignedOut_one": "GitHub istek sınırına ulaşıldı. Yaklaşık {{count}} dakika sonra tekrar deneyin veya Orca'nın hesabınızdaki daha yüksek sınırı kullanabilmesi için `gh auth login` komutunu çalıştırın.", + "rateLimitedInMinutesSignedOut_other": "GitHub istek sınırına ulaşıldı. Yaklaşık {{count}} dakika sonra tekrar deneyin veya Orca'nın hesabınızdaki daha yüksek sınırı kullanabilmesi için `gh auth login` komutunu çalıştırın." + } }, "jira": { "connect": { diff --git a/src/renderer/src/i18n/locales/zh-Hant.json b/src/renderer/src/i18n/locales/zh-Hant.json index 766cfd4adc1..fa5baab5aa8 100644 --- a/src/renderer/src/i18n/locales/zh-Hant.json +++ b/src/renderer/src/i18n/locales/zh-Hant.json @@ -20845,7 +20845,35 @@ "preparingAriaLabel": "正在準備更新。點選展開。" }, "wslMoveInstallHeld": "專案正在移到其 WSL 主機。移動完成後再安裝更新。", - "wslMoveUndoInstallHeld": "專案正在從其 WSL 主機移回。完成後再安裝更新。" + "wslMoveUndoInstallHeld": "專案正在從其 WSL 主機移回。完成後再安裝更新。", + "failure": { + "releaseNotReady": "此裝置尚無可用的較新版本。請稍後再檢查。", + "updateServerUnreachable": "無法連線至更新伺服器。請稍候幾分鐘再試。", + "channelUnsupportedPlatform": "{{channel}} 建置版本僅針對 macOS 和 Windows 提供。", + "unsignedDevChannel": "{{channel}} 建置版本未經簽署,而目前已簽署的建置版本只會安裝由 Orca 發行者簽署的更新。請從發行版本頁面下載安裝程式並執行一次,之後更新即可正常運作,包括切回穩定版。", + "linuxPackageMissing": "下載的套件已不在更新快取中。請重新下載更新,或從官方發行版本頁面取得。", + "linuxPackageNotRegular": "更新快取中下載的套件已不再是有效檔案。請重新下載更新,或從官方發行版本頁面取得。", + "linuxPackageHashMismatch": "下載的套件已不再符合驗證過的發行版本,因此 Orca 不會將其交給套件管理員。請重新下載更新,或從官方發行版本頁面取得。", + "linuxPackageReadFailed": "Orca 無法讀取下載的套件。請重新下載更新,或從官方發行版本頁面取得。", + "linuxPackageNoSudo": "系統目錄中找不到 sudo 指令,因此 Orca 無法產生安全的安裝指令。請顯示套件,並使用套件管理員安裝。", + "linuxPackageNoPackageManager": "系統目錄中找不到支援的套件管理員,因此 Orca 無法產生安全的安裝指令。請顯示套件,並使用套件管理員安裝。", + "linuxPackageInvalidPath": "下載的套件不在可用的路徑中,因此 Orca 無法產生安全的安裝指令。請顯示套件,並使用套件管理員安裝。", + "linuxPackageMarkerUnusable": "Orca 無法驗證已安裝的 Linux 套件格式,因此不會自動安裝此更新。請從官方發行版本頁面下載更新並手動安裝。", + "linuxPackageExternallyManaged": "此 Orca 由系統套件管理員管理,因此 Orca 無法自行安裝更新。請透過您使用的發行版更新 Orca。", + "linuxPackageManualInstall": "執行系統套件安裝指令前,請先結束 Orca。", + "linuxPackageMetadataUnusable": "無法驗證下載套件的後設資料。請先結束 Orca,再從官方發行版本頁面下載並安裝更新。", + "unknownReleaseChannel": "未知的發布頻道「{{channel}}」。", + "invalidReleaseTag": "「{{tag}}」不是有效的發行版本標籤。", + "releaseRepoNotFound": "在 {{repo}} 找不到發行版本存放庫。", + "releaseListFailed": "無法列出 {{channel}} 建置版本(HTTP {{status}})。", + "releaseListUnreadable": "無法讀取 {{channel}} 的發行版本清單。", + "rateLimitedSoon": "已達 GitHub 速率限制。請稍候幾分鐘再試。", + "rateLimitedSoonSignedOut": "已達 GitHub 速率限制。請稍候幾分鐘再試,或執行 `gh auth login`,讓 Orca 使用您帳戶的較高額度。", + "rateLimitedInMinute": "已達 GitHub 速率限制。請約一分鐘後再試。", + "rateLimitedInMinuteSignedOut": "已達 GitHub 速率限制。請約一分鐘後再試,或執行 `gh auth login`,讓 Orca 使用您帳戶的較高額度。", + "rateLimitedInMinutes_other": "已達 GitHub 速率限制。請約 {{count}} 分鐘後再試。", + "rateLimitedInMinutesSignedOut_other": "已達 GitHub 速率限制。請約 {{count}} 分鐘後再試,或執行 `gh auth login`,讓 Orca 使用您帳戶的較高額度。" + } }, "jira": { "connect": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index ce4b14d081f..98be6bf4550 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -21234,7 +21234,35 @@ "browserSummary": "后台 Orca 服务器的浏览器仍在使用此应用程序。" }, "wslMoveInstallHeld": "项目正在移到其 WSL 主机。移动完成后再安装更新。", - "wslMoveUndoInstallHeld": "项目正在从其 WSL 主机移回。完成后再安装更新。" + "wslMoveUndoInstallHeld": "项目正在从其 WSL 主机移回。完成后再安装更新。", + "failure": { + "releaseNotReady": "此设备暂时还无法获取更新的版本。请稍后再检查。", + "updateServerUnreachable": "无法连接更新服务器。请几分钟后重试。", + "channelUnsupportedPlatform": "{{channel}} 构建仅针对 macOS 和 Windows 提供。", + "unsignedDevChannel": "{{channel}} 构建未签名,而当前已签名的构建仅安装由 Orca 发布者签名的更新。请从版本发布页面下载安装程序并运行一次,之后更新即可正常进行,包括切回稳定版。", + "linuxPackageMissing": "下载的安装包已不在更新缓存中。请重新下载更新,或从官方版本发布页面获取。", + "linuxPackageNotRegular": "更新缓存中下载的安装包已不再是有效文件。请重新下载更新,或从官方版本发布页面获取。", + "linuxPackageHashMismatch": "下载的安装包与已验证的版本不再匹配,因此 Orca 不会将其交给包管理器。请重新下载更新,或从官方版本发布页面获取。", + "linuxPackageReadFailed": "Orca 无法读取下载的安装包。请重新下载更新,或从官方版本发布页面获取。", + "linuxPackageNoSudo": "系统目录中未找到 sudo 命令,因此 Orca 无法生成安全的安装命令。请显示安装包并使用包管理器安装。", + "linuxPackageNoPackageManager": "系统目录中未找到受支持的包管理器,因此 Orca 无法生成安全的安装命令。请显示安装包并使用包管理器安装。", + "linuxPackageInvalidPath": "下载的安装包不在可用路径中,因此 Orca 无法生成安全的安装命令。请显示安装包并使用包管理器安装。", + "linuxPackageMarkerUnusable": "Orca 无法验证已安装的 Linux 软件包格式,因此不会自动安装此更新。请从官方版本发布页面下载更新并手动安装。", + "linuxPackageExternallyManaged": "此 Orca 由系统包管理器管理,因此 Orca 无法自行安装更新。请通过所用的发行版更新 Orca。", + "linuxPackageManualInstall": "运行系统软件包安装命令前,请先退出 Orca。", + "linuxPackageMetadataUnusable": "无法验证下载的安装包元数据。请先退出 Orca,再从官方版本发布页面下载并安装更新。", + "unknownReleaseChannel": "未知的发布渠道“{{channel}}”。", + "invalidReleaseTag": "“{{tag}}”不是有效的发布标签。", + "releaseRepoNotFound": "在 {{repo}} 未找到发布版本的仓库。", + "releaseListFailed": "无法列出 {{channel}} 构建(HTTP {{status}})。", + "releaseListUnreadable": "无法读取 {{channel}} 的版本发布列表。", + "rateLimitedSoon": "已达到 GitHub 速率限制。请几分钟后重试。", + "rateLimitedSoonSignedOut": "已达到 GitHub 速率限制。请几分钟后重试,或运行 `gh auth login`,以便 Orca 使用你账户的更高限额。", + "rateLimitedInMinute": "已达到 GitHub 速率限制。请约一分钟后重试。", + "rateLimitedInMinuteSignedOut": "已达到 GitHub 速率限制。请约一分钟后重试,或运行 `gh auth login`,以便 Orca 使用你账户的更高限额。", + "rateLimitedInMinutes_other": "已达到 GitHub 速率限制。请约 {{count}} 分钟后重试。", + "rateLimitedInMinutesSignedOut_other": "已达到 GitHub 速率限制。请约 {{count}} 分钟后重试,或运行 `gh auth login`,以便 Orca 使用你账户的更高限额。" + } }, "onboarding": { "language": { diff --git a/src/renderer/src/lib/updater-message-copy.test.ts b/src/renderer/src/lib/updater-message-copy.test.ts new file mode 100644 index 00000000000..c1a4d5b7515 --- /dev/null +++ b/src/renderer/src/lib/updater-message-copy.test.ts @@ -0,0 +1,39 @@ +import { afterEach, describe, expect, it } from 'vitest' +import { i18n } from '@/i18n/i18n' +import es from '@/i18n/locales/es.json' +import { updaterCodedMessage } from '../../../shared/updater-failure-copy' +import { updaterMessageText } from './updater-message-copy' + +const originalLanguage = i18n.language + +afterEach(async () => { + await i18n.changeLanguage(originalLanguage) +}) + +describe('updater message copy', () => { + it('translates coded updater messages and keeps channel names verbatim', async () => { + i18n.addResourceBundle('es', 'translation', es) + await i18n.changeLanguage('es') + expect(updaterMessageText(updaterCodedMessage('updateServerUnreachable'))).toBe( + es.updates.failure.updateServerUnreachable + ) + expect( + updaterMessageText(updaterCodedMessage('unsignedDevChannel', { channel: 'Hourly' })) + ).toBe(es.updates.failure.unsignedDevChannel.replace('{{channel}}', 'Hourly')) + }) + + it('keeps the English message for older hosts, unknown codes and missing params', async () => { + i18n.addResourceBundle('es', 'translation', es) + await i18n.changeLanguage('es') + expect(updaterMessageText({ message: 'Older host text' })).toBe('Older host text') + expect(updaterMessageText({ message: 'Newer host text', messageCode: 'futureCode' })).toBe( + 'Newer host text' + ) + expect( + updaterMessageText({ + message: 'Hourly builds are unsigned…', + messageCode: 'unsignedDevChannel' + }) + ).toBe('Hourly builds are unsigned…') + }) +}) diff --git a/src/renderer/src/lib/updater-message-copy.ts b/src/renderer/src/lib/updater-message-copy.ts new file mode 100644 index 00000000000..76a90f2745f --- /dev/null +++ b/src/renderer/src/lib/updater-message-copy.ts @@ -0,0 +1,158 @@ +import { translate } from '@/i18n/i18n' +import type { LinuxPackageCommandUnavailableReason } from '../../../shared/update-status-types' +import { + UPDATER_FAILURE_COPY as EN, + type UpdaterFailureCode +} from '../../../shared/updater-failure-copy' + +type Param = (name: string) => string | null +type CountParam = (name: string) => number | null + +// null = required params missing; caller shows the host's English message. +const COPY: Record string | null> = { + releaseNotReady: () => translate('updates.failure.releaseNotReady', EN.releaseNotReady), + updateServerUnreachable: () => + translate('updates.failure.updateServerUnreachable', EN.updateServerUnreachable), + channelUnsupportedPlatform: (param) => { + const channel = param('channel') + return channel === null + ? null + : translate('updates.failure.channelUnsupportedPlatform', EN.channelUnsupportedPlatform, { + channel + }) + }, + unsignedDevChannel: (param) => { + const channel = param('channel') + return channel === null + ? null + : translate('updates.failure.unsignedDevChannel', EN.unsignedDevChannel, { channel }) + }, + linuxPackageMissing: () => + translate('updates.failure.linuxPackageMissing', EN.linuxPackageMissing), + linuxPackageNotRegular: () => + translate('updates.failure.linuxPackageNotRegular', EN.linuxPackageNotRegular), + linuxPackageHashMismatch: () => + translate('updates.failure.linuxPackageHashMismatch', EN.linuxPackageHashMismatch), + linuxPackageReadFailed: () => + translate('updates.failure.linuxPackageReadFailed', EN.linuxPackageReadFailed), + linuxPackageNoSudo: () => translate('updates.failure.linuxPackageNoSudo', EN.linuxPackageNoSudo), + linuxPackageNoPackageManager: () => + translate('updates.failure.linuxPackageNoPackageManager', EN.linuxPackageNoPackageManager), + linuxPackageInvalidPath: () => + translate('updates.failure.linuxPackageInvalidPath', EN.linuxPackageInvalidPath), + linuxPackageMarkerUnusable: () => + translate('updates.failure.linuxPackageMarkerUnusable', EN.linuxPackageMarkerUnusable), + linuxPackageExternallyManaged: () => + translate('updates.failure.linuxPackageExternallyManaged', EN.linuxPackageExternallyManaged), + linuxPackageManualInstall: () => + translate('updates.failure.linuxPackageManualInstall', EN.linuxPackageManualInstall), + linuxPackageMetadataUnusable: () => + translate('updates.failure.linuxPackageMetadataUnusable', EN.linuxPackageMetadataUnusable), + unknownReleaseChannel: (param) => { + const channel = param('channel') + return channel === null + ? null + : translate('updates.failure.unknownReleaseChannel', EN.unknownReleaseChannel, { channel }) + }, + invalidReleaseTag: (param) => { + const tag = param('tag') + return tag === null + ? null + : translate('updates.failure.invalidReleaseTag', EN.invalidReleaseTag, { tag }) + }, + releaseRepoNotFound: (param) => { + const repo = param('repo') + return repo === null + ? null + : translate('updates.failure.releaseRepoNotFound', EN.releaseRepoNotFound, { repo }) + }, + releaseListFailed: (param, count) => { + const channel = param('channel') + const status = count('status') + return channel === null || status === null + ? null + : translate('updates.failure.releaseListFailed', EN.releaseListFailed, { channel, status }) + }, + releaseListUnreadable: (param) => { + const channel = param('channel') + return channel === null + ? null + : translate('updates.failure.releaseListUnreadable', EN.releaseListUnreadable, { channel }) + }, + rateLimitedSoon: () => translate('updates.failure.rateLimitedSoon', EN.rateLimitedSoon), + rateLimitedSoonSignedOut: () => + translate('updates.failure.rateLimitedSoonSignedOut', EN.rateLimitedSoonSignedOut), + rateLimitedInMinute: () => + translate('updates.failure.rateLimitedInMinute', EN.rateLimitedInMinute), + rateLimitedInMinuteSignedOut: () => + translate('updates.failure.rateLimitedInMinuteSignedOut', EN.rateLimitedInMinuteSignedOut), + rateLimitedInMinutes: (_param, count) => { + const minutes = count('count') + return minutes === null + ? null + : translate('updates.failure.rateLimitedInMinutes', EN.rateLimitedInMinutes, { + count: minutes, + defaultValue_one: 'GitHub rate limit reached. Try again in about {{count}} minute.' + }) + }, + rateLimitedInMinutesSignedOut: (_param, count) => { + const minutes = count('count') + return minutes === null + ? null + : translate( + 'updates.failure.rateLimitedInMinutesSignedOut', + EN.rateLimitedInMinutesSignedOut, + { + count: minutes, + defaultValue_one: + "GitHub rate limit reached. Try again in about {{count}} minute, or run `gh auth login` so Orca can use your account's higher limit." + } + ) + } +} + +const COMMAND_UNAVAILABLE_CODES: Record = + { + 'no-sudo': 'linuxPackageNoSudo', + 'no-package-manager': 'linuxPackageNoPackageManager' + } + +function isUpdaterFailureCode(code: unknown): code is UpdaterFailureCode { + return typeof code === 'string' && Object.hasOwn(COPY, code) +} + +function codedText(code: unknown, params: unknown): string | null { + if (!isUpdaterFailureCode(code)) { + return null + } + const values: Record = + typeof params === 'object' && params !== null ? Object.fromEntries(Object.entries(params)) : {} + return COPY[code]( + (name) => (typeof values[name] === 'string' ? values[name] : null), + (name) => (typeof values[name] === 'number' ? values[name] : null) + ) +} + +/** Updater message text: known codes translate; unknown codes and older hosts keep the English message. */ +export function updaterMessageText(source: { + message: string + messageCode?: unknown + messageParams?: unknown +}): string { + return codedText(source.messageCode, source.messageParams) ?? source.message +} + +/** The install-instructions result already names its reason; unknown reasons keep main's English. */ +export function linuxPackageCommandUnavailableText(instructions: { + reason: LinuxPackageCommandUnavailableReason + message: string +}): string { + return ( + codedText( + Object.hasOwn(COMMAND_UNAVAILABLE_CODES, instructions.reason) + ? COMMAND_UNAVAILABLE_CODES[instructions.reason] + : undefined, + undefined + ) ?? instructions.message + ) +} diff --git a/src/renderer/src/runtime/remote-server-updater-polling.ts b/src/renderer/src/runtime/remote-server-updater-polling.ts index ba200c6dce4..1c108eda51a 100644 --- a/src/renderer/src/runtime/remote-server-updater-polling.ts +++ b/src/renderer/src/runtime/remote-server-updater-polling.ts @@ -1,4 +1,5 @@ import type { RemoteServerUpdaterSnapshot } from '../../../shared/remote-server-update' +import { updaterMessageText } from '@/lib/updater-message-copy' type PollingTransport = { getUpdaterStatus: (environmentId: string) => Promise @@ -23,7 +24,7 @@ export async function pollRemoteServerUpdater( while (now() < deadline) { const snapshot = await transport.getUpdaterStatus(environmentId) if (snapshot.status.state === 'error') { - throw new Error(snapshot.status.message) + throw new Error(updaterMessageText(snapshot.status)) } onSnapshot(snapshot) if (accept(snapshot)) { diff --git a/src/shared/update-status-types.ts b/src/shared/update-status-types.ts index ffa72c73672..b5c4fb9b773 100644 --- a/src/shared/update-status-types.ts +++ b/src/shared/update-status-types.ts @@ -90,6 +90,9 @@ export type UpdateStatus = ( | { state: 'error' message: string + /** Translation code for `message`; older hosts omit it and newer ones may send unknown codes. */ + messageCode?: string + messageParams?: Record /** Known download/install target; absent for check-time failures and older hosts. */ version?: string /** Omitted by older hosts and for failures whose retryability is unknown. */ @@ -106,4 +109,10 @@ export type UpdateStatus = ( export type ReleaseBuildListResult = | { ok: true; channel: ReleaseChannel; builds: ReleaseBuild[] } - | { ok: false; channel: ReleaseChannel; message: string } + | { + ok: false + channel: ReleaseChannel + message: string + messageCode?: string + messageParams?: Record + } diff --git a/src/shared/updater-failure-copy.ts b/src/shared/updater-failure-copy.ts new file mode 100644 index 00000000000..e0e28fcbfce --- /dev/null +++ b/src/shared/updater-failure-copy.ts @@ -0,0 +1,76 @@ +// One English table for updater failures: main builds the English `message`, the renderer translates by code. +export const UPDATER_FAILURE_COPY = { + releaseNotReady: "A newer release isn't available for this device yet. Check again later.", + updateServerUnreachable: "Couldn't reach the update server. Try again in a few minutes.", + channelUnsupportedPlatform: '{{channel}} builds are produced only for macOS and Windows.', + unsignedDevChannel: + "{{channel}} builds are unsigned, and this signed build only installs updates signed by Orca's publisher. Download the installer from the release page and run it once — updates work normally from there, including back to Stable.", + linuxPackageMissing: + 'The downloaded package is no longer in the update cache. Download the update again, or get it from the official release page.', + linuxPackageNotRegular: + 'The downloaded package is no longer a valid file in the update cache. Download the update again, or get it from the official release page.', + linuxPackageHashMismatch: + 'The downloaded package no longer matches the verified release, so Orca will not hand it to a package manager. Download the update again, or get it from the official release page.', + linuxPackageReadFailed: + 'Orca could not read the downloaded package. Download the update again, or get it from the official release page.', + linuxPackageNoSudo: + 'No sudo command was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.', + linuxPackageNoPackageManager: + 'No supported package manager was found in the system directories, so Orca cannot build a safe install command. Show the package and install it with your package manager.', + linuxPackageInvalidPath: + 'The downloaded package is not at a usable path, so Orca cannot build a safe install command. Show the package and install it with your package manager.', + linuxPackageMarkerUnusable: + 'Orca could not verify the installed Linux package format, so it will not install this update automatically. Download the update from the official release page and install it manually.', + linuxPackageExternallyManaged: + 'This copy of Orca is managed by your system package manager, so Orca cannot install updates itself. Update Orca through your distribution instead.', + linuxPackageManualInstall: 'Quit Orca before running the system package install command.', + linuxPackageMetadataUnusable: + 'The downloaded package metadata could not be verified. Quit Orca before downloading and installing the update from the official release page.', + unknownReleaseChannel: 'Unknown release channel "{{channel}}".', + invalidReleaseTag: '"{{tag}}" is not a valid release tag.', + releaseRepoNotFound: 'No releases repository found at {{repo}}.', + releaseListFailed: 'Could not list {{channel}} builds (HTTP {{status}}).', + releaseListUnreadable: 'Could not read the {{channel}} release list.', + rateLimitedSoon: 'GitHub rate limit reached. Try again in a few minutes.', + rateLimitedSoonSignedOut: + "GitHub rate limit reached. Try again in a few minutes, or run `gh auth login` so Orca can use your account's higher limit.", + rateLimitedInMinute: 'GitHub rate limit reached. Try again in about a minute.', + rateLimitedInMinuteSignedOut: + "GitHub rate limit reached. Try again in about a minute, or run `gh auth login` so Orca can use your account's higher limit.", + // Only for two or more minutes; one minute uses rateLimitedInMinute. + rateLimitedInMinutes: 'GitHub rate limit reached. Try again in about {{count}} minutes.', + rateLimitedInMinutesSignedOut: + "GitHub rate limit reached. Try again in about {{count}} minutes, or run `gh auth login` so Orca can use your account's higher limit." +} as const + +export type UpdaterFailureCode = keyof typeof UPDATER_FAILURE_COPY + +export type UpdaterFailureParams = Record + +/** Wire shape: `message` stays English for older readers; code and params let the renderer translate it. */ +export type UpdaterCodedMessage = { + message: string + messageCode?: string + messageParams?: UpdaterFailureParams +} + +// One pass, so a value containing "{{status}}" stays verbatim. +export function updaterFailureText( + code: UpdaterFailureCode, + params: UpdaterFailureParams = {} +): string { + return UPDATER_FAILURE_COPY[code].replace(/\{\{(\w+)\}\}/g, (slot, name: string) => + Object.hasOwn(params, name) ? String(params[name]) : slot + ) +} + +export function updaterCodedMessage( + code: UpdaterFailureCode, + params?: UpdaterFailureParams +): UpdaterCodedMessage { + return { + message: updaterFailureText(code, params), + messageCode: code, + ...(params ? { messageParams: params } : {}) + } +}