diff --git a/src/main/github/project-view.test.ts b/src/main/github/project-view.test.ts index a9343b2f474..2a44c12657d 100644 --- a/src/main/github/project-view.test.ts +++ b/src/main/github/project-view.test.ts @@ -238,7 +238,8 @@ describe('resolveProjectRef', () => { ok: false, error: { type: 'validation_error', - message: GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR + message: GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR, + reason: 'input_too_large' } }) await expect(resolveProjectRef({ input })).resolves.not.toMatchObject({ diff --git a/src/main/github/project-view/internals.ts b/src/main/github/project-view/internals.ts index fb7d76c0b13..7f6b506822f 100644 --- a/src/main/github/project-view/internals.ts +++ b/src/main/github/project-view/internals.ts @@ -48,7 +48,8 @@ export async function projectHostAuthenticationError( // Verify pasted hosts before sending ambient Enterprise credentials. return { type: 'auth_required', - message: `GitHub CLI is not authenticated to ${selectedHost}. Run gh auth login --hostname ${selectedHost}.` + message: `GitHub CLI is not authenticated to ${selectedHost}. Run gh auth login --hostname ${selectedHost}.`, + host: selectedHost } } @@ -79,7 +80,10 @@ export function assertSlug( ok: false, error: { type: 'validation_error', - message: `Invalid ${field}: "${String(value)}" is not a valid GitHub slug.` + message: `Invalid ${field}: "${String(value)}" is not a valid GitHub slug.`, + reason: 'invalid_slug', + detail: field, + value: String(value) } } } @@ -95,7 +99,9 @@ export function assertPositiveInt( ok: false, error: { type: 'validation_error', - message: `Invalid ${field}: must be a positive integer.` + message: `Invalid ${field}: must be a positive integer.`, + reason: 'invalid_positive_integer', + detail: field } } } @@ -224,7 +230,12 @@ export async function runRest( } catch { return { ok: false, - error: { type: 'unknown', message: `Unexpected REST response: ${stderr.trim()}` } + error: { + type: 'unknown', + message: `Unexpected REST response: ${stderr.trim()}`, + reason: 'unexpected_rest_response', + detail: stderr.trim() + } } } } catch (err) { diff --git a/src/main/github/project-view/issue-comment-mutations.ts b/src/main/github/project-view/issue-comment-mutations.ts index fcf7e056fb6..fa16455e66d 100644 --- a/src/main/github/project-view/issue-comment-mutations.ts +++ b/src/main/github/project-view/issue-comment-mutations.ts @@ -42,7 +42,14 @@ export async function addIssueCommentBySlug( return { ok: false, error: number.error } } if (typeof args.body !== 'string' || !args.body.trim()) { - return { ok: false, error: { type: 'validation_error', message: 'Comment body required.' } } + return { + ok: false, + error: { + type: 'validation_error', + message: 'Comment body required.', + reason: 'comment_body_required' + } + } } const result = await runRest( [ @@ -73,7 +80,14 @@ export async function updateIssueCommentBySlug( return { ok: false, error: number.error } } if (typeof args.body !== 'string' || !args.body.trim()) { - return { ok: false, error: { type: 'validation_error', message: 'Comment body required.' } } + return { + ok: false, + error: { + type: 'validation_error', + message: 'Comment body required.', + reason: 'comment_body_required' + } + } } const result = await runRest( [ diff --git a/src/main/github/project-view/issue-type-mutation.ts b/src/main/github/project-view/issue-type-mutation.ts index d36cf26955a..30f9fb6c59b 100644 --- a/src/main/github/project-view/issue-type-mutation.ts +++ b/src/main/github/project-view/issue-type-mutation.ts @@ -33,7 +33,10 @@ export async function updateIssueTypeBySlug( } const issueId = lookup.data.repository?.issue?.id if (!issueId) { - return { ok: false, error: { type: 'not_found', message: 'Issue not found.' } } + return { + ok: false, + error: { type: 'not_found', message: 'Issue not found.', reason: 'issue_not_found' } + } } const query = args.issueTypeId ? `mutation($issueId:ID!, $issueTypeId:ID!) { diff --git a/src/main/github/project-view/mutations.ts b/src/main/github/project-view/mutations.ts index 65d7eb32ec9..ea890d2b155 100644 --- a/src/main/github/project-view/mutations.ts +++ b/src/main/github/project-view/mutations.ts @@ -27,7 +27,10 @@ export async function updateIssueBySlug( return { ok: false, error: number.error } } if (!args.updates || typeof args.updates !== 'object') { - return { ok: false, error: { type: 'validation_error', message: 'Updates required.' } } + return { + ok: false, + error: { type: 'validation_error', message: 'Updates required.', reason: 'updates_required' } + } } const duplicateError = validateDuplicateUpdate(args) if (duplicateError) { @@ -75,7 +78,8 @@ function validateDuplicateUpdate(args: UpdateIssueBySlugArgs): GitHubProjectMuta ok: false, error: { type: 'validation_error', - message: 'Duplicate target is only valid when closing as duplicate.' + message: 'Duplicate target is only valid when closing as duplicate.', + reason: 'duplicate_target_not_allowed' } } } @@ -84,7 +88,8 @@ function validateDuplicateUpdate(args: UpdateIssueBySlugArgs): GitHubProjectMuta ok: false, error: { type: 'validation_error', - message: 'Duplicate target issue number is required.' + message: 'Duplicate target issue number is required.', + reason: 'duplicate_target_required' } } } diff --git a/src/main/github/project-view/project-error-classification.ts b/src/main/github/project-view/project-error-classification.ts index 9e2f4488bdc..4b0df5977e3 100644 --- a/src/main/github/project-view/project-error-classification.ts +++ b/src/main/github/project-view/project-error-classification.ts @@ -83,7 +83,8 @@ export function classifyProjectError( ) { return { type: 'auth_required', - message: `Sign in to GitHub to load project tasks. Run \`gh auth login --hostname ${selectedHost}\`.` + message: `Sign in to GitHub to load project tasks. Run \`gh auth login --hostname ${selectedHost}\`.`, + host: selectedHost } } if ( @@ -93,7 +94,8 @@ export function classifyProjectError( ) { return { type: 'scope_missing', - message: `GitHub project access needs additional scopes. Run \`gh auth refresh --hostname ${selectedHost} -s project -s read:org -s repo\`.` + message: `GitHub project access needs additional scopes. Run \`gh auth refresh --hostname ${selectedHost} -s project -s read:org -s repo\`.`, + host: selectedHost } } if (s.includes('rate limit') || s.includes('api rate limit exceeded')) { @@ -119,13 +121,19 @@ export function classifyProjectError( return { type: 'not_found', message: 'Project or view not found.', + reason: 'project_or_view_not_found', details: firstNotFound ? { path: firstNotFound.path, code: firstNotFound.extensions?.code } : undefined } } if (s.includes('http 422') || s.includes('validation failed')) { - return { type: 'validation_error', message: `Invalid request — ${stderr.trim()}` } + return { + type: 'validation_error', + message: `Invalid request — ${stderr.trim()}`, + reason: 'request_invalid', + detail: stderr.trim() + } } if (errors.length > 0) { const first = errors[0] @@ -144,7 +152,9 @@ export function classifyProjectError( const safe = firstLine.length > 200 ? `${firstLine.slice(0, 200)}…` : firstLine return { type: 'unknown', - message: safe ? `GitHub request failed: ${safe}` : 'GitHub request failed.' + message: safe ? `GitHub request failed: ${safe}` : 'GitHub request failed.', + reason: 'request_failed', + ...(safe ? { detail: safe } : {}) } } @@ -152,7 +162,12 @@ export function driftError( reason: string, details?: { path?: (string | number)[]; code?: string } ): GitHubProjectViewError { - return { type: 'schema_drift', message: `Could not read this project view: ${reason}.`, details } + return { + type: 'schema_drift', + message: `Could not read this project view: ${reason}.`, + detail: reason, + details + } } // Shape pre-spawn breaker failures like classified rate limits; the guard already handles missing snapshots. @@ -165,6 +180,7 @@ export function rateLimitedError(blocked: { const mins = Math.ceil(resetIn / 60) return { type: 'rate_limited', - message: `GitHub rate limit nearly exhausted (${blocked.remaining}/${blocked.limit} left). Resets in ~${mins}m.` + message: `GitHub rate limit nearly exhausted (${blocked.remaining}/${blocked.limit} left). Resets in ~${mins}m.`, + rateLimit: { remaining: blocked.remaining, limit: blocked.limit, resetMinutes: mins } } } diff --git a/src/main/github/project-view/project-field-mutations.ts b/src/main/github/project-view/project-field-mutations.ts index 5fc5edb1985..69130f7197b 100644 --- a/src/main/github/project-view/project-field-mutations.ts +++ b/src/main/github/project-view/project-field-mutations.ts @@ -51,7 +51,10 @@ export async function updateProjectItemFieldValue( args: UpdateProjectItemFieldArgs ): Promise { if (!args.projectId || !args.itemId || !args.fieldId) { - return { ok: false, error: { type: 'validation_error', message: 'Missing ids.' } } + return { + ok: false, + error: { type: 'validation_error', message: 'Missing ids.', reason: 'missing_ids' } + } } let valueFragment: string let valueVariable: { type: string; val: string | number } @@ -88,7 +91,10 @@ export async function clearProjectItemFieldValue( args: ClearProjectItemFieldArgs ): Promise { if (!args.projectId || !args.itemId || !args.fieldId) { - return { ok: false, error: { type: 'validation_error', message: 'Missing ids.' } } + return { + ok: false, + error: { type: 'validation_error', message: 'Missing ids.', reason: 'missing_ids' } + } } const result = await runGraphql( `mutation($projectId:ID!, $itemId:ID!, $fieldId:ID!) { diff --git a/src/main/github/project-view/project-view-config.ts b/src/main/github/project-view/project-view-config.ts index 5f6d58d514f..da828e1c278 100644 --- a/src/main/github/project-view/project-view-config.ts +++ b/src/main/github/project-view/project-view-config.ts @@ -137,7 +137,10 @@ export async function fetchProjectViewsPage(args: { const top = res.data[root] const project = top?.projectV2 ?? null if (!project || typeof project.id !== 'string') { - return { ok: false, error: { type: 'not_found', message: 'Project not found.' } } + return { + ok: false, + error: { type: 'not_found', message: 'Project not found.', reason: 'project_not_found' } + } } const pageInfo = project.views?.pageInfo const views = (project.views?.nodes ?? []).filter((v): v is RawProjectView => v !== null) diff --git a/src/main/github/project-view/project-view-item-page.ts b/src/main/github/project-view/project-view-item-page.ts index cb726a11243..61c0c6911b7 100644 --- a/src/main/github/project-view/project-view-item-page.ts +++ b/src/main/github/project-view/project-view-item-page.ts @@ -159,7 +159,11 @@ export async function fetchItemsPageWithRaw(args: { if (!page) { return { ok: false, - error: { type: 'not_found', message: 'Project or view not found.' }, + error: { + type: 'not_found', + message: 'Project or view not found.', + reason: 'project_or_view_not_found' + }, rawErrors: [], stderr } diff --git a/src/main/github/project-view/project-view-listing.ts b/src/main/github/project-view/project-view-listing.ts index 14fdca24d53..aeacb4d5e69 100644 --- a/src/main/github/project-view/project-view-listing.ts +++ b/src/main/github/project-view/project-view-listing.ts @@ -19,7 +19,14 @@ export async function listProjectViews( return { ok: false, error: numCheck.error } } if (args.ownerType !== 'organization' && args.ownerType !== 'user') { - return { ok: false, error: { type: 'validation_error', message: 'Invalid ownerType.' } } + return { + ok: false, + error: { + type: 'validation_error', + message: 'Invalid ownerType.', + reason: 'invalid_owner_type' + } + } } const summaries: GitHubProjectViewSummary[] = [] let cursor: string | null = null diff --git a/src/main/github/project-view/project-view-reference.ts b/src/main/github/project-view/project-view-reference.ts index 942d8eeb5f2..02815daf698 100644 --- a/src/main/github/project-view/project-view-reference.ts +++ b/src/main/github/project-view/project-view-reference.ts @@ -111,12 +111,18 @@ async function resolveOwnerType( } const top = res.data[root] if (!top) { - return { ok: false, error: { type: 'not_found', message: 'Owner not found.' } } + return { + ok: false, + error: { type: 'not_found', message: 'Owner not found.', reason: 'owner_not_found' } + } } if (num) { const p = top.projectV2 if (!p || typeof p.id !== 'string') { - return { ok: false, error: { type: 'not_found', message: 'Project not found.' } } + return { + ok: false, + error: { type: 'not_found', message: 'Project not found.', reason: 'project_not_found' } + } } return { ok: true, title: p.title ?? '' } } @@ -153,7 +159,11 @@ async function resolveOwnerType( rememberOwnerType(owner, null, host) return { ok: false, - error: lastError ?? { type: 'not_found', message: 'Owner not found.' } + error: lastError ?? { + type: 'not_found', + message: 'Owner not found.', + reason: 'owner_not_found' + } } } @@ -164,13 +174,17 @@ export async function resolveProjectRef( if (!input) { return { ok: false, - error: { type: 'validation_error', message: 'Input required.' } + error: { type: 'validation_error', message: 'Input required.', reason: 'input_required' } } } if (isGitHubProjectRefInputTooLarge(input)) { return { ok: false, - error: { type: 'validation_error', message: GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR } + error: { + type: 'validation_error', + message: GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR, + reason: 'input_too_large' + } } } const parsed = parseProjectPaste(input, args.host) @@ -179,7 +193,8 @@ export async function resolveProjectRef( ok: false, error: { type: 'validation_error', - message: 'Could not parse input. Expected a GitHub project URL or `owner/number`.' + message: 'Could not parse input. Expected a GitHub project URL or `owner/number`.', + reason: 'input_unparseable' } } } @@ -206,7 +221,10 @@ export async function resolveProjectRef( } const p = res.data[root]?.projectV2 if (!p || typeof p.id !== 'string') { - return { ok: false, error: { type: 'not_found', message: 'Project not found.' } } + return { + ok: false, + error: { type: 'not_found', message: 'Project not found.', reason: 'project_not_found' } + } } return { ok: true, diff --git a/src/main/github/project-view/project-view-table.ts b/src/main/github/project-view/project-view-table.ts index 3c050e3bf53..8c69f7a2d30 100644 --- a/src/main/github/project-view/project-view-table.ts +++ b/src/main/github/project-view/project-view-table.ts @@ -28,7 +28,11 @@ export async function getProjectViewTable( if (args.ownerType !== 'organization' && args.ownerType !== 'user') { return { ok: false, - error: { type: 'validation_error', message: 'Invalid ownerType.' } + error: { + type: 'validation_error', + message: 'Invalid ownerType.', + reason: 'invalid_owner_type' + } } } @@ -79,7 +83,10 @@ export async function getProjectViewTable( } } if (!project) { - return { ok: false, error: { type: 'not_found', message: 'Project not found.' } } + return { + ok: false, + error: { type: 'not_found', message: 'Project not found.', reason: 'project_not_found' } + } } const noSelector = args.viewId === undefined && args.viewNumber === undefined && args.viewName === undefined @@ -91,7 +98,14 @@ export async function getProjectViewTable( null } if (!selectedRaw) { - return { ok: false, error: { type: 'not_found', message: 'Could not find the selected view.' } } + return { + ok: false, + error: { + type: 'not_found', + message: 'Could not find the selected view.', + reason: 'view_not_found' + } + } } let extraFields: RawProjectV2Field[] = [] diff --git a/src/main/github/project-view/pull-request-mutation.ts b/src/main/github/project-view/pull-request-mutation.ts index 7a4d056dab2..7a49d9cbf0e 100644 --- a/src/main/github/project-view/pull-request-mutation.ts +++ b/src/main/github/project-view/pull-request-mutation.ts @@ -14,7 +14,10 @@ export async function updatePullRequestBySlug( return { ok: false, error: number.error } } if (!args.updates || typeof args.updates !== 'object') { - return { ok: false, error: { type: 'validation_error', message: 'Updates required.' } } + return { + ok: false, + error: { type: 'validation_error', message: 'Updates required.', reason: 'updates_required' } + } } const patchArgs: string[] = [ '-X', diff --git a/src/main/github/project-view/work-item-details.ts b/src/main/github/project-view/work-item-details.ts index ad6d122f707..e09a8a038cb 100644 --- a/src/main/github/project-view/work-item-details.ts +++ b/src/main/github/project-view/work-item-details.ts @@ -45,7 +45,10 @@ export async function getWorkItemDetailsBySlug( return { ok: false, error: number.error } } if (args.type !== 'issue' && args.type !== 'pr') { - return { ok: false, error: { type: 'validation_error', message: 'Invalid type.' } } + return { + ok: false, + error: { type: 'validation_error', message: 'Invalid type.', reason: 'invalid_item_type' } + } } const contentFragment = args.type === 'issue' ? ISSUE_FRAGMENT : PULL_REQUEST_FRAGMENT const result = await runGraphql<{ @@ -66,7 +69,10 @@ export async function getWorkItemDetailsBySlug( const raw = args.type === 'issue' ? result.data.repository?.issue : result.data.repository?.pullRequest if (!raw) { - return { ok: false, error: { type: 'not_found', message: 'Item not found.' } } + return { + ok: false, + error: { type: 'not_found', message: 'Item not found.', reason: 'item_not_found' } + } } return { ok: true, details: mapWorkItemDetails(raw, args) } } diff --git a/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx b/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx index 8da43989bd4..59f868a61f7 100644 --- a/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx +++ b/src/renderer/src/components/github-project/GhAuthErrorHelp.tsx @@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button' import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' import type { GhAuthDiagnostic } from '../../../../shared/github/auth-types' import { translate } from '@/i18n/i18n' +import { projectViewErrorMessage } from './project-view-error-copy' type AuthErrorKind = 'auth_required' | 'scope_missing' @@ -129,8 +130,16 @@ export function buildRemediation( // Missing Enterprise credentials outrank token/scope advice for another host. if (diag.requiredHost && diag.requiredHostAuthenticated === false) { return { - summary: `\`gh\` is not signed in to ${diag.requiredHost}.`, - detail: `GitHub Enterprise hosts need their own \`gh\` login, separate from github.com. Run the login command in a terminal, complete the browser flow on ${diag.requiredHost}, then reload.`, + summary: translate( + 'projectViews.authHelp.hostSignedOut', + '`gh` is not signed in to {{host}}.', + { host: diag.requiredHost } + ), + detail: translate( + 'projectViews.authHelp.hostSignedOutDetail', + 'GitHub Enterprise hosts need their own `gh` login, separate from github.com. Run the login command in a terminal, complete the browser flow on {{host}}, then reload.', + { host: diag.requiredHost } + ), commands: [ { label: translate( @@ -145,9 +154,14 @@ export function buildRemediation( if (!diag.ghAvailable) { return { - summary: 'GitHub CLI (`gh`) is not installed or not on PATH.', - detail: - 'Orca uses `gh` to talk to GitHub Projects. Install it from cli.github.com, then sign in.', + summary: translate( + 'projectViews.authHelp.ghMissing', + 'GitHub CLI (`gh`) is not installed or not on PATH.' + ), + detail: translate( + 'projectViews.authHelp.ghMissingDetail', + 'Orca uses `gh` to talk to GitHub Projects. Install it from cli.github.com, then sign in.' + ), commands: [ { label: translate( @@ -166,13 +180,32 @@ export function buildRemediation( if (active?.envToken) { const varName = active.envToken const fallback = diag.hasKeyringFallback - ? ' Your keyring already has a `gh` login that will take over once the env var is gone.' - : ' After unsetting it, run `gh auth login` to sign in normally, then retry.' + ? translate( + 'projectViews.authHelp.envTokenKeyringFallback', + 'Your keyring already has a `gh` login that will take over once the env var is gone.' + ) + : translate( + 'projectViews.authHelp.envTokenLoginAfterUnset', + 'After unsetting it, run `gh auth login` to sign in normally, then retry.' + ) + const removal = IS_WINDOWS + ? translate( + 'projectViews.authHelp.envTokenRemoveWindows', + 'Find where `{{varName}}` is set (System or User environment variables, or your PowerShell profile), remove it, then restart Orca so the new environment is picked up.', + { varName } + ) + : translate( + 'projectViews.authHelp.envTokenRemovePosix', + "Find where `{{varName}}` is exported (commonly `~/.zshrc`, `~/.zshenv`, `~/.bashrc`, `~/.profile`, or your shell's secrets manager), remove it, then restart Orca so the new environment is picked up.", + { varName } + ) return { - summary: `\`${varName}\` is set in your environment, so \`gh\` is using that token instead of your keyring login. \`gh auth refresh\` cannot modify env-supplied tokens — that's why running it didn't help.`, - detail: IS_WINDOWS - ? `Find where \`${varName}\` is set (System or User environment variables, or your PowerShell profile), remove it, then restart Orca so the new environment is picked up.${fallback}` - : `Find where \`${varName}\` is exported (commonly \`~/.zshrc\`, \`~/.zshenv\`, \`~/.bashrc\`, \`~/.profile\`, or your shell's secrets manager), remove it, then restart Orca so the new environment is picked up.${fallback}`, + summary: translate( + 'projectViews.authHelp.envTokenActive', + "`{{varName}}` is set in your environment, so `gh` is using that token instead of your keyring login. `gh auth refresh` cannot modify env-supplied tokens — that's why running it didn't help.", + { varName } + ), + detail: `${removal} ${fallback}`, commands: [findEnvVarCommand(varName), unsetEnvVarCommand(varName)], docsUrl: 'https://cli.github.com/manual/gh_help_environment' } @@ -182,10 +215,22 @@ export function buildRemediation( if (diag.envTokenInProcess && (!active || diag.missingScopes.length > 0)) { const varName = diag.envTokenInProcess return { - summary: `Orca inherited \`${varName}\` from your shell, and \`gh\` is using that token. \`gh auth refresh\` doesn't apply to env-supplied tokens.`, - detail: `Unset \`${varName}\` in the shell that launches Orca${ - IS_WINDOWS ? ' (or in your user environment variables)' : ' (or in your shell rc file)' - }, then restart Orca.`, + summary: translate( + 'projectViews.authHelp.envTokenInherited', + "Orca inherited `{{varName}}` from your shell, and `gh` is using that token. `gh auth refresh` doesn't apply to env-supplied tokens.", + { varName } + ), + detail: IS_WINDOWS + ? translate( + 'projectViews.authHelp.envTokenInheritedDetailWindows', + 'Unset `{{varName}}` in the shell that launches Orca (or in your user environment variables), then restart Orca.', + { varName } + ) + : translate( + 'projectViews.authHelp.envTokenInheritedDetailPosix', + 'Unset `{{varName}}` in the shell that launches Orca (or in your shell rc file), then restart Orca.', + { varName } + ), commands: [findEnvVarCommand(varName), unsetEnvVarCommand(varName)], docsUrl: 'https://cli.github.com/manual/gh_help_environment' } @@ -193,7 +238,10 @@ export function buildRemediation( if (kind === 'auth_required' || !active) { return { - summary: 'You’re not signed in to GitHub via `gh`.', + summary: translate( + 'projectViews.authHelp.signedOut', + 'You’re not signed in to GitHub via `gh`.' + ), commands: [ { label: translate( @@ -208,13 +256,15 @@ export function buildRemediation( if (diag.missingScopes.length > 0) { return { - summary: `Your \`gh\` token is missing the ${diag.missingScopes - .map((s) => `\`${s}\``) - .join( - ', ' - )} scope${diag.missingScopes.length === 1 ? '' : 's'} needed for GitHub Projects.`, - detail: - 'Run the refresh command in a terminal. It will open a browser to authorize the new scopes, then come back here and reload.', + summary: translate( + 'projectViews.authHelp.missingScopes', + 'Your `gh` token is missing scopes needed for GitHub Projects: {{scopes}}.', + { scopes: diag.missingScopes.map((scope) => `\`${scope}\``).join(', ') } + ), + detail: translate( + 'projectViews.authHelp.missingScopesDetail', + 'Run the refresh command in a terminal. It will open a browser to authorize the new scopes, then come back here and reload.' + ), commands: [ { label: translate( @@ -230,8 +280,10 @@ export function buildRemediation( // Valid scopes can still fail on org visibility or SAML SSO authorization. return { summary: errorMessage, - detail: - 'Your token has the required scopes but GitHub still denied access. If the project is in an org with SAML SSO, you must authorize this token for the org under Settings → Developer settings → Personal access tokens → Configure SSO.', + detail: translate( + 'projectViews.authHelp.accessDeniedDetail', + 'Your token has the required scopes but GitHub still denied access. If the project is in an org with SAML SSO, you must authorize this token for the org under Settings → Developer settings → Personal access tokens → Configure SSO.' + ), commands: [ { label: translate( @@ -273,7 +325,7 @@ export function GhAuthErrorHelp({ } }, [host]) - const remedy = buildRemediation(error.message, error.type, diag, host) + const remedy = buildRemediation(projectViewErrorMessage(error, { host }), error.type, diag, host) const docsUrl = remedy.docsUrl if (variant === 'banner') { diff --git a/src/renderer/src/components/github-project/ProjectPicker.tsx b/src/renderer/src/components/github-project/ProjectPicker.tsx index a01e4a57640..a78fd5ad29b 100644 --- a/src/renderer/src/components/github-project/ProjectPicker.tsx +++ b/src/renderer/src/components/github-project/ProjectPicker.tsx @@ -25,6 +25,7 @@ import { filterGitHubProjectPickerProjects } from './github-project-picker-filte import { ProjectPickerBrowse } from './ProjectPickerBrowse' import { ProjectViewPickStep } from './ProjectPickerPanels' import { parseProjectInput } from './project-picker-input' +import { projectViewErrorMessage } from './project-view-error-copy' import { getProjectPickerBrowseHost, listProjectViewsForRuntime, @@ -160,7 +161,9 @@ export default function ProjectPicker({ } if (!result.ok) { setViewList([]) - toast.error(result.error.message) + toast.error( + projectViewErrorMessage(result.error, { host: githubProjectHost(choice.host) }) + ) return } setViewList(result.views) @@ -190,13 +193,21 @@ export default function ProjectPicker({ const handlePaste = useCallback(async () => { if (isGitHubProjectRefInputTooLarge(pasteInput)) { - setPasteError(GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR) + setPasteError( + projectViewErrorMessage({ + type: 'validation_error', + message: GITHUB_PROJECT_REF_INPUT_TOO_LARGE_ERROR, + reason: 'input_too_large' + }) + ) return } const input = pasteInput.trim() const parsed = parseProjectInput(input) if (!parsed) { - setPasteError('Expected a project URL or owner/number') + setPasteError( + translate('projectViews.picker.pasteUnparseable', 'Expected a project URL or owner/number') + ) return } setPasteError(null) @@ -207,7 +218,7 @@ export default function ProjectPicker({ return } if (!result.ok) { - setPasteError(result.error.message) + setPasteError(projectViewErrorMessage(result.error, { host: parsed.host })) return } setPasteInput('') diff --git a/src/renderer/src/components/github-project/ProjectPickerPanels.tsx b/src/renderer/src/components/github-project/ProjectPickerPanels.tsx index 72071b51669..36af5891189 100644 --- a/src/renderer/src/components/github-project/ProjectPickerPanels.tsx +++ b/src/renderer/src/components/github-project/ProjectPickerPanels.tsx @@ -1,6 +1,7 @@ import React from 'react' import { AlertTriangle, Loader, Pin } from 'lucide-react' import { GhAuthErrorHelp } from './GhAuthErrorHelp' +import { projectViewErrorMessage } from './project-view-error-copy' import { cn } from '@/lib/utils' import { translate } from '@/i18n/i18n' import { isRenderableProjectViewLayout } from '../../../../shared/github/project-types' @@ -164,8 +165,14 @@ export function ProjectPickerPartialFailures({ }): React.JSX.Element { const summary = failures.length === 1 && failures[0].owner !== '*' - ? `Couldn't load projects from ${failures[0].owner}.` - : `Some organizations didn't load (${failures.length}).` + ? translate('projectViews.picker.ownerLoadFailed', "Couldn't load projects from {{owner}}.", { + owner: failures[0].owner + }) + : translate( + 'projectViews.picker.organizationsLoadFailed', + "Some organizations didn't load ({{failureCount}}).", + { failureCount: failures.length } + ) const detail = failures .map((failure) => `${failure.owner === '*' ? 'orgs' : failure.owner}: ${failure.message}`) .join('\n') @@ -208,7 +215,7 @@ export function ProjectPickerError({ } return (
-
{error.message}
+
{projectViewErrorMessage(error, { host })}
) } diff --git a/src/renderer/src/components/github-project/ProjectViewStates.tsx b/src/renderer/src/components/github-project/ProjectViewStates.tsx index b1d83eaa949..2204c21388e 100644 --- a/src/renderer/src/components/github-project/ProjectViewStates.tsx +++ b/src/renderer/src/components/github-project/ProjectViewStates.tsx @@ -5,6 +5,7 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/h import { cn } from '@/lib/utils' import { translate } from '@/i18n/i18n' import { GhAuthErrorHelp } from './GhAuthErrorHelp' +import { projectViewErrorMessage } from './project-view-error-copy' import { isRenderableProjectViewLayout } from '../../../../shared/github/project-types' import type { GitHubProjectViewSummary } from '../../../../shared/github/project-types' import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' @@ -46,11 +47,11 @@ function ProjectViewTab({ const supported = isRenderableProjectViewLayout(view.layout) const layoutLabel = view.layout === 'BOARD_LAYOUT' - ? 'Board' + ? translate('projectViews.layout.board', 'Board') : view.layout === 'ROADMAP_LAYOUT' - ? 'Roadmap' + ? translate('projectViews.layout.roadmap', 'Roadmap') : view.layout === 'TABLE_LAYOUT' - ? 'Table' + ? translate('projectViews.layout.table', 'Table') : formatUnknownLayout(view.layout) const Icon = view.layout === 'BOARD_LAYOUT' @@ -88,7 +89,11 @@ function ProjectViewTab({ if (supported) { return tab } - const message = `Orca doesn't support ${layoutLabel} project views yet.` + const message = translate( + 'projectViews.unsupported.layoutNotSupported', + "Orca doesn't support {{layout}} project views yet.", + { layout: layoutLabel } + ) return ( @@ -154,19 +159,11 @@ export function ProjectViewErrorState({ ) } const copy = - error.type === 'too_large' - ? `This view has ${totalCount ?? 'many'} items — too large to render in Orca. Narrow the view's filter on GitHub.` - : error.type === 'unsupported_layout' - ? // Older hosts may still reject roadmaps; keep the explanation independent of the requested layout. - translate( - 'projectViews.unsupported.description', - 'Orca renders table, board, and roadmap project views. This view uses a layout it cannot render yet.' - ) - : error.type === 'not_found' - ? 'Could not find this project or view.' - : error.type === 'schema_drift' - ? 'Could not read this project view.' - : error.message + error.type === 'not_found' + ? translate('projectViews.error.notFoundState', 'Could not find this project or view.') + : error.type === 'schema_drift' + ? translate('projectViews.error.schemaDrift', 'Could not read this project view.') + : projectViewErrorMessage(error, { host, totalCount }) return (
{copy}
diff --git a/src/renderer/src/components/github-project/project-view-error-copy.ts b/src/renderer/src/components/github-project/project-view-error-copy.ts new file mode 100644 index 00000000000..0a374dbc03b --- /dev/null +++ b/src/renderer/src/components/github-project/project-view-error-copy.ts @@ -0,0 +1,172 @@ +import { translate } from '@/i18n/i18n' +import type { GitHubProjectViewError } from '../../../../shared/github/project-result-types' + +type ProjectViewErrorCopyContext = { + /** Host of the failing surface, used when an older host omits `error.host`. */ + host?: string + totalCount?: number +} + +// Returns undefined when no reason is set. +function reasonCopy(error: GitHubProjectViewError): string | undefined { + const detail = error.detail ?? '' + switch (error.reason) { + case 'project_or_view_not_found': + return translate('projectViews.error.projectOrViewNotFound', 'Project or view not found.') + case 'project_not_found': + return translate('projectViews.error.projectNotFound', 'Project not found.') + case 'view_not_found': + return translate('projectViews.error.viewNotFound', 'Could not find the selected view.') + case 'owner_not_found': + return translate('projectViews.error.ownerNotFound', 'Owner not found.') + case 'item_not_found': + return translate('projectViews.error.itemNotFound', 'Item not found.') + case 'issue_not_found': + return translate('projectViews.error.issueNotFound', 'Issue not found.') + case 'input_required': + return translate('projectViews.error.inputRequired', 'Input required.') + case 'input_too_large': + return translate( + 'projectViews.error.inputTooLarge', + 'Project reference is too large to resolve.' + ) + case 'input_unparseable': + return translate( + 'projectViews.error.inputUnparseable', + 'Could not parse input. Expected a GitHub project URL or `owner/number`.' + ) + case 'invalid_slug': + return translate( + 'projectViews.error.invalidSlug', + 'Invalid {{field}}: "{{value}}" is not a valid GitHub slug.', + { field: detail, value: error.value ?? '' } + ) + case 'invalid_positive_integer': + return translate( + 'projectViews.error.invalidPositiveInteger', + 'Invalid {{field}}: must be a positive integer.', + { field: detail } + ) + case 'invalid_owner_type': + return translate('projectViews.error.invalidOwnerType', 'Invalid ownerType.') + case 'invalid_item_type': + return translate('projectViews.error.invalidItemType', 'Invalid item type.') + case 'missing_ids': + return translate('projectViews.error.missingIds', 'Missing ids.') + case 'updates_required': + return translate('projectViews.error.updatesRequired', 'Updates required.') + case 'comment_body_required': + return translate('projectViews.error.commentBodyRequired', 'Comment body required.') + case 'duplicate_target_not_allowed': + return translate( + 'projectViews.error.duplicateTargetNotAllowed', + 'Duplicate target is only valid when closing as duplicate.' + ) + case 'duplicate_target_required': + return translate( + 'projectViews.error.duplicateTargetRequired', + 'Duplicate target issue number is required.' + ) + case 'request_invalid': + return translate('projectViews.error.requestInvalid', 'Invalid request — {{detail}}', { + detail + }) + case 'request_failed': + return detail + ? translate('projectViews.error.requestFailedDetail', 'GitHub request failed: {{detail}}', { + detail + }) + : translate('projectViews.error.requestFailed', 'GitHub request failed.') + case 'unexpected_rest_response': + return translate( + 'projectViews.error.unexpectedRestResponse', + 'Unexpected REST response: {{detail}}', + { detail } + ) + case undefined: + return undefined + } + // A reason from a newer host falls through to the type copy. + return undefined +} + +// Sentences each `type` identifies on its own, so older hosts without `reason` still get them. +function typeCopy( + error: GitHubProjectViewError, + context: ProjectViewErrorCopyContext +): string | undefined { + const host = error.host ?? context.host + switch (error.type) { + case 'auth_required': + return host + ? translate( + 'projectViews.error.authRequired', + 'Sign in to GitHub to load project tasks. Run `{{command}}`.', + { command: `gh auth login --hostname ${host}` } + ) + : undefined + case 'scope_missing': + return host + ? translate( + 'projectViews.error.scopeMissing', + 'GitHub project access needs additional scopes. Run `{{command}}`.', + { command: `gh auth refresh --hostname ${host} -s project -s read:org -s repo` } + ) + : undefined + case 'rate_limited': + return error.rateLimit + ? translate( + 'projectViews.error.rateLimitNearlyExhausted', + 'GitHub rate limit nearly exhausted ({{remaining}}/{{limit}} left). Resets in ~{{minutes}}m.', + { + remaining: error.rateLimit.remaining, + limit: error.rateLimit.limit, + minutes: error.rateLimit.resetMinutes + } + ) + : translate( + 'projectViews.error.rateLimited', + 'GitHub rate limit hit. Try again in a few minutes.' + ) + case 'network_error': + return translate('projectViews.error.network', 'Network error — check your connection.') + case 'too_large': + return context.totalCount === undefined + ? translate( + 'projectViews.error.tooLargeUnknownCount', + "This view has too many items to render in Orca. Narrow the view's filter on GitHub." + ) + : translate( + 'projectViews.error.tooLarge', + "This view has {{itemCount}} items — too large to render in Orca. Narrow the view's filter on GitHub.", + { itemCount: context.totalCount } + ) + case 'unsupported_layout': + // Older hosts may still reject roadmaps; keep the explanation independent of the requested layout. + return translate( + 'projectViews.unsupported.description', + 'Orca renders table, board, and roadmap project views. This view uses a layout it cannot render yet.' + ) + case 'schema_drift': + return error.detail + ? translate( + 'projectViews.error.schemaDriftDetail', + 'Could not read this project view: {{detail}}.', + { detail: error.detail } + ) + : translate('projectViews.error.schemaDrift', 'Could not read this project view.') + case 'not_found': + case 'validation_error': + case 'unknown': + return undefined + } + return undefined +} + +/** Localized copy for a project-view failure; GitHub's own text and unknown failures keep `message`. */ +export function projectViewErrorMessage( + error: GitHubProjectViewError, + context: ProjectViewErrorCopyContext = {} +): string { + return reasonCopy(error) ?? typeCopy(error, context) ?? error.message +} diff --git a/src/renderer/src/components/github-project/slug-dialog/Comments.tsx b/src/renderer/src/components/github-project/slug-dialog/Comments.tsx index 1c392be6388..2ef14f08683 100644 --- a/src/renderer/src/components/github-project/slug-dialog/Comments.tsx +++ b/src/renderer/src/components/github-project/slug-dialog/Comments.tsx @@ -15,6 +15,7 @@ import type { GitHubProjectMutationResult } from '../../../../../shared/github/project-result-types' import { translate } from '@/i18n/i18n' +import { projectViewErrorMessage } from '../project-view-error-copy' function getRuntimeTarget(target: RuntimeClientTarget) { return target.kind === 'environment' ? target : null @@ -68,7 +69,7 @@ export function CommentsList({ ) : await window.api.gh.deleteIssueCommentBySlug(args) if (!res.ok) { - toast.error(res.error.message) + toast.error(projectViewErrorMessage(res.error)) return } onChange(comments.filter((x) => x.id !== c.id)) @@ -91,7 +92,7 @@ export function CommentsList({ ) : await window.api.gh.updateIssueCommentBySlug(args) if (!res.ok) { - toast.error(res.error.message) + toast.error(projectViewErrorMessage(res.error)) return } onChange(comments.map((x) => (x.id === c.id ? { ...x, body: next } : x))) @@ -235,7 +236,7 @@ export function NewCommentForm({ ) : await window.api.gh.addIssueCommentBySlug(args) if (!res.ok) { - toast.error(res.error.message) + toast.error(projectViewErrorMessage(res.error)) return } onAdded(res.comment) diff --git a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx index 3d407f20f29..d4b99a30812 100644 --- a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx +++ b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx @@ -8,11 +8,13 @@ import CommentMarkdown from '@/components/sidebar/CommentMarkdown' import { useAppStore } from '@/store' import { callRuntimeRpc } from '@/runtime/runtime-rpc-client' import type { GitHubWorkItemDetails } from '../../../../../shared/github/work-item-types' +import type { ProjectWorkItemDetailsBySlugResult } from '../../../../../shared/github/project-result-types' import type { GitHubItemDialogProjectOrigin } from '@/components/GitHubItemDialog' import { LabelsEditor } from './LabelsEditor' import { AssigneesEditor } from './AssigneesEditor' import { CommentsList, NewCommentForm } from './Comments' import { translate } from '@/i18n/i18n' +import { projectViewErrorMessage } from '../project-view-error-copy' export function SlugDialogBody({ projectOrigin, @@ -56,9 +58,7 @@ export function SlugDialogBody({ const target = sourceTarget const request = target.kind === 'environment' - ? callRuntimeRpc< - { ok: true; details: GitHubWorkItemDetails } | { ok: false; error: { message: string } } - >( + ? callRuntimeRpc( target, 'github.project.workItemDetailsBySlug', { owner, repo, host, number, type }, @@ -73,7 +73,7 @@ export function SlugDialogBody({ if (res.ok) { setDetails(res.details) } else { - setError(res.error.message) + setError(projectViewErrorMessage(res.error)) } }) .catch((err) => { @@ -109,7 +109,7 @@ export function SlugDialogBody({ } const res = await patchProjectIssueOrPr(cacheKey, row.id, { title: next }) if (!res.ok) { - toast.error(res.error.message) + toast.error(projectViewErrorMessage(res.error)) } }, [titleDraft, title, patchProjectIssueOrPr, cacheKey, row]) @@ -126,7 +126,7 @@ export function SlugDialogBody({ } const res = await patchProjectIssueOrPr(cacheKey, row.id, { body: bodyDraft }) if (!res.ok) { - toast.error(res.error.message) + toast.error(projectViewErrorMessage(res.error)) return } setDetails((prev) => (prev ? { ...prev, body: bodyDraft } : prev)) @@ -227,7 +227,7 @@ export function SlugDialogBody({ ...(remove.length ? { removeLabels: remove } : {}) }) if (!res.ok) { - toast.error(res.error.message) + toast.error(projectViewErrorMessage(res.error)) } }} /> @@ -247,7 +247,7 @@ export function SlugDialogBody({ ...(remove.length ? { removeAssignees: remove } : {}) }) if (!res.ok) { - toast.error(res.error.message) + toast.error(projectViewErrorMessage(res.error)) } }} /> diff --git a/src/renderer/src/components/github-project/useProjectRowMutations.ts b/src/renderer/src/components/github-project/useProjectRowMutations.ts index c343edf6251..ccb1574f7dd 100644 --- a/src/renderer/src/components/github-project/useProjectRowMutations.ts +++ b/src/renderer/src/components/github-project/useProjectRowMutations.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react' import { toast } from 'sonner' import { useAppStore } from '@/store' +import { projectViewErrorMessage } from './project-view-error-copy' import type { GitHubIssueType, GitHubProjectFieldMutationValue, @@ -22,7 +23,7 @@ export function useProjectRowMutations(currentCacheKey: string | null) { ...(remove.length ? { removeAssignees: remove } : {}) }) if (!result.ok) { - toast.error(result.error.message) + toast.error(projectViewErrorMessage(result.error)) } }, [currentCacheKey, patchProjectIssueOrPr] @@ -37,7 +38,7 @@ export function useProjectRowMutations(currentCacheKey: string | null) { ...(remove.length ? { removeLabels: remove } : {}) }) if (!result.ok) { - toast.error(result.error.message) + toast.error(projectViewErrorMessage(result.error)) } }, [currentCacheKey, patchProjectIssueOrPr] @@ -49,7 +50,7 @@ export function useProjectRowMutations(currentCacheKey: string | null) { } const result = await patchProjectRowIssueType(currentCacheKey, row.id, issueType) if (!result.ok) { - toast.error(result.error.message) + toast.error(projectViewErrorMessage(result.error)) } }, [currentCacheKey, patchProjectRowIssueType] @@ -67,7 +68,7 @@ export function useProjectRowMutations(currentCacheKey: string | null) { ? await updateProjectFieldValue(currentCacheKey, row.id, fieldId, value) : await clearProjectFieldValue(currentCacheKey, row.id, fieldId) if (!result.ok) { - toast.error(result.error.message) + toast.error(projectViewErrorMessage(result.error)) } }, [clearProjectFieldValue, currentCacheKey, updateProjectFieldValue] diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 6b8e1d6d331..4a74b597bb9 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -21456,11 +21456,71 @@ }, "projectViews": { "layout": { - "board": "Board" + "board": "Board", + "roadmap": "Roadmap", + "table": "Table" }, "unsupported": { "switchLayout": "Switch to a Table, Board, or Roadmap view to work with this project in Orca.", - "description": "Orca renders table, board, and roadmap project views. This view uses a layout it cannot render yet." + "description": "Orca renders table, board, and roadmap project views. This view uses a layout it cannot render yet.", + "layoutNotSupported": "Orca doesn't support {{layout}} project views yet." + }, + "authHelp": { + "hostSignedOut": "`gh` is not signed in to {{host}}.", + "hostSignedOutDetail": "GitHub Enterprise hosts need their own `gh` login, separate from github.com. Run the login command in a terminal, complete the browser flow on {{host}}, then reload.", + "ghMissing": "GitHub CLI (`gh`) is not installed or not on PATH.", + "ghMissingDetail": "Orca uses `gh` to talk to GitHub Projects. Install it from cli.github.com, then sign in.", + "envTokenKeyringFallback": "Your keyring already has a `gh` login that will take over once the env var is gone.", + "envTokenLoginAfterUnset": "After unsetting it, run `gh auth login` to sign in normally, then retry.", + "envTokenRemoveWindows": "Find where `{{varName}}` is set (System or User environment variables, or your PowerShell profile), remove it, then restart Orca so the new environment is picked up.", + "envTokenRemovePosix": "Find where `{{varName}}` is exported (commonly `~/.zshrc`, `~/.zshenv`, `~/.bashrc`, `~/.profile`, or your shell's secrets manager), remove it, then restart Orca so the new environment is picked up.", + "envTokenActive": "`{{varName}}` is set in your environment, so `gh` is using that token instead of your keyring login. `gh auth refresh` cannot modify env-supplied tokens — that's why running it didn't help.", + "envTokenInherited": "Orca inherited `{{varName}}` from your shell, and `gh` is using that token. `gh auth refresh` doesn't apply to env-supplied tokens.", + "envTokenInheritedDetailWindows": "Unset `{{varName}}` in the shell that launches Orca (or in your user environment variables), then restart Orca.", + "envTokenInheritedDetailPosix": "Unset `{{varName}}` in the shell that launches Orca (or in your shell rc file), then restart Orca.", + "signedOut": "You’re not signed in to GitHub via `gh`.", + "missingScopes": "Your `gh` token is missing scopes needed for GitHub Projects: {{scopes}}.", + "missingScopesDetail": "Run the refresh command in a terminal. It will open a browser to authorize the new scopes, then come back here and reload.", + "accessDeniedDetail": "Your token has the required scopes but GitHub still denied access. If the project is in an org with SAML SSO, you must authorize this token for the org under Settings → Developer settings → Personal access tokens → Configure SSO." + }, + "picker": { + "pasteUnparseable": "Expected a project URL or owner/number", + "ownerLoadFailed": "Couldn't load projects from {{owner}}.", + "organizationsLoadFailed": "Some organizations didn't load ({{failureCount}})." + }, + "error": { + "notFoundState": "Could not find this project or view.", + "schemaDrift": "Could not read this project view.", + "projectOrViewNotFound": "Project or view not found.", + "projectNotFound": "Project not found.", + "viewNotFound": "Could not find the selected view.", + "ownerNotFound": "Owner not found.", + "itemNotFound": "Item not found.", + "issueNotFound": "Issue not found.", + "inputRequired": "Input required.", + "inputTooLarge": "Project reference is too large to resolve.", + "inputUnparseable": "Could not parse input. Expected a GitHub project URL or `owner/number`.", + "invalidSlug": "Invalid {{field}}: \"{{value}}\" is not a valid GitHub slug.", + "invalidPositiveInteger": "Invalid {{field}}: must be a positive integer.", + "invalidOwnerType": "Invalid ownerType.", + "invalidItemType": "Invalid item type.", + "missingIds": "Missing ids.", + "updatesRequired": "Updates required.", + "commentBodyRequired": "Comment body required.", + "duplicateTargetNotAllowed": "Duplicate target is only valid when closing as duplicate.", + "duplicateTargetRequired": "Duplicate target issue number is required.", + "requestInvalid": "Invalid request — {{detail}}", + "requestFailedDetail": "GitHub request failed: {{detail}}", + "requestFailed": "GitHub request failed.", + "unexpectedRestResponse": "Unexpected REST response: {{detail}}", + "authRequired": "Sign in to GitHub to load project tasks. Run `{{command}}`.", + "scopeMissing": "GitHub project access needs additional scopes. Run `{{command}}`.", + "rateLimitNearlyExhausted": "GitHub rate limit nearly exhausted ({{remaining}}/{{limit}} left). Resets in ~{{minutes}}m.", + "rateLimited": "GitHub rate limit hit. Try again in a few minutes.", + "network": "Network error — check your connection.", + "tooLargeUnknownCount": "This view has too many items to render in Orca. Narrow the view's filter on GitHub.", + "tooLarge": "This view has {{itemCount}} items — too large to render in Orca. Narrow the view's filter on GitHub.", + "schemaDriftDetail": "Could not read this project view: {{detail}}." } }, "projectField": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index d66a349007f..dcb164d7349 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -21203,11 +21203,71 @@ }, "projectViews": { "layout": { - "board": "Tablero" + "board": "Tablero", + "roadmap": "Hoja de ruta", + "table": "Tabla" }, "unsupported": { "switchLayout": "Cambia a una vista de Tabla, Tablero o Hoja de ruta para trabajar con este proyecto en Orca.", - "description": "Orca representa vistas de proyectos de tabla, tablero y hoja de ruta. Esta vista usa un diseño que aún no puede representar." + "description": "Orca representa vistas de proyectos de tabla, tablero y hoja de ruta. Esta vista usa un diseño que aún no puede representar.", + "layoutNotSupported": "Orca aún no admite vistas de proyecto de tipo {{layout}}." + }, + "authHelp": { + "hostSignedOut": "`gh` no ha iniciado sesión en {{host}}.", + "hostSignedOutDetail": "Los hosts de GitHub Enterprise necesitan su propio inicio de sesión de `gh`, independiente de github.com. Ejecuta el comando de inicio de sesión en una terminal, completa el flujo del navegador en {{host}} y luego recarga.", + "ghMissing": "GitHub CLI (`gh`) no está instalado o no está en el PATH.", + "ghMissingDetail": "Orca usa `gh` para comunicarse con GitHub Projects. Instálalo desde cli.github.com y luego inicia sesión.", + "envTokenKeyringFallback": "Tu llavero ya tiene un inicio de sesión de `gh` que se usará cuando quites la variable de entorno.", + "envTokenLoginAfterUnset": "Después de quitarla, ejecuta `gh auth login` para iniciar sesión normalmente y vuelve a intentarlo.", + "envTokenRemoveWindows": "Busca dónde está definida `{{varName}}` (variables de entorno del sistema o del usuario, o tu perfil de PowerShell), elimínala y reinicia Orca para que cargue el nuevo entorno.", + "envTokenRemovePosix": "Busca dónde se exporta `{{varName}}` (normalmente `~/.zshrc`, `~/.zshenv`, `~/.bashrc`, `~/.profile` o el gestor de secretos de tu shell), elimínala y reinicia Orca para que cargue el nuevo entorno.", + "envTokenActive": "`{{varName}}` está definida en tu entorno, así que `gh` usa ese token en lugar del inicio de sesión de tu llavero. `gh auth refresh` no puede modificar tokens que vienen del entorno; por eso ejecutarlo no sirvió.", + "envTokenInherited": "Orca heredó `{{varName}}` de tu shell y `gh` está usando ese token. `gh auth refresh` no se aplica a tokens que vienen del entorno.", + "envTokenInheritedDetailWindows": "Quita `{{varName}}` en el shell que inicia Orca (o en tus variables de entorno de usuario) y luego reinicia Orca.", + "envTokenInheritedDetailPosix": "Quita `{{varName}}` en el shell que inicia Orca (o en el archivo rc de tu shell) y luego reinicia Orca.", + "signedOut": "No has iniciado sesión en GitHub con `gh`.", + "missingScopes": "A tu token de `gh` le faltan permisos necesarios para GitHub Projects: {{scopes}}.", + "missingScopesDetail": "Ejecuta el comando de actualización en una terminal. Abrirá un navegador para autorizar los nuevos permisos; después vuelve aquí y recarga.", + "accessDeniedDetail": "Tu token tiene los permisos necesarios, pero GitHub sigue denegando el acceso. Si el proyecto está en una organización con SAML SSO, debes autorizar este token para la organización en Settings → Developer settings → Personal access tokens → Configure SSO." + }, + "picker": { + "pasteUnparseable": "Se esperaba una URL de proyecto o owner/number", + "ownerLoadFailed": "No se pudieron cargar los proyectos de {{owner}}.", + "organizationsLoadFailed": "Algunas organizaciones no se cargaron ({{failureCount}})." + }, + "error": { + "notFoundState": "No se pudo encontrar este proyecto o vista.", + "schemaDrift": "No se pudo leer esta vista del proyecto.", + "projectOrViewNotFound": "No se encontró el proyecto o la vista.", + "projectNotFound": "No se encontró el proyecto.", + "viewNotFound": "No se pudo encontrar la vista seleccionada.", + "ownerNotFound": "No se encontró el propietario.", + "itemNotFound": "No se encontró el elemento.", + "issueNotFound": "No se encontró el issue.", + "inputRequired": "Se requiere un valor.", + "inputTooLarge": "La referencia del proyecto es demasiado larga para resolverla.", + "inputUnparseable": "No se pudo interpretar la entrada. Se esperaba una URL de proyecto de GitHub o `owner/number`.", + "invalidSlug": "{{field}} no válido: \"{{value}}\" no es un identificador de GitHub válido.", + "invalidPositiveInteger": "{{field}} no válido: debe ser un entero positivo.", + "invalidOwnerType": "Tipo de propietario no válido.", + "invalidItemType": "Tipo de elemento no válido.", + "missingIds": "Faltan identificadores.", + "updatesRequired": "Se requieren cambios.", + "commentBodyRequired": "El comentario no puede estar vacío.", + "duplicateTargetNotAllowed": "El destino duplicado solo es válido al cerrar como duplicado.", + "duplicateTargetRequired": "Se requiere el número del issue de destino duplicado.", + "requestInvalid": "Solicitud no válida — {{detail}}", + "requestFailedDetail": "La solicitud a GitHub falló: {{detail}}", + "requestFailed": "La solicitud a GitHub falló.", + "unexpectedRestResponse": "Respuesta REST inesperada: {{detail}}", + "authRequired": "Inicia sesión en GitHub para cargar las tareas del proyecto. Ejecuta `{{command}}`.", + "scopeMissing": "El acceso a proyectos de GitHub necesita permisos adicionales. Ejecuta `{{command}}`.", + "rateLimitNearlyExhausted": "El límite de velocidad de GitHub está casi agotado (quedan {{remaining}}/{{limit}}). Se restablece en unos {{minutes}} min.", + "rateLimited": "Se alcanzó el límite de velocidad de GitHub. Inténtalo de nuevo en unos minutos.", + "network": "Error de red — comprueba tu conexión.", + "tooLargeUnknownCount": "Esta vista tiene demasiados elementos para mostrarla en Orca. Restringe el filtro de la vista en GitHub.", + "tooLarge": "Esta vista tiene {{itemCount}} elementos, demasiados para mostrarla en Orca. Restringe el filtro de la vista en GitHub.", + "schemaDriftDetail": "No se pudo leer esta vista del proyecto: {{detail}}." } }, "projectField": { diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index 7fcc8983643..311b1ce12fd 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -21203,11 +21203,71 @@ }, "projectViews": { "layout": { - "board": "Board" + "board": "Board", + "roadmap": "Feuille de route", + "table": "Tableau" }, "unsupported": { "switchLayout": "Basculez vers une vue Tableau, Board ou Feuille de route pour travailler avec ce projet dans Orca.", - "description": "Orca affiche les vues de projet de type Tableau, Board et Feuille de route. Cette vue utilise une mise en page qu'Orca ne peut pas encore restituer." + "description": "Orca affiche les vues de projet de type Tableau, Board et Feuille de route. Cette vue utilise une mise en page qu'Orca ne peut pas encore restituer.", + "layoutNotSupported": "Orca ne prend pas encore en charge les vues de projet de type {{layout}}." + }, + "authHelp": { + "hostSignedOut": "`gh` n'est pas connecté à {{host}}.", + "hostSignedOutDetail": "Les hôtes GitHub Enterprise nécessitent leur propre connexion `gh`, distincte de github.com. Exécutez la commande de connexion dans un terminal, terminez le parcours dans le navigateur sur {{host}}, puis rechargez.", + "ghMissing": "GitHub CLI (`gh`) n'est pas installé ou n'est pas dans le PATH.", + "ghMissingDetail": "Orca utilise `gh` pour communiquer avec GitHub Projects. Installez-le depuis cli.github.com, puis connectez-vous.", + "envTokenKeyringFallback": "Votre trousseau contient déjà une connexion `gh` qui prendra le relais une fois la variable d'environnement supprimée.", + "envTokenLoginAfterUnset": "Après l'avoir supprimée, exécutez `gh auth login` pour vous connecter normalement, puis réessayez.", + "envTokenRemoveWindows": "Trouvez où `{{varName}}` est définie (variables d'environnement système ou utilisateur, ou votre profil PowerShell), supprimez-la, puis redémarrez Orca pour qu'il prenne en compte le nouvel environnement.", + "envTokenRemovePosix": "Trouvez où `{{varName}}` est exportée (souvent `~/.zshrc`, `~/.zshenv`, `~/.bashrc`, `~/.profile` ou le gestionnaire de secrets de votre shell), supprimez-la, puis redémarrez Orca pour qu'il prenne en compte le nouvel environnement.", + "envTokenActive": "`{{varName}}` est définie dans votre environnement, donc `gh` utilise ce jeton au lieu de la connexion de votre trousseau. `gh auth refresh` ne peut pas modifier les jetons fournis par l'environnement — c'est pourquoi l'exécuter n'a rien changé.", + "envTokenInherited": "Orca a hérité de `{{varName}}` depuis votre shell, et `gh` utilise ce jeton. `gh auth refresh` ne s'applique pas aux jetons fournis par l'environnement.", + "envTokenInheritedDetailWindows": "Supprimez `{{varName}}` dans le shell qui lance Orca (ou dans vos variables d'environnement utilisateur), puis redémarrez Orca.", + "envTokenInheritedDetailPosix": "Supprimez `{{varName}}` dans le shell qui lance Orca (ou dans le fichier rc de votre shell), puis redémarrez Orca.", + "signedOut": "Vous n'êtes pas connecté à GitHub via `gh`.", + "missingScopes": "Il manque à votre jeton `gh` des autorisations nécessaires pour GitHub Projects : {{scopes}}.", + "missingScopesDetail": "Exécutez la commande d'actualisation dans un terminal. Elle ouvrira un navigateur pour autoriser les nouvelles autorisations ; revenez ensuite ici et rechargez.", + "accessDeniedDetail": "Votre jeton dispose des autorisations requises, mais GitHub refuse toujours l'accès. Si le projet appartient à une organisation avec SAML SSO, vous devez autoriser ce jeton pour l'organisation dans Settings → Developer settings → Personal access tokens → Configure SSO." + }, + "picker": { + "pasteUnparseable": "URL de projet ou owner/number attendu", + "ownerLoadFailed": "Impossible de charger les projets de {{owner}}.", + "organizationsLoadFailed": "Certaines organisations n'ont pas été chargées ({{failureCount}})." + }, + "error": { + "notFoundState": "Impossible de trouver ce projet ou cette vue.", + "schemaDrift": "Impossible de lire cette vue de projet.", + "projectOrViewNotFound": "Projet ou vue introuvable.", + "projectNotFound": "Projet introuvable.", + "viewNotFound": "Impossible de trouver la vue sélectionnée.", + "ownerNotFound": "Propriétaire introuvable.", + "itemNotFound": "Élément introuvable.", + "issueNotFound": "Issue introuvable.", + "inputRequired": "Saisie requise.", + "inputTooLarge": "La référence du projet est trop longue pour être résolue.", + "inputUnparseable": "Impossible d'analyser la saisie. URL de projet GitHub ou `owner/number` attendu.", + "invalidSlug": "{{field}} invalide : « {{value}} » n'est pas un identifiant GitHub valide.", + "invalidPositiveInteger": "{{field}} invalide : doit être un entier positif.", + "invalidOwnerType": "Type de propriétaire invalide.", + "invalidItemType": "Type d'élément invalide.", + "missingIds": "Identifiants manquants.", + "updatesRequired": "Modifications requises.", + "commentBodyRequired": "Le contenu du commentaire est requis.", + "duplicateTargetNotAllowed": "Une cible de doublon n'est valide que lors d'une fermeture comme doublon.", + "duplicateTargetRequired": "Le numéro de l'issue cible du doublon est requis.", + "requestInvalid": "Requête invalide — {{detail}}", + "requestFailedDetail": "La requête GitHub a échoué : {{detail}}", + "requestFailed": "La requête GitHub a échoué.", + "unexpectedRestResponse": "Réponse REST inattendue : {{detail}}", + "authRequired": "Connectez-vous à GitHub pour charger les tâches du projet. Exécutez `{{command}}`.", + "scopeMissing": "L'accès aux projets GitHub nécessite des autorisations supplémentaires. Exécutez `{{command}}`.", + "rateLimitNearlyExhausted": "Limite de débit GitHub presque atteinte (il reste {{remaining}}/{{limit}}). Réinitialisation dans environ {{minutes}} min.", + "rateLimited": "Limite de débit GitHub atteinte. Réessayez dans quelques minutes.", + "network": "Erreur réseau — vérifiez votre connexion.", + "tooLargeUnknownCount": "Cette vue contient trop d'éléments pour être affichée dans Orca. Restreignez le filtre de la vue sur GitHub.", + "tooLarge": "Cette vue contient {{itemCount}} éléments, trop pour être affichée dans Orca. Restreignez le filtre de la vue sur GitHub.", + "schemaDriftDetail": "Impossible de lire cette vue de projet : {{detail}}." } }, "projectField": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index b9f84c537b2..7a096a868dd 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -21207,11 +21207,71 @@ }, "projectViews": { "layout": { - "board": "ボード" + "board": "ボード", + "roadmap": "ロードマップ", + "table": "テーブル" }, "unsupported": { "switchLayout": "Orca でこのプロジェクトを操作するには、テーブル、ボード、またはロードマップビューに切り替えます。", - "description": "Orca はテーブル、ボード、ロードマップのプロジェクトビューをレンダリングします。このビューは、まだレンダリングできないレイアウトを使用しています。" + "description": "Orca はテーブル、ボード、ロードマップのプロジェクトビューをレンダリングします。このビューは、まだレンダリングできないレイアウトを使用しています。", + "layoutNotSupported": "Orca は{{layout}}のプロジェクトビューにはまだ対応していません。" + }, + "authHelp": { + "hostSignedOut": "`gh` は {{host}} にサインインしていません。", + "hostSignedOutDetail": "GitHub Enterprise ホストには github.com とは別に `gh` のログインが必要です。ターミナルでログインコマンドを実行し、{{host}} でブラウザーのフローを完了してから再読み込みしてください。", + "ghMissing": "GitHub CLI(`gh`)がインストールされていないか、PATH にありません。", + "ghMissingDetail": "Orca は `gh` を使って GitHub Projects と通信します。cli.github.com からインストールしてサインインしてください。", + "envTokenKeyringFallback": "キーリングにはすでに `gh` のログインがあり、環境変数を削除するとそちらが使われます。", + "envTokenLoginAfterUnset": "削除したら `gh auth login` を実行して通常どおりサインインし、もう一度お試しください。", + "envTokenRemoveWindows": "`{{varName}}` が設定されている場所(システムまたはユーザーの環境変数、あるいは PowerShell プロファイル)を見つけて削除し、新しい環境を読み込むために Orca を再起動してください。", + "envTokenRemovePosix": "`{{varName}}` を export している場所(一般的には `~/.zshrc`、`~/.zshenv`、`~/.bashrc`、`~/.profile`、またはシェルのシークレットマネージャー)を見つけて削除し、新しい環境を読み込むために Orca を再起動してください。", + "envTokenActive": "環境に `{{varName}}` が設定されているため、`gh` はキーリングのログインではなくそのトークンを使っています。`gh auth refresh` は環境変数で渡されたトークンを変更できないため、実行しても効果がありませんでした。", + "envTokenInherited": "Orca はシェルから `{{varName}}` を引き継いでおり、`gh` はそのトークンを使っています。`gh auth refresh` は環境変数で渡されたトークンには適用されません。", + "envTokenInheritedDetailWindows": "Orca を起動するシェル(またはユーザー環境変数)で `{{varName}}` を削除してから、Orca を再起動してください。", + "envTokenInheritedDetailPosix": "Orca を起動するシェル(またはシェルの rc ファイル)で `{{varName}}` を削除してから、Orca を再起動してください。", + "signedOut": "`gh` で GitHub にサインインしていません。", + "missingScopes": "`gh` トークンに GitHub Projects に必要なスコープがありません: {{scopes}}。", + "missingScopesDetail": "ターミナルで refresh コマンドを実行してください。ブラウザーが開いて新しいスコープを承認できます。完了したらここに戻って再読み込みしてください。", + "accessDeniedDetail": "トークンには必要なスコープがありますが、GitHub がアクセスを拒否しました。プロジェクトが SAML SSO を使う組織にある場合は、Settings → Developer settings → Personal access tokens → Configure SSO でこのトークンをその組織に対して承認する必要があります。" + }, + "picker": { + "pasteUnparseable": "プロジェクトの URL または owner/number を入力してください", + "ownerLoadFailed": "{{owner}} のプロジェクトを読み込めませんでした。", + "organizationsLoadFailed": "一部の組織を読み込めませんでした({{failureCount}})。" + }, + "error": { + "notFoundState": "このプロジェクトまたはビューが見つかりませんでした。", + "schemaDrift": "このプロジェクトビューを読み取れませんでした。", + "projectOrViewNotFound": "プロジェクトまたはビューが見つかりません。", + "projectNotFound": "プロジェクトが見つかりません。", + "viewNotFound": "選択したビューが見つかりませんでした。", + "ownerNotFound": "所有者が見つかりません。", + "itemNotFound": "アイテムが見つかりません。", + "issueNotFound": "Issue が見つかりません。", + "inputRequired": "入力が必要です。", + "inputTooLarge": "プロジェクトの参照が長すぎるため解決できません。", + "inputUnparseable": "入力を解析できませんでした。GitHub プロジェクトの URL または `owner/number` を入力してください。", + "invalidSlug": "{{field}} が無効です: \"{{value}}\" は有効な GitHub のスラッグではありません。", + "invalidPositiveInteger": "{{field}} が無効です: 正の整数を指定してください。", + "invalidOwnerType": "所有者の種類が無効です。", + "invalidItemType": "アイテムの種類が無効です。", + "missingIds": "ID がありません。", + "updatesRequired": "更新内容が必要です。", + "commentBodyRequired": "コメント本文が必要です。", + "duplicateTargetNotAllowed": "重複先は、重複としてクローズする場合にのみ指定できます。", + "duplicateTargetRequired": "重複先の Issue 番号が必要です。", + "requestInvalid": "無効なリクエスト — {{detail}}", + "requestFailedDetail": "GitHub へのリクエストが失敗しました: {{detail}}", + "requestFailed": "GitHub へのリクエストが失敗しました。", + "unexpectedRestResponse": "予期しない REST レスポンス: {{detail}}", + "authRequired": "プロジェクトのタスクを読み込むには GitHub にサインインしてください。`{{command}}` を実行します。", + "scopeMissing": "GitHub プロジェクトへのアクセスには追加のスコープが必要です。`{{command}}` を実行します。", + "rateLimitNearlyExhausted": "GitHub のレート制限がまもなく上限に達します(残り {{remaining}}/{{limit}})。約 {{minutes}} 分後にリセットされます。", + "rateLimited": "GitHub のレート制限に達しました。数分後にもう一度お試しください。", + "network": "ネットワークエラー — 接続を確認してください。", + "tooLargeUnknownCount": "このビューはアイテムが多すぎて Orca で表示できません。GitHub でビューのフィルターを絞り込んでください。", + "tooLarge": "このビューには {{itemCount}} 件のアイテムがあり、Orca で表示するには多すぎます。GitHub でビューのフィルターを絞り込んでください。", + "schemaDriftDetail": "このプロジェクトビューを読み取れませんでした: {{detail}}。" } }, "projectField": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 78d9f256c68..71294f0d583 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -21144,11 +21144,71 @@ }, "projectViews": { "layout": { - "board": "보드" + "board": "보드", + "roadmap": "로드맵", + "table": "테이블" }, "unsupported": { "switchLayout": "Orca에서 이 프로젝트로 작업하려면 테이블, 보드 또는 로드맵 보기로 전환하세요.", - "description": "Orca는 테이블, 보드, 로드맵 프로젝트 보기를 렌더링합니다. 이 보기는 아직 렌더링할 수 없는 레이아웃을 사용합니다." + "description": "Orca는 테이블, 보드, 로드맵 프로젝트 보기를 렌더링합니다. 이 보기는 아직 렌더링할 수 없는 레이아웃을 사용합니다.", + "layoutNotSupported": "Orca는 아직 {{layout}} 프로젝트 보기를 지원하지 않습니다." + }, + "authHelp": { + "hostSignedOut": "`gh`가 {{host}}에 로그인되어 있지 않습니다.", + "hostSignedOutDetail": "GitHub Enterprise 호스트는 github.com과 별도로 `gh` 로그인이 필요합니다. 터미널에서 로그인 명령을 실행하고 {{host}}에서 브라우저 절차를 완료한 다음 다시 로드하세요.", + "ghMissing": "GitHub CLI(`gh`)가 설치되어 있지 않거나 PATH에 없습니다.", + "ghMissingDetail": "Orca는 `gh`를 사용해 GitHub Projects와 통신합니다. cli.github.com에서 설치한 다음 로그인하세요.", + "envTokenKeyringFallback": "키링에 이미 `gh` 로그인이 있으므로 환경 변수를 제거하면 그 로그인이 사용됩니다.", + "envTokenLoginAfterUnset": "해제한 후 `gh auth login`을 실행해 정상적으로 로그인한 다음 다시 시도하세요.", + "envTokenRemoveWindows": "`{{varName}}`이(가) 설정된 위치(시스템 또는 사용자 환경 변수, 또는 PowerShell 프로필)를 찾아 제거한 다음, 새 환경이 적용되도록 Orca를 다시 시작하세요.", + "envTokenRemovePosix": "`{{varName}}`을(를) export하는 위치(보통 `~/.zshrc`, `~/.zshenv`, `~/.bashrc`, `~/.profile` 또는 셸의 시크릿 관리자)를 찾아 제거한 다음, 새 환경이 적용되도록 Orca를 다시 시작하세요.", + "envTokenActive": "환경에 `{{varName}}`이(가) 설정되어 있어 `gh`가 키링 로그인 대신 해당 토큰을 사용하고 있습니다. `gh auth refresh`는 환경 변수로 제공된 토큰을 변경할 수 없으므로 실행해도 효과가 없었습니다.", + "envTokenInherited": "Orca가 셸에서 `{{varName}}`을(를) 상속했으며 `gh`가 해당 토큰을 사용하고 있습니다. `gh auth refresh`는 환경 변수로 제공된 토큰에는 적용되지 않습니다.", + "envTokenInheritedDetailWindows": "Orca를 실행하는 셸(또는 사용자 환경 변수)에서 `{{varName}}`을(를) 해제한 다음 Orca를 다시 시작하세요.", + "envTokenInheritedDetailPosix": "Orca를 실행하는 셸(또는 셸 rc 파일)에서 `{{varName}}`을(를) 해제한 다음 Orca를 다시 시작하세요.", + "signedOut": "`gh`를 통해 GitHub에 로그인되어 있지 않습니다.", + "missingScopes": "`gh` 토큰에 GitHub Projects에 필요한 범위가 없습니다: {{scopes}}.", + "missingScopesDetail": "터미널에서 refresh 명령을 실행하세요. 새 범위를 승인할 수 있도록 브라우저가 열립니다. 완료되면 여기로 돌아와 다시 로드하세요.", + "accessDeniedDetail": "토큰에 필요한 범위가 있지만 GitHub가 여전히 접근을 거부했습니다. 프로젝트가 SAML SSO를 사용하는 조직에 있다면 Settings → Developer settings → Personal access tokens → Configure SSO에서 해당 조직에 대해 이 토큰을 승인해야 합니다." + }, + "picker": { + "pasteUnparseable": "프로젝트 URL 또는 owner/number 형식이어야 합니다", + "ownerLoadFailed": "{{owner}}의 프로젝트를 불러올 수 없습니다.", + "organizationsLoadFailed": "일부 조직을 불러오지 못했습니다({{failureCount}})." + }, + "error": { + "notFoundState": "이 프로젝트 또는 보기를 찾을 수 없습니다.", + "schemaDrift": "이 프로젝트 보기를 읽을 수 없습니다.", + "projectOrViewNotFound": "프로젝트 또는 보기를 찾을 수 없습니다.", + "projectNotFound": "프로젝트를 찾을 수 없습니다.", + "viewNotFound": "선택한 보기를 찾을 수 없습니다.", + "ownerNotFound": "소유자를 찾을 수 없습니다.", + "itemNotFound": "항목을 찾을 수 없습니다.", + "issueNotFound": "이슈를 찾을 수 없습니다.", + "inputRequired": "입력이 필요합니다.", + "inputTooLarge": "프로젝트 참조가 너무 길어 확인할 수 없습니다.", + "inputUnparseable": "입력을 해석할 수 없습니다. GitHub 프로젝트 URL 또는 `owner/number` 형식이어야 합니다.", + "invalidSlug": "잘못된 {{field}}: \"{{value}}\"은(는) 유효한 GitHub 슬러그가 아닙니다.", + "invalidPositiveInteger": "잘못된 {{field}}: 양의 정수여야 합니다.", + "invalidOwnerType": "잘못된 소유자 유형입니다.", + "invalidItemType": "잘못된 항목 유형입니다.", + "missingIds": "ID가 없습니다.", + "updatesRequired": "변경 사항이 필요합니다.", + "commentBodyRequired": "댓글 내용이 필요합니다.", + "duplicateTargetNotAllowed": "중복 대상은 중복으로 닫을 때만 지정할 수 있습니다.", + "duplicateTargetRequired": "중복 대상 이슈 번호가 필요합니다.", + "requestInvalid": "잘못된 요청 — {{detail}}", + "requestFailedDetail": "GitHub 요청 실패: {{detail}}", + "requestFailed": "GitHub 요청에 실패했습니다.", + "unexpectedRestResponse": "예상치 못한 REST 응답: {{detail}}", + "authRequired": "프로젝트 작업을 불러오려면 GitHub에 로그인하세요. `{{command}}`을(를) 실행하세요.", + "scopeMissing": "GitHub 프로젝트에 접근하려면 추가 범위가 필요합니다. `{{command}}`을(를) 실행하세요.", + "rateLimitNearlyExhausted": "GitHub 속도 제한이 거의 소진되었습니다({{remaining}}/{{limit}} 남음). 약 {{minutes}}분 후 초기화됩니다.", + "rateLimited": "GitHub 속도 제한에 도달했습니다. 몇 분 후 다시 시도하세요.", + "network": "네트워크 오류 — 연결을 확인하세요.", + "tooLargeUnknownCount": "이 보기는 항목이 너무 많아 Orca에서 렌더링할 수 없습니다. GitHub에서 보기 필터를 좁히세요.", + "tooLarge": "이 보기에는 항목이 {{itemCount}}개 있어 Orca에서 렌더링하기에는 너무 많습니다. GitHub에서 보기 필터를 좁히세요.", + "schemaDriftDetail": "이 프로젝트 보기를 읽을 수 없습니다: {{detail}}." } }, "projectField": { diff --git a/src/renderer/src/i18n/locales/pt.json b/src/renderer/src/i18n/locales/pt.json index 4a77073beba..26f488e023b 100644 --- a/src/renderer/src/i18n/locales/pt.json +++ b/src/renderer/src/i18n/locales/pt.json @@ -21130,11 +21130,71 @@ }, "projectViews": { "layout": { - "board": "Quadro" + "board": "Quadro", + "roadmap": "Roteiro", + "table": "Tabela" }, "unsupported": { "switchLayout": "Mude para uma visualização de Tabela, Quadro ou Roteiro para trabalhar com este projeto no Orca.", - "description": "O Orca exibe visualizações de projetos em tabela, quadro e roteiro. Esta visualização usa um layout que ele ainda não pode exibir." + "description": "O Orca exibe visualizações de projetos em tabela, quadro e roteiro. Esta visualização usa um layout que ele ainda não pode exibir.", + "layoutNotSupported": "O Orca ainda não oferece suporte a visualizações de projeto do tipo {{layout}}." + }, + "authHelp": { + "hostSignedOut": "O `gh` não está conectado a {{host}}.", + "hostSignedOutDetail": "Hosts do GitHub Enterprise precisam de um login próprio no `gh`, separado do github.com. Execute o comando de login em um terminal, conclua o fluxo no navegador em {{host}} e depois recarregue.", + "ghMissing": "O GitHub CLI (`gh`) não está instalado ou não está no PATH.", + "ghMissingDetail": "O Orca usa o `gh` para se comunicar com o GitHub Projects. Instale-o em cli.github.com e depois faça login.", + "envTokenKeyringFallback": "Seu chaveiro já tem um login do `gh` que passará a ser usado assim que a variável de ambiente for removida.", + "envTokenLoginAfterUnset": "Depois de removê-la, execute `gh auth login` para fazer login normalmente e tente novamente.", + "envTokenRemoveWindows": "Encontre onde `{{varName}}` está definida (variáveis de ambiente do sistema ou do usuário, ou seu perfil do PowerShell), remova-a e reinicie o Orca para que o novo ambiente seja carregado.", + "envTokenRemovePosix": "Encontre onde `{{varName}}` é exportada (geralmente `~/.zshrc`, `~/.zshenv`, `~/.bashrc`, `~/.profile` ou o gerenciador de segredos do seu shell), remova-a e reinicie o Orca para que o novo ambiente seja carregado.", + "envTokenActive": "`{{varName}}` está definida no seu ambiente, então o `gh` está usando esse token em vez do login do seu chaveiro. O `gh auth refresh` não consegue alterar tokens fornecidos pelo ambiente — por isso executá-lo não ajudou.", + "envTokenInherited": "O Orca herdou `{{varName}}` do seu shell, e o `gh` está usando esse token. O `gh auth refresh` não se aplica a tokens fornecidos pelo ambiente.", + "envTokenInheritedDetailWindows": "Remova `{{varName}}` no shell que inicia o Orca (ou nas suas variáveis de ambiente de usuário) e depois reinicie o Orca.", + "envTokenInheritedDetailPosix": "Remova `{{varName}}` no shell que inicia o Orca (ou no arquivo rc do seu shell) e depois reinicie o Orca.", + "signedOut": "Você não está conectado ao GitHub pelo `gh`.", + "missingScopes": "Seu token do `gh` não tem escopos necessários para o GitHub Projects: {{scopes}}.", + "missingScopesDetail": "Execute o comando de atualização em um terminal. Ele abrirá um navegador para autorizar os novos escopos; depois volte aqui e recarregue.", + "accessDeniedDetail": "Seu token tem os escopos necessários, mas o GitHub ainda negou o acesso. Se o projeto estiver em uma organização com SAML SSO, você precisa autorizar este token para a organização em Settings → Developer settings → Personal access tokens → Configure SSO." + }, + "picker": { + "pasteUnparseable": "Esperava-se uma URL de projeto ou owner/number", + "ownerLoadFailed": "Não foi possível carregar os projetos de {{owner}}.", + "organizationsLoadFailed": "Algumas organizações não foram carregadas ({{failureCount}})." + }, + "error": { + "notFoundState": "Não foi possível encontrar este projeto ou visualização.", + "schemaDrift": "Não foi possível ler esta visualização do projeto.", + "projectOrViewNotFound": "Projeto ou visualização não encontrado.", + "projectNotFound": "Projeto não encontrado.", + "viewNotFound": "Não foi possível encontrar a visualização selecionada.", + "ownerNotFound": "Proprietário não encontrado.", + "itemNotFound": "Item não encontrado.", + "issueNotFound": "Issue não encontrada.", + "inputRequired": "Entrada obrigatória.", + "inputTooLarge": "A referência do projeto é longa demais para ser resolvida.", + "inputUnparseable": "Não foi possível interpretar a entrada. Esperava-se uma URL de projeto do GitHub ou `owner/number`.", + "invalidSlug": "{{field}} inválido: \"{{value}}\" não é um identificador válido do GitHub.", + "invalidPositiveInteger": "{{field}} inválido: deve ser um inteiro positivo.", + "invalidOwnerType": "Tipo de proprietário inválido.", + "invalidItemType": "Tipo de item inválido.", + "missingIds": "IDs ausentes.", + "updatesRequired": "Alterações obrigatórias.", + "commentBodyRequired": "O conteúdo do comentário é obrigatório.", + "duplicateTargetNotAllowed": "O destino duplicado só é válido ao fechar como duplicada.", + "duplicateTargetRequired": "O número da issue de destino duplicada é obrigatório.", + "requestInvalid": "Solicitação inválida — {{detail}}", + "requestFailedDetail": "A solicitação ao GitHub falhou: {{detail}}", + "requestFailed": "A solicitação ao GitHub falhou.", + "unexpectedRestResponse": "Resposta REST inesperada: {{detail}}", + "authRequired": "Faça login no GitHub para carregar as tarefas do projeto. Execute `{{command}}`.", + "scopeMissing": "O acesso a projetos do GitHub precisa de escopos adicionais. Execute `{{command}}`.", + "rateLimitNearlyExhausted": "O limite de taxa do GitHub está quase esgotado (restam {{remaining}}/{{limit}}). Será redefinido em cerca de {{minutes}} min.", + "rateLimited": "Limite de taxa do GitHub atingido. Tente novamente em alguns minutos.", + "network": "Erro de rede — verifique sua conexão.", + "tooLargeUnknownCount": "Esta visualização tem itens demais para ser exibida no Orca. Restrinja o filtro da visualização no GitHub.", + "tooLarge": "Esta visualização tem {{itemCount}} itens, demais para ser exibida no Orca. Restrinja o filtro da visualização no GitHub.", + "schemaDriftDetail": "Não foi possível ler esta visualização do projeto: {{detail}}." } }, "projectField": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 3a2ffbd6c43..5222e302f2e 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -21145,11 +21145,71 @@ }, "projectViews": { "layout": { - "board": "看板" + "board": "看板", + "roadmap": "路线图", + "table": "表格" }, "unsupported": { "switchLayout": "切换到表格、看板或路线图视图以在 Orca 中处理此项目。", - "description": "Orca 可渲染表格、看板和路线图项目视图。此视图使用的布局尚无法渲染。" + "description": "Orca 可渲染表格、看板和路线图项目视图。此视图使用的布局尚无法渲染。", + "layoutNotSupported": "Orca 尚不支持{{layout}}项目视图。" + }, + "authHelp": { + "hostSignedOut": "`gh` 尚未登录 {{host}}。", + "hostSignedOutDetail": "GitHub Enterprise 主机需要单独的 `gh` 登录,与 github.com 分开。请在终端中运行登录命令,在 {{host}} 上完成浏览器流程,然后重新加载。", + "ghMissing": "GitHub CLI(`gh`)未安装或不在 PATH 中。", + "ghMissingDetail": "Orca 使用 `gh` 与 GitHub Projects 通信。请从 cli.github.com 安装,然后登录。", + "envTokenKeyringFallback": "你的钥匙串中已有 `gh` 登录,移除该环境变量后会自动接管。", + "envTokenLoginAfterUnset": "取消设置后,请运行 `gh auth login` 正常登录,然后重试。", + "envTokenRemoveWindows": "找到设置 `{{varName}}` 的位置(系统或用户环境变量,或你的 PowerShell 配置文件),将其移除,然后重启 Orca 以加载新的环境。", + "envTokenRemovePosix": "找到导出 `{{varName}}` 的位置(通常是 `~/.zshrc`、`~/.zshenv`、`~/.bashrc`、`~/.profile` 或你的 shell 密钥管理器),将其移除,然后重启 Orca 以加载新的环境。", + "envTokenActive": "你的环境中设置了 `{{varName}}`,因此 `gh` 使用的是该令牌,而不是钥匙串中的登录。`gh auth refresh` 无法修改来自环境变量的令牌,所以运行它没有效果。", + "envTokenInherited": "Orca 从你的 shell 继承了 `{{varName}}`,`gh` 正在使用该令牌。`gh auth refresh` 不适用于来自环境变量的令牌。", + "envTokenInheritedDetailWindows": "在启动 Orca 的 shell 中(或在用户环境变量中)取消设置 `{{varName}}`,然后重启 Orca。", + "envTokenInheritedDetailPosix": "在启动 Orca 的 shell 中(或在 shell rc 文件中)取消设置 `{{varName}}`,然后重启 Orca。", + "signedOut": "你尚未通过 `gh` 登录 GitHub。", + "missingScopes": "你的 `gh` 令牌缺少 GitHub Projects 所需的权限范围:{{scopes}}。", + "missingScopesDetail": "请在终端中运行刷新命令。它会打开浏览器以授权新的权限范围,完成后回到这里并重新加载。", + "accessDeniedDetail": "你的令牌已具备所需权限范围,但 GitHub 仍拒绝访问。如果项目所在组织启用了 SAML SSO,你需要在 Settings → Developer settings → Personal access tokens → Configure SSO 中为该组织授权此令牌。" + }, + "picker": { + "pasteUnparseable": "应为项目 URL 或 owner/number", + "ownerLoadFailed": "无法加载 {{owner}} 的项目。", + "organizationsLoadFailed": "部分组织未能加载({{failureCount}})。" + }, + "error": { + "notFoundState": "找不到此项目或视图。", + "schemaDrift": "无法读取此项目视图。", + "projectOrViewNotFound": "未找到项目或视图。", + "projectNotFound": "未找到项目。", + "viewNotFound": "找不到所选视图。", + "ownerNotFound": "未找到所有者。", + "itemNotFound": "未找到条目。", + "issueNotFound": "未找到议题。", + "inputRequired": "请输入内容。", + "inputTooLarge": "项目引用过长,无法解析。", + "inputUnparseable": "无法解析输入。应为 GitHub 项目 URL 或 `owner/number`。", + "invalidSlug": "无效的 {{field}}:“{{value}}”不是有效的 GitHub 标识。", + "invalidPositiveInteger": "无效的 {{field}}:必须是正整数。", + "invalidOwnerType": "无效的所有者类型。", + "invalidItemType": "无效的条目类型。", + "missingIds": "缺少 ID。", + "updatesRequired": "需要提供更新内容。", + "commentBodyRequired": "评论内容不能为空。", + "duplicateTargetNotAllowed": "仅在以重复关闭时才能指定重复目标。", + "duplicateTargetRequired": "需要提供重复目标的议题编号。", + "requestInvalid": "无效请求 — {{detail}}", + "requestFailedDetail": "GitHub 请求失败:{{detail}}", + "requestFailed": "GitHub 请求失败。", + "unexpectedRestResponse": "意外的 REST 响应:{{detail}}", + "authRequired": "请登录 GitHub 以加载项目任务。运行 `{{command}}`。", + "scopeMissing": "访问 GitHub 项目需要额外的权限范围。运行 `{{command}}`。", + "rateLimitNearlyExhausted": "GitHub 速率限制即将用尽(剩余 {{remaining}}/{{limit}})。约 {{minutes}} 分钟后重置。", + "rateLimited": "已达到 GitHub 速率限制。请几分钟后重试。", + "network": "网络错误 — 请检查你的网络连接。", + "tooLargeUnknownCount": "此视图条目过多,无法在 Orca 中渲染。请在 GitHub 上缩小视图的筛选范围。", + "tooLarge": "此视图有 {{itemCount}} 个条目,过多而无法在 Orca 中渲染。请在 GitHub 上缩小视图的筛选范围。", + "schemaDriftDetail": "无法读取此项目视图:{{detail}}。" } }, "projectField": { diff --git a/src/shared/github/project-result-types.ts b/src/shared/github/project-result-types.ts index 8737867523a..f6a9a567842 100644 --- a/src/shared/github/project-result-types.ts +++ b/src/shared/github/project-result-types.ts @@ -22,9 +22,42 @@ export type GitHubProjectViewErrorType = | 'rate_limited' | 'unknown' +/** Distinguishes Orca-authored sentences that share one `type`; renderers fall back to `message` for unknown or missing reasons. */ +export type GitHubProjectViewErrorReason = + | 'project_or_view_not_found' + | 'project_not_found' + | 'view_not_found' + | 'owner_not_found' + | 'item_not_found' + | 'issue_not_found' + | 'input_required' + | 'input_too_large' + | 'input_unparseable' + | 'invalid_slug' + | 'invalid_positive_integer' + | 'invalid_owner_type' + | 'invalid_item_type' + | 'missing_ids' + | 'updates_required' + | 'comment_body_required' + | 'duplicate_target_not_allowed' + | 'duplicate_target_required' + | 'request_invalid' + | 'request_failed' + | 'unexpected_rest_response' + export type GitHubProjectViewError = { type: GitHubProjectViewErrorType + /** English copy kept for mobile, older clients and logs. */ message: string + reason?: GitHubProjectViewErrorReason + /** GitHub host for sign-in commands. */ + host?: string + rateLimit?: { remaining: number; limit: number; resetMinutes: number } + /** Untranslated diagnostic or field name that follows Orca's own prefix. */ + detail?: string + /** Rejected slug value for `invalid_slug`. */ + value?: string /** Classified GraphQL details only; never include tokens or full command stdout. */ details?: { path?: (string | number)[]; code?: string } }