Add i18n foundation and translations (#1027)
* feat: add i18n foundation * feat: localize sessions sidebar * Localize multirun/scheduled tasks and fix dialog dropdown interactions * localize git sidebar surface and add zh-CN keys * feat(ui): localize context panel, diff/plan views, and context sidebar content * fix(config): resolve user config home via fs/home before embedded home * localize header/chat UI and complete model/worktree panel strings * localize worktree + github issue/pr dialog flows * localize settings sections and split settings i18n dictionaries * localize additional settings sections and sidebars * localize more settings pages and dialogs * fix settings select trigger localization * localize tunnel settings ui surface * localize additional settings sections * localize keyboard shortcuts labels in settings * localize terminal and utility dialogs surfaces * feat(i18n): localize remaining UI strings * Add Ukrainian locale * Add Spanish locale * Add Brazilian Portuguese locale * Polish locale translations
This commit is contained in:
committed by
GitHub
parent
87db2ea210
commit
7d7285655d
@@ -63,6 +63,7 @@ import type {
|
||||
GitHubPullRequestStatus,
|
||||
GitRemote,
|
||||
} from '@/lib/api/types';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
type MergeMethod = 'merge' | 'squash' | 'rebase';
|
||||
|
||||
@@ -278,6 +279,7 @@ export const PullRequestSection: React.FC<{
|
||||
remoteBranches?: string[];
|
||||
onGeneratedDescription?: () => void;
|
||||
}> = ({ directory, branch, baseBranch, trackingBranch, remotes = [], remoteBranches = [], onGeneratedDescription }) => {
|
||||
const { t } = useI18n();
|
||||
const { github } = useRuntimeAPIs();
|
||||
const githubAuthStatus = useGitHubAuthStore((state) => state.status);
|
||||
const githubAuthChecked = useGitHubAuthStore((state) => state.hasChecked);
|
||||
@@ -516,7 +518,7 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
const openChecksDialog = React.useCallback(async () => {
|
||||
if (!github?.prContext) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
if (!pr) return;
|
||||
@@ -532,7 +534,7 @@ export const PullRequestSection: React.FC<{
|
||||
setCheckDetails(ctx);
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to load check details', { description: message });
|
||||
toast.error(t('gitView.pr.toast.loadCheckDetailsFailed'), { description: message });
|
||||
} finally {
|
||||
setIsLoadingCheckDetails(false);
|
||||
}
|
||||
@@ -540,7 +542,7 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
const openCommentsDialog = React.useCallback(async () => {
|
||||
if (!github?.prContext) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
if (!pr) return;
|
||||
@@ -555,7 +557,7 @@ export const PullRequestSection: React.FC<{
|
||||
setCommentsDetails(ctx);
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to load comments', { description: message });
|
||||
toast.error(t('gitView.pr.toast.loadCommentsFailed'), { description: message });
|
||||
} finally {
|
||||
setIsLoadingCommentsDetails(false);
|
||||
}
|
||||
@@ -594,11 +596,11 @@ export const PullRequestSection: React.FC<{
|
||||
const issue = (commentsDetails?.issueComments ?? []).map((comment) => ({
|
||||
id: `issue-${comment.id}`,
|
||||
body: comment.body || '',
|
||||
authorName: comment.author?.name || comment.author?.login || 'Unknown author',
|
||||
authorName: comment.author?.name || comment.author?.login || t('gitView.pr.comments.unknownAuthor'),
|
||||
authorLogin: comment.author?.login || null,
|
||||
avatarUrl: comment.author?.avatarUrl || null,
|
||||
createdAt: comment.createdAt,
|
||||
context: 'General comment',
|
||||
context: t('gitView.pr.comments.generalContext'),
|
||||
path: null as string | null,
|
||||
line: null as number | null,
|
||||
}));
|
||||
@@ -606,11 +608,11 @@ export const PullRequestSection: React.FC<{
|
||||
const review = (commentsDetails?.reviewComments ?? []).map((comment) => ({
|
||||
id: `review-${comment.id}`,
|
||||
body: comment.body || '',
|
||||
authorName: comment.author?.name || comment.author?.login || 'Unknown author',
|
||||
authorName: comment.author?.name || comment.author?.login || t('gitView.pr.comments.unknownAuthor'),
|
||||
authorLogin: comment.author?.login || null,
|
||||
avatarUrl: comment.author?.avatarUrl || null,
|
||||
createdAt: comment.createdAt,
|
||||
context: 'Code review comment',
|
||||
context: t('gitView.pr.comments.reviewContext'),
|
||||
path: comment.path || null,
|
||||
line: comment.line ?? null,
|
||||
}));
|
||||
@@ -624,11 +626,11 @@ export const PullRequestSection: React.FC<{
|
||||
return aVal - bVal;
|
||||
});
|
||||
return all;
|
||||
}, [commentsDetails]);
|
||||
}, [commentsDetails, t]);
|
||||
|
||||
const resolveChatDispatchTarget = React.useCallback((): ChatDispatchTarget | null => {
|
||||
if (!currentSessionId) {
|
||||
toast.error('No active session', { description: 'Open a chat session first.' });
|
||||
toast.error(t('gitView.pr.toast.noActiveSession'), { description: t('gitView.pr.toast.noActiveSessionDescription') });
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -637,7 +639,7 @@ export const PullRequestSection: React.FC<{
|
||||
const providerID = currentProviderId || lastUsedProvider?.providerID;
|
||||
const modelID = currentModelId || lastUsedProvider?.modelID;
|
||||
if (!providerID || !modelID) {
|
||||
toast.error('No model selected');
|
||||
toast.error(t('gitView.pr.toast.noModelSelected'));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -670,7 +672,7 @@ export const PullRequestSection: React.FC<{
|
||||
target.currentVariant ?? undefined,
|
||||
).catch((e) => {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to send message', { description: message });
|
||||
toast.error(t('gitView.pr.toast.sendMessageFailed'), { description: message });
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -743,7 +745,7 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
{run.job?.steps && run.job.steps.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
<div className="typography-micro text-muted-foreground">Steps</div>
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.checks.steps')}</div>
|
||||
<div className="space-y-1">
|
||||
{run.job.steps.map((step, idx) => {
|
||||
const c = (step.conclusion || '').toLowerCase();
|
||||
@@ -787,11 +789,11 @@ export const PullRequestSection: React.FC<{
|
||||
</button>
|
||||
<CollapsibleContent>
|
||||
<div className="ml-6 mt-1 rounded border border-border/40 bg-transparent px-2 py-2 typography-micro text-muted-foreground space-y-1">
|
||||
{typeof step.number === 'number' ? <div>Step: {step.number}</div> : null}
|
||||
{step.status ? <div>Status: {step.status}</div> : null}
|
||||
{step.conclusion ? <div>Conclusion: {step.conclusion}</div> : null}
|
||||
{step.startedAt ? <div>Started: {formatTimestamp(step.startedAt)}</div> : null}
|
||||
{step.completedAt ? <div>Completed: {formatTimestamp(step.completedAt)}</div> : null}
|
||||
{typeof step.number === 'number' ? <div>{t('gitView.pr.checks.stepLabel')}: {step.number}</div> : null}
|
||||
{step.status ? <div>{t('gitView.pr.checks.statusLabel')}: {step.status}</div> : null}
|
||||
{step.conclusion ? <div>{t('gitView.pr.checks.conclusionLabel')}: {step.conclusion}</div> : null}
|
||||
{step.startedAt ? <div>{t('gitView.pr.checks.startedLabel')}: {formatTimestamp(step.startedAt)}</div> : null}
|
||||
{step.completedAt ? <div>{t('gitView.pr.checks.completedLabel')}: {formatTimestamp(step.completedAt)}</div> : null}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
@@ -808,7 +810,7 @@ export const PullRequestSection: React.FC<{
|
||||
setActiveMainTab('chat');
|
||||
|
||||
if (!github?.prContext) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
if (!directory || !pr) return;
|
||||
@@ -827,7 +829,7 @@ export const PullRequestSection: React.FC<{
|
||||
});
|
||||
|
||||
if (failed.length === 0) {
|
||||
toast.message('No failed checks');
|
||||
toast.message(t('gitView.pr.toast.noFailedChecks'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -856,7 +858,7 @@ export const PullRequestSection: React.FC<{
|
||||
dispatchSyntheticPrompt(target, visibleText, instructionsText, payloadText);
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to load checks', { description: message });
|
||||
toast.error(t('gitView.pr.toast.loadChecksFailed'), { description: message });
|
||||
}
|
||||
}, [directory, dispatchSyntheticPrompt, github, pr, resolveChatDispatchTarget, setActiveMainTab]);
|
||||
|
||||
@@ -864,7 +866,7 @@ export const PullRequestSection: React.FC<{
|
||||
setActiveMainTab('chat');
|
||||
|
||||
if (!github?.prContext) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
if (!directory || !pr) return;
|
||||
@@ -879,7 +881,7 @@ export const PullRequestSection: React.FC<{
|
||||
const reviewComments = context.reviewComments ?? [];
|
||||
const total = issueComments.length + reviewComments.length;
|
||||
if (total === 0) {
|
||||
toast.message('No PR comments');
|
||||
toast.message(t('gitView.pr.toast.noPrComments'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -895,7 +897,7 @@ export const PullRequestSection: React.FC<{
|
||||
dispatchSyntheticPrompt(target, visibleText, instructionsText, payloadText);
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to load PR comments', { description: message });
|
||||
toast.error(t('gitView.pr.toast.loadPrCommentsFailed'), { description: message });
|
||||
}
|
||||
}, [directory, dispatchSyntheticPrompt, github, pr, resolveChatDispatchTarget, setActiveMainTab]);
|
||||
|
||||
@@ -1136,7 +1138,7 @@ export const PullRequestSection: React.FC<{
|
||||
onGeneratedDescription?.();
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to generate description', { description: message });
|
||||
toast.error(t('gitView.pr.toast.generateDescriptionFailed'), { description: message });
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
@@ -1144,22 +1146,22 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
const createPr = React.useCallback(async () => {
|
||||
if (!github?.prCreate) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
const trimmedTitle = title.trim();
|
||||
if (!trimmedTitle) {
|
||||
toast.error('Title is required');
|
||||
toast.error(t('gitView.pr.toast.titleRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedBase = targetBaseBranch.trim();
|
||||
if (!trimmedBase) {
|
||||
toast.error('Base branch is required');
|
||||
toast.error(t('gitView.pr.toast.baseBranchRequired'));
|
||||
return;
|
||||
}
|
||||
if (trimmedBase === branch) {
|
||||
toast.error('Base branch must differ from head branch');
|
||||
toast.error(t('gitView.pr.toast.baseMustDifferFromHead'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1176,13 +1178,13 @@ export const PullRequestSection: React.FC<{
|
||||
draft,
|
||||
...(selectedRemote ? { remote: selectedRemote.name } : {}),
|
||||
});
|
||||
toast.success('PR created');
|
||||
toast.success(t('gitView.pr.toast.prCreated'));
|
||||
updatePrStatus(prStatusKey, (prev) => (prev ? { ...prev, pr } : prev));
|
||||
await refresh({ force: true });
|
||||
scheduleActionRefresh();
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to create PR', { description: message });
|
||||
toast.error(t('gitView.pr.toast.createPrFailed'), { description: message });
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
@@ -1190,22 +1192,22 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
const mergePr = React.useCallback(async (pr: GitHubPullRequest) => {
|
||||
if (!github?.prMerge) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
setIsMerging(true);
|
||||
try {
|
||||
const result = await github.prMerge({ directory, number: pr.number, method: mergeMethod });
|
||||
if (result.merged) {
|
||||
toast.success('PR merged');
|
||||
toast.success(t('gitView.pr.toast.prMerged'));
|
||||
} else {
|
||||
toast.message('PR not merged', { description: result.message || 'Not mergeable' });
|
||||
toast.message(t('gitView.pr.toast.prNotMerged'), { description: result.message || t('gitView.pr.notMergeable') });
|
||||
}
|
||||
await refresh({ force: true });
|
||||
scheduleActionRefresh();
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Merge failed', { description: message });
|
||||
toast.error(t('gitView.pr.toast.mergeFailed'), { description: message });
|
||||
if (pr.url) {
|
||||
void openExternal(pr.url);
|
||||
}
|
||||
@@ -1216,18 +1218,18 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
const markReady = React.useCallback(async (pr: GitHubPullRequest) => {
|
||||
if (!github?.prReady) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
setIsMarkingReady(true);
|
||||
try {
|
||||
await github.prReady({ directory, number: pr.number });
|
||||
toast.success('Marked ready for review');
|
||||
toast.success(t('gitView.pr.toast.markedReady'));
|
||||
await refresh({ force: true });
|
||||
scheduleActionRefresh();
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to mark ready', { description: message });
|
||||
toast.error(t('gitView.pr.toast.markReadyFailed'), { description: message });
|
||||
if (pr.url) {
|
||||
void openExternal(pr.url);
|
||||
}
|
||||
@@ -1238,13 +1240,13 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
const updatePr = React.useCallback(async (pr: GitHubPullRequest) => {
|
||||
if (!github?.prUpdate) {
|
||||
toast.error('GitHub runtime API unavailable');
|
||||
toast.error(t('gitView.pr.toast.githubApiUnavailable'));
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedTitle = editTitle.trim();
|
||||
if (!trimmedTitle) {
|
||||
toast.error('Title is required');
|
||||
toast.error(t('gitView.pr.toast.titleRequired'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1266,12 +1268,12 @@ export const PullRequestSection: React.FC<{
|
||||
}
|
||||
: prev));
|
||||
setIsEditingPr(false);
|
||||
toast.success('PR updated');
|
||||
toast.success(t('gitView.pr.toast.prUpdated'));
|
||||
await refresh({ force: true });
|
||||
scheduleActionRefresh();
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
toast.error('Failed to update PR', { description: message });
|
||||
toast.error(t('gitView.pr.toast.updatePrFailed'), { description: message });
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
@@ -1312,17 +1314,17 @@ export const PullRequestSection: React.FC<{
|
||||
type="button"
|
||||
className="inline-flex size-6 shrink-0 items-center justify-center rounded-md border border-border/60 bg-background/70 hover:bg-interactive-hover/60"
|
||||
onClick={() => void openExternal(pr.url)}
|
||||
aria-label="Open PR on GitHub"
|
||||
aria-label={t('gitView.pr.actions.openOnGitHubAria')}
|
||||
>
|
||||
<PrStateIcon className="size-4 shrink-0" style={{ color: prColorVar }} />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Open PR on GitHub</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.openOnGitHub')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<PrStateIcon className="size-4 shrink-0" style={{ color: 'var(--surface-muted-foreground)' }} />
|
||||
)}
|
||||
<h3 className="typography-ui-header font-semibold text-foreground truncate">Pull Request</h3>
|
||||
<h3 className="typography-ui-header font-semibold text-foreground truncate">{t('gitView.pullRequest.title')}</h3>
|
||||
{pr ? (
|
||||
<span className="typography-meta text-muted-foreground truncate">#{pr.number}</span>
|
||||
) : null}
|
||||
@@ -1372,7 +1374,7 @@ export const PullRequestSection: React.FC<{
|
||||
<span style={{ color: prColorVar }}>
|
||||
{pr.state}{pr.draft ? ' (draft)' : ''}
|
||||
</span>
|
||||
{pr.mergeable === false ? ' · not mergeable' : ''}
|
||||
{pr.mergeable === false ? ` · ${t('gitView.pr.notMergeable')}` : ''}
|
||||
{pr.state === 'open' && typeof pr.mergeableState === 'string' && pr.mergeableState && pr.mergeableState !== 'unknown'
|
||||
? ` · ${pr.mergeableState}`
|
||||
: ''}
|
||||
@@ -1383,18 +1385,18 @@ export const PullRequestSection: React.FC<{
|
||||
<div className={bodyClassName}>
|
||||
{shouldShowConnectionNotice ? (
|
||||
<div className="space-y-2">
|
||||
<div className="typography-meta text-muted-foreground">
|
||||
GitHub not connected. Connect your GitHub account in settings.
|
||||
<div className="typography-meta text-muted-foreground">
|
||||
{t('gitView.pr.githubNotConnected')}
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={openGitHubSettings} className="w-fit">
|
||||
Open settings
|
||||
{t('gitView.pr.actions.openSettings')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<div className="space-y-2">
|
||||
<div className="typography-ui-label text-foreground">PR status unavailable</div>
|
||||
<div className="typography-ui-label text-foreground">{t('gitView.pr.statusUnavailable')}</div>
|
||||
<div className="typography-meta text-muted-foreground break-words">{error}</div>
|
||||
{repoUrl ? (
|
||||
<Button variant="outline" size="sm" asChild className="w-fit">
|
||||
@@ -1410,7 +1412,7 @@ export const PullRequestSection: React.FC<{
|
||||
{!pr && !isInitialStatusResolved && !error && !shouldShowConnectionNotice ? (
|
||||
<div className="flex items-center gap-2 typography-micro text-muted-foreground">
|
||||
<RiLoader4Line className="size-4 animate-spin" />
|
||||
Checking PR status...
|
||||
{t('gitView.pr.checkingStatus')}
|
||||
</div>
|
||||
) : pr ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -1421,7 +1423,7 @@ export const PullRequestSection: React.FC<{
|
||||
<Input
|
||||
value={editTitle}
|
||||
onChange={(e) => setEditTitle(e.target.value)}
|
||||
placeholder="PR title"
|
||||
placeholder={t('gitView.pr.placeholder.title')}
|
||||
autoCorrect={hasTouchInput ? "on" : "off"}
|
||||
autoCapitalize={hasTouchInput ? "sentences" : "off"}
|
||||
spellCheck={hasTouchInput}
|
||||
@@ -1430,7 +1432,7 @@ export const PullRequestSection: React.FC<{
|
||||
value={editBody}
|
||||
onChange={(e) => setEditBody(e.target.value)}
|
||||
className="min-h-[120px] bg-background/80"
|
||||
placeholder="Describe this PR"
|
||||
placeholder={t('gitView.pr.placeholder.description')}
|
||||
autoCorrect={hasTouchInput ? "on" : "off"}
|
||||
autoCapitalize={hasTouchInput ? "sentences" : "off"}
|
||||
spellCheck={hasTouchInput}
|
||||
@@ -1446,18 +1448,18 @@ export const PullRequestSection: React.FC<{
|
||||
/>
|
||||
) : (
|
||||
<div className="typography-micro text-muted-foreground whitespace-pre-wrap break-words mt-1">
|
||||
{isHydratingCurrentPrBody ? 'Loading description...' : 'No description provided.'}
|
||||
{isHydratingCurrentPrBody ? t('gitView.pr.loadingDescription') : t('gitView.pr.noDescription')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{canMerge && pr.draft ? (
|
||||
<div className="typography-micro text-muted-foreground">
|
||||
Draft PRs must be marked ready before merge.
|
||||
{t('gitView.pr.draftMustBeReady')}
|
||||
</div>
|
||||
) : null}
|
||||
{!canMerge ? (
|
||||
<div className="typography-micro text-muted-foreground">No merge permission; use Open in GitHub.</div>
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.noMergePermission')}</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1478,12 +1480,12 @@ export const PullRequestSection: React.FC<{
|
||||
setEditBody(pr.body || '');
|
||||
}}
|
||||
disabled={isUpdating}
|
||||
aria-label="Cancel editing"
|
||||
aria-label={t('gitView.pr.actions.cancelEditingAria')}
|
||||
>
|
||||
<RiCloseLine className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Cancel editing</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.cancelEditing')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -1492,12 +1494,12 @@ export const PullRequestSection: React.FC<{
|
||||
className="h-7 w-7 px-0"
|
||||
onClick={() => updatePr(pr)}
|
||||
disabled={isUpdating || !editTitle.trim()}
|
||||
aria-label="Save PR title and description"
|
||||
aria-label={t('gitView.pr.actions.savePrAria')}
|
||||
>
|
||||
{isUpdating ? <RiLoader4Line className="size-4 animate-spin" /> : <RiCheckLine className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Save PR title and description</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.savePr')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
@@ -1508,12 +1510,12 @@ export const PullRequestSection: React.FC<{
|
||||
size="sm"
|
||||
className="h-7 w-7 px-0"
|
||||
onClick={() => setIsEditingPr(true)}
|
||||
aria-label="Edit PR title and description"
|
||||
aria-label={t('gitView.pr.actions.editPrAria')}
|
||||
>
|
||||
<RiEditLine className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Edit PR title and description</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.editPr')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
) : null}
|
||||
@@ -1527,12 +1529,12 @@ export const PullRequestSection: React.FC<{
|
||||
className="h-7 w-7 px-0"
|
||||
onClick={openChecksDialog}
|
||||
disabled={isLoadingCheckDetails}
|
||||
aria-label="Open checks details"
|
||||
aria-label={t('gitView.pr.actions.openChecksAria')}
|
||||
>
|
||||
{isLoadingCheckDetails ? <RiLoader4Line className="size-4 animate-spin" /> : <RiInformationLine className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Open checks details</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.openChecks')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
|
||||
@@ -1544,12 +1546,12 @@ export const PullRequestSection: React.FC<{
|
||||
size="sm"
|
||||
className="h-7 w-7 px-0 border-[var(--status-success-border)] bg-[var(--status-success-background)] text-[var(--status-success)]"
|
||||
onClick={sendFailedChecksToChat}
|
||||
aria-label="Resolve failed checks with agent"
|
||||
aria-label={t('gitView.pr.actions.resolveFailedChecksAria')}
|
||||
>
|
||||
<RiErrorWarningLine className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Resolve failed checks with agent</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.resolveFailedChecks')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
|
||||
@@ -1560,12 +1562,12 @@ export const PullRequestSection: React.FC<{
|
||||
size="sm"
|
||||
className="h-7 w-7 px-0"
|
||||
onClick={openCommentsDialog}
|
||||
aria-label="Open PR comments"
|
||||
aria-label={t('gitView.pr.actions.openCommentsAria')}
|
||||
>
|
||||
<RiChat4Line className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Open PR comments</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.openComments')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip delayDuration={300}>
|
||||
@@ -1575,12 +1577,12 @@ export const PullRequestSection: React.FC<{
|
||||
size="sm"
|
||||
className="h-7 w-7 px-0 border-[var(--status-success-border)] bg-[var(--status-success-background)] text-[var(--status-success)]"
|
||||
onClick={sendCommentsToChat}
|
||||
aria-label="Share comments with agent"
|
||||
aria-label={t('gitView.pr.actions.shareCommentsAria')}
|
||||
>
|
||||
<RiAiGenerate2 className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Share comments with agent</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.shareComments')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{canMerge && pr.draft && pr.state === 'open' ? (
|
||||
@@ -1592,12 +1594,12 @@ export const PullRequestSection: React.FC<{
|
||||
className="h-7 w-7 px-0"
|
||||
onClick={() => markReady(pr)}
|
||||
disabled={isMarkingReady || isMerging || isUpdating || isEditingPr}
|
||||
aria-label="Mark PR ready for review"
|
||||
aria-label={t('gitView.pr.actions.markReadyAria')}
|
||||
>
|
||||
{isMarkingReady ? <RiLoader4Line className="size-4 animate-spin" /> : <RiCheckboxCircleLine className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Mark PR ready for review</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.markReady')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1614,9 +1616,9 @@ export const PullRequestSection: React.FC<{
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="squash">Squash</SelectItem>
|
||||
<SelectItem value="merge">Merge</SelectItem>
|
||||
<SelectItem value="rebase">Rebase</SelectItem>
|
||||
<SelectItem value="squash">{t('gitView.pr.mergeMethod.squash')}</SelectItem>
|
||||
<SelectItem value="merge">{t('gitView.pr.mergeMethod.merge')}</SelectItem>
|
||||
<SelectItem value="rebase">{t('gitView.pr.mergeMethod.rebase')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Tooltip delayDuration={300}>
|
||||
@@ -1626,12 +1628,12 @@ export const PullRequestSection: React.FC<{
|
||||
className="h-7 w-7 px-0"
|
||||
onClick={() => mergePr(pr)}
|
||||
disabled={isMerging || isMarkingReady || pr.state !== 'open' || pr.draft || isUpdating || isEditingPr}
|
||||
aria-label="Merge pull request"
|
||||
aria-label={t('gitView.pr.actions.mergePrAria')}
|
||||
>
|
||||
{isMerging ? <RiLoader4Line className="size-4 animate-spin" /> : <RiGitMergeLine className="size-4" />}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Merge pull request</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.mergePr')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : null}
|
||||
@@ -1643,7 +1645,7 @@ export const PullRequestSection: React.FC<{
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<div className="typography-ui-label text-foreground">Create PR</div>
|
||||
<div className="typography-ui-label text-foreground">{t('gitView.pr.createTitle')}</div>
|
||||
<div className="typography-micro text-muted-foreground truncate">
|
||||
{branch} → {targetBaseBranch}
|
||||
</div>
|
||||
@@ -1652,18 +1654,18 @@ export const PullRequestSection: React.FC<{
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href={repoUrl} target="_blank" rel="noopener noreferrer">
|
||||
<RiExternalLinkLine className="size-4" />
|
||||
Repo
|
||||
{t('gitView.pr.actions.repo')}
|
||||
</a>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<label className="space-y-1">
|
||||
<div className="typography-micro text-muted-foreground">Title</div>
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.field.title')}</div>
|
||||
<Input
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="PR title"
|
||||
placeholder={t('gitView.pr.placeholder.title')}
|
||||
autoCorrect={hasTouchInput ? "on" : "off"}
|
||||
autoCapitalize={hasTouchInput ? "sentences" : "off"}
|
||||
spellCheck={hasTouchInput}
|
||||
@@ -1671,11 +1673,11 @@ export const PullRequestSection: React.FC<{
|
||||
</label>
|
||||
|
||||
<label className="space-y-1">
|
||||
<div className="typography-micro text-muted-foreground">Base branch</div>
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.field.baseBranch')}</div>
|
||||
{availableBaseBranches.length > 0 ? (
|
||||
<Select value={targetBaseBranch} onValueChange={setTargetBaseBranch}>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue placeholder="Select base branch" />
|
||||
<SelectValue placeholder={t('gitView.pr.placeholder.selectBaseBranch')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableBaseBranches.map((candidate) => (
|
||||
@@ -1687,18 +1689,18 @@ export const PullRequestSection: React.FC<{
|
||||
<Input
|
||||
value={targetBaseBranch}
|
||||
onChange={(e) => setTargetBaseBranch(e.target.value)}
|
||||
placeholder="main"
|
||||
placeholder={t('gitView.pr.placeholder.main')}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
|
||||
<label className="space-y-1">
|
||||
<div className="typography-micro text-muted-foreground">Description</div>
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.field.description')}</div>
|
||||
<Textarea
|
||||
value={body}
|
||||
onChange={(e) => setBody(e.target.value)}
|
||||
className="min-h-[110px]"
|
||||
placeholder="What changed and why"
|
||||
placeholder={t('gitView.pr.placeholder.whatChanged')}
|
||||
autoCorrect={hasTouchInput ? "on" : "off"}
|
||||
autoCapitalize={hasTouchInput ? "sentences" : "off"}
|
||||
spellCheck={hasTouchInput}
|
||||
@@ -1722,9 +1724,9 @@ export const PullRequestSection: React.FC<{
|
||||
size="sm"
|
||||
checked={draft}
|
||||
onChange={(next) => setDraft(next)}
|
||||
ariaLabel="Toggle draft PR"
|
||||
ariaLabel={t('gitView.pr.actions.toggleDraftAria')}
|
||||
/>
|
||||
<span className="typography-ui-label text-foreground select-none">Draft</span>
|
||||
<span className="typography-ui-label text-foreground select-none">{t('gitView.pr.field.draft')}</span>
|
||||
</div>
|
||||
|
||||
{/* Additional Context Section */}
|
||||
@@ -1732,20 +1734,20 @@ export const PullRequestSection: React.FC<{
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="typography-micro text-muted-foreground">
|
||||
Additional context (optional)
|
||||
{t('gitView.pr.additionalContext.optional')}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setIsContextSheetOpen(true)}
|
||||
>
|
||||
{additionalContext.trim() ? 'Edit' : 'Add'}
|
||||
{additionalContext.trim() ? t('gitView.pr.actions.edit') : t('gitView.pr.actions.add')}
|
||||
</Button>
|
||||
</div>
|
||||
{additionalContext.trim() && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-flex items-center rounded-full bg-[var(--interactive-selection)] px-2 py-0.5 text-xs text-[var(--interactive-selection-foreground)]">
|
||||
Context added
|
||||
{t('gitView.pr.additionalContext.added')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -1754,10 +1756,10 @@ export const PullRequestSection: React.FC<{
|
||||
<Collapsible open={isContextOpen} onOpenChange={setIsContextOpen}>
|
||||
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] px-3 py-2 hover:bg-[var(--interactive-hover)]">
|
||||
<span className="typography-micro text-muted-foreground">
|
||||
Additional context (optional)
|
||||
{t('gitView.pr.additionalContext.optional')}
|
||||
</span>
|
||||
<span className="typography-micro text-[var(--primary-base)]">
|
||||
{isContextOpen ? 'Hide' : additionalContext.trim() ? 'Edit' : 'Add'}
|
||||
{isContextOpen ? t('gitView.pr.actions.hide') : additionalContext.trim() ? t('gitView.pr.actions.edit') : t('gitView.pr.actions.add')}
|
||||
</span>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
@@ -1766,10 +1768,10 @@ export const PullRequestSection: React.FC<{
|
||||
value={additionalContext}
|
||||
onChange={(e) => setAdditionalContext(e.target.value)}
|
||||
className="min-h-[100px] bg-transparent"
|
||||
placeholder="Explain why this change is needed... Mention how to test (commands / steps)... Call out risks / rollout plan..."
|
||||
placeholder={t('gitView.pr.placeholder.additionalContext')}
|
||||
/>
|
||||
<p className="typography-micro text-muted-foreground">
|
||||
This text is only used to guide PR generation.
|
||||
{t('gitView.pr.additionalContext.hint')}
|
||||
</p>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
@@ -1780,14 +1782,14 @@ export const PullRequestSection: React.FC<{
|
||||
<MobileOverlayPanel
|
||||
open={isContextSheetOpen}
|
||||
onClose={() => setIsContextSheetOpen(false)}
|
||||
title="Additional context"
|
||||
title={t('gitView.pr.additionalContext.title')}
|
||||
footer={
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setIsContextSheetOpen(false)}
|
||||
className="w-full"
|
||||
>
|
||||
Done
|
||||
{t('gitView.common.done')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
@@ -1796,11 +1798,11 @@ export const PullRequestSection: React.FC<{
|
||||
value={additionalContext}
|
||||
onChange={(e) => setAdditionalContext(e.target.value)}
|
||||
className="min-h-[200px] bg-transparent"
|
||||
placeholder="Explain why this change is needed... Mention how to test (commands / steps)... Call out risks / rollout plan..."
|
||||
placeholder={t('gitView.pr.placeholder.additionalContext')}
|
||||
autoFocus
|
||||
/>
|
||||
<p className="typography-micro text-muted-foreground">
|
||||
This text is only used to guide PR generation.
|
||||
{t('gitView.pr.additionalContext.hint')}
|
||||
</p>
|
||||
</div>
|
||||
</MobileOverlayPanel>
|
||||
@@ -1813,7 +1815,7 @@ export const PullRequestSection: React.FC<{
|
||||
disabled={isGenerating || isCreating}
|
||||
>
|
||||
{isGenerating ? <RiLoader4Line className="size-4 animate-spin" /> : <RiAiGenerate2 className="size-4 text-primary" />}
|
||||
Generate
|
||||
{t('gitView.commit.generate')}
|
||||
</Button>
|
||||
<div className="flex-1" />
|
||||
<Button
|
||||
@@ -1825,7 +1827,7 @@ export const PullRequestSection: React.FC<{
|
||||
<span className="inline-flex size-4 items-center justify-center">
|
||||
{isCreating ? <RiLoader4Line className="size-4 animate-spin" /> : <RiGitPullRequestLine className="size-4" />}
|
||||
</span>
|
||||
<span>Create PR</span>
|
||||
<span>{t('gitView.pr.actions.createPr')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1837,10 +1839,10 @@ export const PullRequestSection: React.FC<{
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiGitPullRequestLine className="h-5 w-5" />
|
||||
Check Details
|
||||
{t('gitView.pr.checkDetails.title')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{pr ? `PR #${pr.number}` : 'Pull request'}
|
||||
{pr ? t('gitView.pr.numberLabel', { number: pr.number }) : t('gitView.pullRequest.title')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -1848,7 +1850,7 @@ export const PullRequestSection: React.FC<{
|
||||
{isLoadingCheckDetails ? (
|
||||
<div className="text-center text-muted-foreground py-8 flex items-center justify-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
Loading...
|
||||
{t('gitView.loading.loading')}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1864,7 +1866,7 @@ export const PullRequestSection: React.FC<{
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="text-center text-muted-foreground py-8">No check details available.</div>
|
||||
<div className="text-center text-muted-foreground py-8">{t('gitView.pr.checkDetails.empty')}</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -1878,9 +1880,9 @@ export const PullRequestSection: React.FC<{
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<RiGitPullRequestLine className="h-5 w-5" />
|
||||
PR Comments
|
||||
{t('gitView.pr.comments.title')}
|
||||
{pr ? (
|
||||
<span className="typography-meta text-muted-foreground">PR #{pr.number}</span>
|
||||
<span className="typography-meta text-muted-foreground">{t('gitView.pr.numberLabel', { number: pr.number })}</span>
|
||||
) : null}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
@@ -1889,7 +1891,7 @@ export const PullRequestSection: React.FC<{
|
||||
{isLoadingCommentsDetails ? (
|
||||
<div className="text-center text-muted-foreground py-8 flex items-center justify-center gap-2">
|
||||
<RiLoader4Line className="h-4 w-4 animate-spin" />
|
||||
Loading...
|
||||
{t('gitView.loading.loading')}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1927,13 +1929,13 @@ export const PullRequestSection: React.FC<{
|
||||
onClick={() => {
|
||||
void sendSingleCommentToChat(comment);
|
||||
}}
|
||||
aria-label="Send this comment to agent"
|
||||
aria-label={t('gitView.pr.actions.sendCommentToAgentAria')}
|
||||
>
|
||||
<RiAiGenerate2 className="size-3.5" />
|
||||
Send to agent
|
||||
{t('gitView.pr.actions.sendToAgent')}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>Send this comment to agent</p></TooltipContent>
|
||||
<TooltipContent><p>{t('gitView.pr.actions.sendCommentToAgent')}</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="typography-micro text-muted-foreground">
|
||||
@@ -1955,7 +1957,7 @@ export const PullRequestSection: React.FC<{
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center text-muted-foreground py-8">No comments found.</div>
|
||||
<div className="text-center text-muted-foreground py-8">{t('gitView.pr.comments.empty')}</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user