feat(ui): issues tabs in GitLab MR and GitHub PR views
Add an Issues tab alongside the merge-request / pull-request content in the context panel's PR surface for both providers. The tab lists open issues lazily and expands a selected issue into an inline detail view (body, labels, assignees, comments) with a back action. Read-only: no issue create, update, or close actions. Adds 15 i18n keys across all 11 locales.
This commit is contained in:
@@ -5,6 +5,8 @@ import { Button } from '@/components/ui/button';
|
||||
import { ScrollShadow } from '@/components/ui/ScrollShadow';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { GitLabIssuesSection } from '@/components/views/git/GitLabIssuesSection';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
|
||||
import { useGitStatus, useGitStore } from '@/stores/useGitStore';
|
||||
@@ -83,6 +85,10 @@ export const GitLabMrView: React.FC = () => {
|
||||
setSettingsDialogOpen(true);
|
||||
}, [setSettingsDialogOpen, setSettingsPage]);
|
||||
|
||||
// Local tab selection between the merge-request and issues surfaces. Not
|
||||
// persisted: reopening the panel always lands on merge requests.
|
||||
const [activeTab, setActiveTab] = React.useState<'mr' | 'issues'>('mr');
|
||||
|
||||
// ---- Current-branch merge request --------------------------------------
|
||||
|
||||
const [branchMr, setBranchMr] = React.useState<GitLabMergeRequestSummary | null>(null);
|
||||
@@ -558,6 +564,23 @@ export const GitLabMrView: React.FC = () => {
|
||||
preventOverscroll
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex h-8 min-w-0">
|
||||
<SortableTabsStrip
|
||||
className="h-full"
|
||||
items={[
|
||||
{ id: 'mr', label: t('contextPanel.gitlabMr.tabs.mergeRequests') },
|
||||
{ id: 'issues', label: t('contextPanel.gitlabMr.tabs.issues') },
|
||||
]}
|
||||
activeId={activeTab}
|
||||
onSelect={(tabId) => setActiveTab(tabId as 'mr' | 'issues')}
|
||||
layoutMode="fit"
|
||||
variant="active-pill"
|
||||
activePillButtonClassName="h-7"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{activeTab === 'mr' ? (
|
||||
<>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="typography-ui-header font-semibold text-foreground">{t('contextPanel.gitlabMr.title')}</div>
|
||||
<div className="typography-micro text-muted-foreground">{t('contextPanel.gitlabMr.listSectionTitle')}</div>
|
||||
@@ -921,6 +944,10 @@ export const GitLabMrView: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
) : (
|
||||
<GitLabIssuesSection directory={currentDirectory} />
|
||||
)}
|
||||
</div>
|
||||
</ScrollableOverlay>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,9 @@ import type { GitRemote } from '@/lib/api/types';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { ScrollShadow } from '@/components/ui/ScrollShadow';
|
||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { PullRequestSection } from './git/PullRequestSection';
|
||||
import { GitHubIssuesSection } from './git/GitHubIssuesSection';
|
||||
import { deriveBaseBranch } from './git/baseBranch';
|
||||
|
||||
const normalizePath = (value?: string | null): string =>
|
||||
@@ -240,14 +242,23 @@ export const PullRequestView: React.FC = () => {
|
||||
worktreeMetadata?.createdFromBranch,
|
||||
]);
|
||||
|
||||
if (!currentDirectory || !currentBranch) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<Icon name="git-pull-request" className="h-12 w-12 text-muted-foreground/50" />
|
||||
<div className="typography-ui-header text-foreground">{t('gitView.pullRequest.title')}</div>
|
||||
<div className="max-w-sm typography-micro text-muted-foreground">{t('gitView.pullRequest.createHint')}</div>
|
||||
</div>
|
||||
);
|
||||
// Local tab selection between the pull-request and issues surfaces. Not
|
||||
// persisted: reopening the panel always lands on pull requests.
|
||||
const [activeTab, setActiveTab] = React.useState<'pr' | 'issues'>('pr');
|
||||
|
||||
// Empty state for the pull-request surface: returned full-height when there
|
||||
// is no effective directory, and shown inside the "Pull requests" tab when
|
||||
// the current branch has not resolved (issues need no branch, PRs do).
|
||||
const prEmptyState = (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<Icon name="git-pull-request" className="h-12 w-12 text-muted-foreground/50" />
|
||||
<div className="typography-ui-header text-foreground">{t('gitView.pullRequest.title')}</div>
|
||||
<div className="max-w-sm typography-micro text-muted-foreground">{t('gitView.pullRequest.createHint')}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!currentDirectory) {
|
||||
return prEmptyState;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -258,14 +269,39 @@ export const PullRequestView: React.FC = () => {
|
||||
disableHorizontal
|
||||
preventOverscroll
|
||||
>
|
||||
<PullRequestSection
|
||||
directory={currentDirectory}
|
||||
branch={currentBranch}
|
||||
baseBranch={baseBranch}
|
||||
trackingBranch={status?.tracking ?? undefined}
|
||||
remotes={remotes}
|
||||
remoteBranches={remoteBranches}
|
||||
/>
|
||||
<div className="flex h-full min-h-0 flex-col gap-4">
|
||||
<div className="flex h-8 min-w-0">
|
||||
<SortableTabsStrip
|
||||
className="h-full"
|
||||
items={[
|
||||
{ id: 'pr', label: t('gitView.pullRequest.tabs.pullRequests') },
|
||||
{ id: 'issues', label: t('gitView.pullRequest.tabs.issues') },
|
||||
]}
|
||||
activeId={activeTab}
|
||||
onSelect={(tabId) => setActiveTab(tabId as 'pr' | 'issues')}
|
||||
layoutMode="fit"
|
||||
variant="active-pill"
|
||||
activePillButtonClassName="h-7"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{activeTab === 'pr' ? (
|
||||
currentBranch ? (
|
||||
<PullRequestSection
|
||||
directory={currentDirectory}
|
||||
branch={currentBranch}
|
||||
baseBranch={baseBranch}
|
||||
trackingBranch={status?.tracking ?? undefined}
|
||||
remotes={remotes}
|
||||
remoteBranches={remoteBranches}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex min-h-0 flex-1 flex-col">{prEmptyState}</div>
|
||||
)
|
||||
) : (
|
||||
<GitHubIssuesSection directory={currentDirectory} />
|
||||
)}
|
||||
</div>
|
||||
</ScrollableOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { formatDateTimeForPreference } from '@/lib/timeFormat';
|
||||
import type {
|
||||
GitHubIssue,
|
||||
GitHubIssueComment,
|
||||
GitHubIssueSummary,
|
||||
GitHubRepoSelector,
|
||||
} from '@/lib/api/types';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
const issueStateColor = (state: string): string => {
|
||||
switch (state) {
|
||||
case 'closed':
|
||||
return 'var(--pr-closed)';
|
||||
default:
|
||||
return 'var(--pr-open)';
|
||||
}
|
||||
};
|
||||
|
||||
const issueLabelBadgeClass =
|
||||
'inline-flex items-center rounded border border-border/60 bg-surface-elevated px-1.5 py-px typography-micro text-foreground';
|
||||
|
||||
/**
|
||||
* Open GitHub issues for the context panel's pull-request view. List and detail
|
||||
* are fetched lazily: the parent only mounts this component while the Issues
|
||||
* tab is active. Read-only by design — no create, update, or close actions.
|
||||
*
|
||||
* The parent does not gate on GitHub auth state, so the connection state is
|
||||
* derived from the API results themselves (`connected === false` renders a
|
||||
* not-connected state with a settings CTA).
|
||||
*/
|
||||
export const GitHubIssuesSection: React.FC<{ directory: string }> = ({ directory }) => {
|
||||
const { t } = useI18n();
|
||||
const { github } = useRuntimeAPIs();
|
||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
||||
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
|
||||
|
||||
// ---- Open issues list ----------------------------------------------------
|
||||
|
||||
const [issues, setIssues] = React.useState<GitHubIssueSummary[]>([]);
|
||||
const [listPage, setListPage] = React.useState(1);
|
||||
const [listHasMore, setListHasMore] = React.useState(false);
|
||||
const [listLoading, setListLoading] = React.useState(false);
|
||||
const [listLoadingMore, setListLoadingMore] = React.useState(false);
|
||||
const [listError, setListError] = React.useState<string | null>(null);
|
||||
const [listNotConnected, setListNotConnected] = React.useState(false);
|
||||
const [retryToken, setRetryToken] = React.useState(0);
|
||||
|
||||
// ---- Selected issue detail ------------------------------------------------
|
||||
|
||||
const [selectedNumber, setSelectedNumber] = React.useState<number | null>(null);
|
||||
const [selectedSourceRepo, setSelectedSourceRepo] = React.useState<
|
||||
(GitHubRepoSelector & { source: string }) | null
|
||||
>(null);
|
||||
const [issue, setIssue] = React.useState<GitHubIssue | null>(null);
|
||||
const [comments, setComments] = React.useState<GitHubIssueComment[]>([]);
|
||||
const [detailLoading, setDetailLoading] = React.useState(false);
|
||||
const [detailError, setDetailError] = React.useState<string | null>(null);
|
||||
|
||||
const retry = React.useCallback(() => setRetryToken((value) => value + 1), []);
|
||||
|
||||
const openGitHubSettings = React.useCallback(() => {
|
||||
setSettingsPage('git');
|
||||
setSettingsDialogOpen(true);
|
||||
}, [setSettingsDialogOpen, setSettingsPage]);
|
||||
|
||||
// A different repository invalidates the previously loaded list and detail so
|
||||
// a stale repository's issues never leak into the new one.
|
||||
React.useEffect(() => {
|
||||
setIssues([]);
|
||||
setListPage(1);
|
||||
setListHasMore(false);
|
||||
setListLoading(false);
|
||||
setListLoadingMore(false);
|
||||
setListError(null);
|
||||
setListNotConnected(false);
|
||||
setSelectedNumber(null);
|
||||
setSelectedSourceRepo(null);
|
||||
setIssue(null);
|
||||
setComments([]);
|
||||
setDetailLoading(false);
|
||||
setDetailError(null);
|
||||
}, [directory]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!github?.issuesList) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setListLoading(true);
|
||||
setListError(null);
|
||||
setListNotConnected(false);
|
||||
void github
|
||||
.issuesList(directory, { page: 1 })
|
||||
.then((result) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
if (result.connected === false) {
|
||||
setListNotConnected(true);
|
||||
return;
|
||||
}
|
||||
setIssues(result.issues ?? []);
|
||||
setListPage(result.page ?? 1);
|
||||
setListHasMore(Boolean(result.hasMore));
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
setListError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) {
|
||||
setListLoading(false);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [directory, github, retryToken]);
|
||||
|
||||
const loadMore = React.useCallback(async () => {
|
||||
if (!github?.issuesList || listLoadingMore || listLoading || !listHasMore) {
|
||||
return;
|
||||
}
|
||||
setListLoadingMore(true);
|
||||
try {
|
||||
const next = await github.issuesList(directory, { page: listPage + 1 });
|
||||
if (next.connected === false) {
|
||||
setListNotConnected(true);
|
||||
return;
|
||||
}
|
||||
setIssues((previous) => [...previous, ...(next.issues ?? [])]);
|
||||
setListPage(next.page ?? listPage + 1);
|
||||
setListHasMore(Boolean(next.hasMore));
|
||||
} catch (error) {
|
||||
setListError(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
setListLoadingMore(false);
|
||||
}
|
||||
}, [directory, github, listHasMore, listLoading, listLoadingMore, listPage]);
|
||||
|
||||
// Selecting a row remembers the summary's sourceRepo too: the server route
|
||||
// resolves the repo from the directory, but cross-repo issues need the
|
||||
// explicit sourceRepo to fetch the issue and its comments.
|
||||
const selectIssue = React.useCallback((item: GitHubIssueSummary) => {
|
||||
setSelectedNumber(item.number);
|
||||
setSelectedSourceRepo(item.sourceRepo ?? null);
|
||||
}, []);
|
||||
|
||||
// Fetch the issue and its comments in parallel whenever a row is selected. A
|
||||
// cancelled flag keeps a stale selection from overwriting a newer one.
|
||||
React.useEffect(() => {
|
||||
if (selectedNumber === null || !github?.issueGet || !github.issueComments) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setDetailLoading(true);
|
||||
setDetailError(null);
|
||||
setIssue(null);
|
||||
setComments([]);
|
||||
const sourceRepo = selectedSourceRepo ?? null;
|
||||
void Promise.all([
|
||||
github.issueGet(directory, selectedNumber, { sourceRepo }),
|
||||
github.issueComments(directory, selectedNumber, { sourceRepo }),
|
||||
])
|
||||
.then(([issueResult, commentsResult]) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
if (issueResult.connected === false || commentsResult.connected === false) {
|
||||
setDetailError(t('gitView.pr.githubNotConnected'));
|
||||
return;
|
||||
}
|
||||
if (!issueResult.issue) {
|
||||
setDetailError(t('session.githubIssuePicker.error.issueNotFound'));
|
||||
return;
|
||||
}
|
||||
setIssue(issueResult.issue);
|
||||
setComments(commentsResult.comments ?? []);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
setDetailError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) {
|
||||
setDetailLoading(false);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [directory, github, selectedNumber, selectedSourceRepo, t]);
|
||||
|
||||
const backToIssues = React.useCallback(() => {
|
||||
setSelectedNumber(null);
|
||||
setSelectedSourceRepo(null);
|
||||
setIssue(null);
|
||||
setComments([]);
|
||||
setDetailLoading(false);
|
||||
setDetailError(null);
|
||||
}, []);
|
||||
|
||||
const formatTimestamp = React.useCallback((value?: string) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
const timestamp = Date.parse(value);
|
||||
if (!Number.isFinite(timestamp)) {
|
||||
return value;
|
||||
}
|
||||
return formatDateTimeForPreference(timestamp, timeFormatPreference, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}, [timeFormatPreference]);
|
||||
|
||||
if (selectedNumber !== null) {
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
<Button variant="ghost" size="sm" className="h-7 gap-1.5 px-2" onClick={backToIssues}>
|
||||
<Icon name="arrow-left" className="size-4" />
|
||||
{t('gitView.pullRequest.issues.detail.back')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{detailLoading ? (
|
||||
<div className="flex items-center gap-2 typography-micro text-muted-foreground">
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
{t('session.githubIssuePicker.loading.issues')}
|
||||
</div>
|
||||
) : detailError ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="typography-micro text-muted-foreground break-words">{detailError}</div>
|
||||
<Button variant="outline" size="sm" onClick={backToIssues} className="w-fit">
|
||||
{t('gitView.pullRequest.issues.detail.back')}
|
||||
</Button>
|
||||
</div>
|
||||
) : issue ? (
|
||||
<>
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="typography-ui-header font-semibold text-foreground break-words leading-snug">
|
||||
<span className="text-muted-foreground">#{issue.number}</span> {issue.title}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 typography-micro text-muted-foreground">
|
||||
<span className="inline-flex items-center gap-1" style={{ color: issueStateColor(issue.state) }}>
|
||||
<span className="size-1.5 rounded-full" style={{ backgroundColor: issueStateColor(issue.state) }} />
|
||||
</span>
|
||||
{issue.labels?.map((label) => (
|
||||
<span key={label.name} className={issueLabelBadgeClass}>{label.name}</span>
|
||||
))}
|
||||
</div>
|
||||
{issue.assignees && issue.assignees.length > 0 ? (
|
||||
<div className="typography-micro text-muted-foreground">
|
||||
{issue.assignees.map((assignee) => assignee.name?.trim() || assignee.login).join(', ')}
|
||||
</div>
|
||||
) : null}
|
||||
<Button variant="outline" size="sm" asChild className="h-7 w-fit gap-1.5 px-2">
|
||||
<a href={issue.url} target="_blank" rel="noopener noreferrer">
|
||||
<Icon name="external-link" className="size-4" />
|
||||
{t('gitView.pullRequest.issues.detail.openInGitHub')}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="typography-micro font-semibold text-foreground">{t('gitView.pr.field.description')}</div>
|
||||
{issue.body?.trim() ? (
|
||||
<SimpleMarkdownRenderer
|
||||
content={issue.body}
|
||||
className="typography-markdown-body min-w-0 text-muted-foreground break-words"
|
||||
enableFileReferences={false}
|
||||
/>
|
||||
) : (
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.noDescription')}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<div className="typography-micro font-semibold text-foreground">{t('gitView.pr.segment.comments')}</div>
|
||||
{comments.length > 0 ? (
|
||||
comments.map((comment) => (
|
||||
<div key={comment.id} className="flex min-w-0 flex-col gap-1 rounded-lg bg-surface-elevated px-3 py-2">
|
||||
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 typography-micro text-muted-foreground">
|
||||
<span className="text-foreground whitespace-nowrap">
|
||||
{comment.author?.name?.trim() || comment.author?.login || ''}
|
||||
</span>
|
||||
{comment.createdAt ? (
|
||||
<span className="whitespace-nowrap">{formatTimestamp(comment.createdAt)}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<SimpleMarkdownRenderer
|
||||
content={comment.body || ''}
|
||||
className="typography-markdown-body text-foreground break-words"
|
||||
enableFileReferences={false}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pullRequest.issues.detail.commentsEmpty')}</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="typography-ui-header font-semibold text-foreground">{t('gitView.pullRequest.issues.listSectionTitle')}</div>
|
||||
</div>
|
||||
|
||||
{!github?.issuesList ? (
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pullRequest.issues.empty')}</div>
|
||||
) : listNotConnected ? (
|
||||
<div className="flex flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<Icon name="github" className="h-12 w-12 text-muted-foreground/50" />
|
||||
<div className="typography-ui-header text-foreground">{t('gitView.pr.githubNotConnected')}</div>
|
||||
<Button variant="outline" size="sm" onClick={openGitHubSettings} className="w-fit">
|
||||
{t('gitView.pr.actions.openSettings')}
|
||||
</Button>
|
||||
</div>
|
||||
) : listLoading ? (
|
||||
<div className="flex items-center gap-2 typography-micro text-muted-foreground">
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
{t('session.githubIssuePicker.loading.issues')}
|
||||
</div>
|
||||
) : listError ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="typography-ui-label text-foreground">{t('gitView.pullRequest.issues.error.loadFailed')}</div>
|
||||
<div className="typography-micro text-muted-foreground break-words">{listError}</div>
|
||||
<Button variant="outline" size="sm" onClick={retry} className="w-fit">
|
||||
{t('contextPanel.preview.actions.retry')}
|
||||
</Button>
|
||||
</div>
|
||||
) : issues.length === 0 ? (
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pullRequest.issues.empty')}</div>
|
||||
) : (
|
||||
<div className="flex min-w-0 flex-col">
|
||||
{issues.map((item) => (
|
||||
<div
|
||||
key={item.number}
|
||||
className="group flex cursor-pointer items-center gap-2 rounded py-1.5 transition-colors hover:bg-interactive-hover/30"
|
||||
onClick={() => selectIssue(item)}
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="typography-small truncate text-foreground">
|
||||
<span className="mr-1 text-muted-foreground">#{item.number}</span>
|
||||
{item.title}
|
||||
</p>
|
||||
{item.labels && item.labels.length > 0 ? (
|
||||
<p className="mt-1 flex min-w-0 flex-wrap gap-1">
|
||||
{item.labels.map((label) => (
|
||||
<span key={label.name} className={issueLabelBadgeClass}>{label.name}</span>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
aria-label={t('gitView.pullRequest.issues.detail.openInGitHub')}
|
||||
className="hidden size-5 flex-shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground group-hover:flex"
|
||||
>
|
||||
<Icon name="external-link" className="size-4" />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{listHasMore ? (
|
||||
<div className="flex justify-center py-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => void loadMore()} disabled={listLoadingMore}>
|
||||
{listLoadingMore ? (
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
) : null}
|
||||
{t('session.githubIssuePicker.actions.loadMore')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,378 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { formatDateTimeForPreference } from '@/lib/timeFormat';
|
||||
import type { GitLabIssue, GitLabIssueComment, GitLabIssueSummary } from '@/lib/api/types';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
|
||||
const issueStateColor = (state: string): string => {
|
||||
switch (state) {
|
||||
case 'closed':
|
||||
return 'var(--pr-closed)';
|
||||
default:
|
||||
return 'var(--pr-open)';
|
||||
}
|
||||
};
|
||||
|
||||
const issueLabelBadgeClass =
|
||||
'inline-flex items-center rounded border border-border/60 bg-surface-elevated px-1.5 py-px typography-micro text-foreground';
|
||||
|
||||
/**
|
||||
* Open GitLab issues for the context panel's MR view. List and detail are
|
||||
* fetched lazily: the parent only mounts this component while the Issues tab
|
||||
* is active. Read-only by design — no create, update, or close actions.
|
||||
*/
|
||||
export const GitLabIssuesSection: React.FC<{ directory: string }> = ({ directory }) => {
|
||||
const { t } = useI18n();
|
||||
const { gitlab } = useRuntimeAPIs();
|
||||
const setSettingsDialogOpen = useUIStore((state) => state.setSettingsDialogOpen);
|
||||
const setSettingsPage = useUIStore((state) => state.setSettingsPage);
|
||||
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
|
||||
|
||||
// ---- Open issues list ----------------------------------------------------
|
||||
|
||||
const [issues, setIssues] = React.useState<GitLabIssueSummary[]>([]);
|
||||
const [listPage, setListPage] = React.useState(1);
|
||||
const [listHasMore, setListHasMore] = React.useState(false);
|
||||
const [listLoading, setListLoading] = React.useState(false);
|
||||
const [listLoadingMore, setListLoadingMore] = React.useState(false);
|
||||
const [listError, setListError] = React.useState<string | null>(null);
|
||||
const [listNotConnected, setListNotConnected] = React.useState(false);
|
||||
const [retryToken, setRetryToken] = React.useState(0);
|
||||
|
||||
// ---- Selected issue detail ------------------------------------------------
|
||||
|
||||
const [selectedNumber, setSelectedNumber] = React.useState<number | null>(null);
|
||||
const [issue, setIssue] = React.useState<GitLabIssue | null>(null);
|
||||
const [comments, setComments] = React.useState<GitLabIssueComment[]>([]);
|
||||
const [detailLoading, setDetailLoading] = React.useState(false);
|
||||
const [detailError, setDetailError] = React.useState<string | null>(null);
|
||||
|
||||
const retry = React.useCallback(() => setRetryToken((value) => value + 1), []);
|
||||
|
||||
const openGitLabSettings = React.useCallback(() => {
|
||||
setSettingsPage('git');
|
||||
setSettingsDialogOpen(true);
|
||||
}, [setSettingsDialogOpen, setSettingsPage]);
|
||||
|
||||
// A different repository invalidates the previously loaded list and detail so
|
||||
// a stale repository's issues never leak into the new one.
|
||||
React.useEffect(() => {
|
||||
setIssues([]);
|
||||
setListPage(1);
|
||||
setListHasMore(false);
|
||||
setListLoading(false);
|
||||
setListLoadingMore(false);
|
||||
setListError(null);
|
||||
setListNotConnected(false);
|
||||
setSelectedNumber(null);
|
||||
setIssue(null);
|
||||
setComments([]);
|
||||
setDetailLoading(false);
|
||||
setDetailError(null);
|
||||
}, [directory]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!gitlab?.issuesList) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setListLoading(true);
|
||||
setListError(null);
|
||||
setListNotConnected(false);
|
||||
void gitlab
|
||||
.issuesList(directory, { page: 1 })
|
||||
.then((result) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
if (result.connected === false) {
|
||||
setListNotConnected(true);
|
||||
return;
|
||||
}
|
||||
setIssues(result.issues ?? []);
|
||||
setListPage(result.page ?? 1);
|
||||
setListHasMore(Boolean(result.hasMore));
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
setListError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) {
|
||||
setListLoading(false);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [directory, gitlab, retryToken]);
|
||||
|
||||
const loadMore = React.useCallback(async () => {
|
||||
if (!gitlab?.issuesList || listLoadingMore || listLoading || !listHasMore) {
|
||||
return;
|
||||
}
|
||||
setListLoadingMore(true);
|
||||
try {
|
||||
const next = await gitlab.issuesList(directory, { page: listPage + 1 });
|
||||
if (next.connected === false) {
|
||||
setListNotConnected(true);
|
||||
return;
|
||||
}
|
||||
setIssues((previous) => [...previous, ...(next.issues ?? [])]);
|
||||
setListPage(next.page ?? listPage + 1);
|
||||
setListHasMore(Boolean(next.hasMore));
|
||||
} catch (error) {
|
||||
setListError(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
setListLoadingMore(false);
|
||||
}
|
||||
}, [directory, gitlab, listHasMore, listLoading, listLoadingMore, listPage]);
|
||||
|
||||
// Fetch the issue and its comments in parallel whenever a row is selected. A
|
||||
// cancelled flag keeps a stale selection from overwriting a newer one.
|
||||
React.useEffect(() => {
|
||||
if (selectedNumber === null || !gitlab?.issueGet || !gitlab.issueComments) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setDetailLoading(true);
|
||||
setDetailError(null);
|
||||
setIssue(null);
|
||||
setComments([]);
|
||||
void Promise.all([
|
||||
gitlab.issueGet(directory, selectedNumber),
|
||||
gitlab.issueComments(directory, selectedNumber),
|
||||
])
|
||||
.then(([issueResult, commentsResult]) => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
if (issueResult.connected === false || commentsResult.connected === false) {
|
||||
setDetailError(t('contextPanel.gitlabMr.error.notConnected'));
|
||||
return;
|
||||
}
|
||||
if (!issueResult.issue) {
|
||||
setDetailError(t('session.gitlabIssuePicker.error.issueNotFound'));
|
||||
return;
|
||||
}
|
||||
setIssue(issueResult.issue);
|
||||
setComments(commentsResult.comments ?? []);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!cancelled) {
|
||||
setDetailError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) {
|
||||
setDetailLoading(false);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [directory, gitlab, selectedNumber, t]);
|
||||
|
||||
const backToIssues = React.useCallback(() => {
|
||||
setSelectedNumber(null);
|
||||
setIssue(null);
|
||||
setComments([]);
|
||||
setDetailLoading(false);
|
||||
setDetailError(null);
|
||||
}, []);
|
||||
|
||||
const formatTimestamp = React.useCallback((value?: string) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
const timestamp = Date.parse(value);
|
||||
if (!Number.isFinite(timestamp)) {
|
||||
return value;
|
||||
}
|
||||
return formatDateTimeForPreference(timestamp, timeFormatPreference, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}, [timeFormatPreference]);
|
||||
|
||||
if (selectedNumber !== null) {
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1">
|
||||
<Button variant="ghost" size="sm" className="h-7 gap-1.5 px-2" onClick={backToIssues}>
|
||||
<Icon name="arrow-left" className="size-4" />
|
||||
{t('contextPanel.gitlabMr.issues.detail.back')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{detailLoading ? (
|
||||
<div className="flex items-center gap-2 typography-micro text-muted-foreground">
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
{t('contextPanel.gitlabMr.loading')}
|
||||
</div>
|
||||
) : detailError ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="typography-micro text-muted-foreground break-words">{detailError}</div>
|
||||
<Button variant="outline" size="sm" onClick={backToIssues} className="w-fit">
|
||||
{t('contextPanel.gitlabMr.issues.detail.back')}
|
||||
</Button>
|
||||
</div>
|
||||
) : issue ? (
|
||||
<>
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="typography-ui-header font-semibold text-foreground break-words leading-snug">
|
||||
<span className="text-muted-foreground">#{issue.number}</span> {issue.title}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 typography-micro text-muted-foreground">
|
||||
<span className="inline-flex items-center gap-1" style={{ color: issueStateColor(issue.state) }}>
|
||||
<span className="size-1.5 rounded-full" style={{ backgroundColor: issueStateColor(issue.state) }} />
|
||||
{issue.state === 'closed' ? t('contextPanel.gitlabMr.state.closed') : t('contextPanel.gitlabMr.state.opened')}
|
||||
</span>
|
||||
{issue.labels.map((label) => (
|
||||
<span key={label} className={issueLabelBadgeClass}>{label}</span>
|
||||
))}
|
||||
</div>
|
||||
{issue.assignees && issue.assignees.length > 0 ? (
|
||||
<div className="typography-micro text-muted-foreground">
|
||||
{issue.assignees.map((assignee) => assignee.name?.trim() || assignee.username).join(', ')}
|
||||
</div>
|
||||
) : null}
|
||||
<Button variant="outline" size="sm" asChild className="h-7 w-fit gap-1.5 px-2">
|
||||
<a href={issue.url} target="_blank" rel="noopener noreferrer">
|
||||
<Icon name="external-link" className="size-4" />
|
||||
{t('contextPanel.gitlabMr.openInGitLab')}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="typography-micro font-semibold text-foreground">{t('gitView.pr.field.description')}</div>
|
||||
{issue.body?.trim() ? (
|
||||
<SimpleMarkdownRenderer
|
||||
content={issue.body}
|
||||
className="typography-markdown-body min-w-0 text-muted-foreground break-words"
|
||||
enableFileReferences={false}
|
||||
/>
|
||||
) : (
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.noDescription')}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<div className="typography-micro font-semibold text-foreground">{t('gitView.pr.segment.comments')}</div>
|
||||
{comments.length > 0 ? (
|
||||
comments.map((comment) => (
|
||||
<div key={comment.id} className="flex min-w-0 flex-col gap-1 rounded-lg bg-surface-elevated px-3 py-2">
|
||||
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-0.5 typography-micro text-muted-foreground">
|
||||
<span className="text-foreground whitespace-nowrap">
|
||||
{comment.author?.name?.trim() || comment.author?.username || ''}
|
||||
</span>
|
||||
{comment.createdAt ? (
|
||||
<span className="whitespace-nowrap">{formatTimestamp(comment.createdAt)}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<SimpleMarkdownRenderer
|
||||
content={comment.body || ''}
|
||||
className="typography-markdown-body text-foreground break-words"
|
||||
enableFileReferences={false}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="typography-micro text-muted-foreground">{t('gitView.pr.comments.empty')}</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="typography-ui-header font-semibold text-foreground">{t('contextPanel.gitlabMr.issues.listSectionTitle')}</div>
|
||||
</div>
|
||||
|
||||
{!gitlab?.issuesList ? (
|
||||
<div className="typography-micro text-muted-foreground">{t('contextPanel.gitlabMr.issues.empty')}</div>
|
||||
) : listNotConnected ? (
|
||||
<div className="flex flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<Icon name="gitlab" className="h-12 w-12 text-muted-foreground/50" />
|
||||
<div className="typography-ui-header text-foreground">{t('contextPanel.gitlabMr.error.notConnected')}</div>
|
||||
<Button variant="outline" size="sm" onClick={openGitLabSettings} className="w-fit">
|
||||
{t('contextPanel.gitlabMr.actions.openSettings')}
|
||||
</Button>
|
||||
</div>
|
||||
) : listLoading ? (
|
||||
<div className="flex items-center gap-2 typography-micro text-muted-foreground">
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
{t('contextPanel.gitlabMr.loading')}
|
||||
</div>
|
||||
) : listError ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="typography-ui-label text-foreground">{t('contextPanel.gitlabMr.issues.error.loadFailed')}</div>
|
||||
<div className="typography-micro text-muted-foreground break-words">{listError}</div>
|
||||
<Button variant="outline" size="sm" onClick={retry} className="w-fit">
|
||||
{t('contextPanel.preview.actions.retry')}
|
||||
</Button>
|
||||
</div>
|
||||
) : issues.length === 0 ? (
|
||||
<div className="typography-micro text-muted-foreground">{t('contextPanel.gitlabMr.issues.empty')}</div>
|
||||
) : (
|
||||
<div className="flex min-w-0 flex-col">
|
||||
{issues.map((item) => (
|
||||
<div
|
||||
key={item.number}
|
||||
className="group flex cursor-pointer items-center gap-2 rounded py-1.5 transition-colors hover:bg-interactive-hover/30"
|
||||
onClick={() => setSelectedNumber(item.number)}
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="typography-small truncate text-foreground">
|
||||
<span className="mr-1 text-muted-foreground">#{item.number}</span>
|
||||
{item.title}
|
||||
</p>
|
||||
{item.labels.length > 0 ? (
|
||||
<p className="mt-1 flex min-w-0 flex-wrap gap-1">
|
||||
{item.labels.map((label) => (
|
||||
<span key={label} className={issueLabelBadgeClass}>{label}</span>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<a
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
aria-label={t('contextPanel.gitlabMr.openInGitLab')}
|
||||
className="hidden size-5 flex-shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground group-hover:flex"
|
||||
>
|
||||
<Icon name="external-link" className="size-4" />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{listHasMore ? (
|
||||
<div className="flex justify-center py-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => void loadMore()} disabled={listLoadingMore}>
|
||||
{listLoadingMore ? (
|
||||
<Icon name="loader-4" className="size-4 animate-spin" />
|
||||
) : null}
|
||||
{t('contextPanel.gitlabMr.loadMore')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -977,6 +977,14 @@ export const dict = {
|
||||
'gitView.pr.toast.updatePrFailed': 'Fehler beim Aktualisieren des Pull-Requests',
|
||||
'gitView.pullRequest.createHint': 'Erstelle und verwalte Pull-Requests aus diesem Branch.',
|
||||
'gitView.pullRequest.availableOnFeatureBranches': 'Verfügbar, wenn der aktuelle Branch einen Pull-Request öffnen kann.',
|
||||
'gitView.pullRequest.issues.detail.back': 'Zurück zu den Problemen',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': 'Keine Kommentare',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': 'Auf GitHub öffnen',
|
||||
'gitView.pullRequest.issues.empty': 'Keine offenen Probleme',
|
||||
'gitView.pullRequest.issues.error.loadFailed': 'Fehler beim Laden der Probleme',
|
||||
'gitView.pullRequest.issues.listSectionTitle': 'Offene Probleme',
|
||||
'gitView.pullRequest.tabs.issues': 'Probleme',
|
||||
'gitView.pullRequest.tabs.pullRequests': 'Pull-Requests',
|
||||
'gitView.pullRequest.title': 'Pull-Request',
|
||||
'gitView.tabs.worktree': 'Worktree',
|
||||
'gitView.toast.abortOperationFailed': 'Fehler beim Abbrechen der Operation',
|
||||
@@ -2985,6 +2993,8 @@ export const dict = {
|
||||
'contextPanel.editorEmpty.title': 'Kein Kontext ausgewählt',
|
||||
'contextPanel.editorEmpty.description': 'Wählen Sie etwas aus der Seitenleiste aus, um Kontext anzuzeigen.',
|
||||
'contextPanel.gitlabMr.title': 'Merge-Requests',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': 'Merge-Requests',
|
||||
'contextPanel.gitlabMr.tabs.issues': 'Probleme',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': 'Aktueller Zweig',
|
||||
'contextPanel.gitlabMr.openMrTitle': 'Offene Merge-Requests',
|
||||
'contextPanel.gitlabMr.listSectionTitle': 'In diesem Repository',
|
||||
@@ -3003,6 +3013,11 @@ export const dict = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab ist nicht verbunden',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': 'Kein aktives Projekt',
|
||||
'contextPanel.gitlabMr.actions.openSettings': 'Einstellungen öffnen',
|
||||
'contextPanel.gitlabMr.issues.detail.back': 'Zurück zu den Problemen',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': 'Keine Kommentare',
|
||||
'contextPanel.gitlabMr.issues.empty': 'Keine offenen Probleme',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': 'Fehler beim Laden der Probleme',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': 'Offene Probleme',
|
||||
'contextPanel.gitlabMr.createMr.title': 'Neuer Merge-Request',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': 'Quellbranch',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': 'Zielbranch',
|
||||
|
||||
@@ -1046,6 +1046,14 @@ export const dict = {
|
||||
'gitView.pr.toast.updatePrFailed': 'Failed to update pull request',
|
||||
'gitView.pullRequest.createHint': 'Create and manage pull requests from this branch.',
|
||||
'gitView.pullRequest.availableOnFeatureBranches': 'Available when the current branch can open a pull request.',
|
||||
'gitView.pullRequest.issues.detail.back': 'Back to issues',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': 'No comments',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': 'Open in GitHub',
|
||||
'gitView.pullRequest.issues.empty': 'No open issues',
|
||||
'gitView.pullRequest.issues.error.loadFailed': 'Failed to load issues',
|
||||
'gitView.pullRequest.issues.listSectionTitle': 'Open issues',
|
||||
'gitView.pullRequest.tabs.issues': 'Issues',
|
||||
'gitView.pullRequest.tabs.pullRequests': 'Pull requests',
|
||||
'gitView.pullRequest.title': 'Pull request',
|
||||
'gitView.tabs.worktree': 'Worktree',
|
||||
'gitView.toast.abortOperationFailed': 'Failed to abort operation',
|
||||
@@ -1122,6 +1130,8 @@ export const dict = {
|
||||
'contextPanel.editorEmpty.title': 'No file open',
|
||||
'contextPanel.editorEmpty.description': 'Pick a file from the tree to start editing.',
|
||||
'contextPanel.gitlabMr.title': 'Merge requests',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': 'Merge requests',
|
||||
'contextPanel.gitlabMr.tabs.issues': 'Issues',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': 'Current branch',
|
||||
'contextPanel.gitlabMr.openMrTitle': 'Open merge requests',
|
||||
'contextPanel.gitlabMr.listSectionTitle': 'In this repository',
|
||||
@@ -1140,6 +1150,11 @@ export const dict = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab is not connected',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': 'No active project',
|
||||
'contextPanel.gitlabMr.actions.openSettings': 'Open settings',
|
||||
'contextPanel.gitlabMr.issues.detail.back': 'Back to issues',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': 'No comments',
|
||||
'contextPanel.gitlabMr.issues.empty': 'No open issues',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': 'Failed to load issues',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': 'Open issues',
|
||||
'contextPanel.gitlabMr.createMr.title': 'New merge request',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': 'Source branch',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': 'Target branch',
|
||||
|
||||
@@ -1047,6 +1047,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
"gitView.pr.toast.updatePrFailed": "No se pudo actualizar la PR",
|
||||
"gitView.pullRequest.createHint": "Crea y gestiona PR desde esta rama.",
|
||||
"gitView.pullRequest.availableOnFeatureBranches": "Disponible cuando la rama actual puede abrir un PR.",
|
||||
"gitView.pullRequest.issues.detail.back": "Volver a issues",
|
||||
"gitView.pullRequest.issues.detail.commentsEmpty": "Sin comentarios",
|
||||
"gitView.pullRequest.issues.detail.openInGitHub": "Abrir en GitHub",
|
||||
"gitView.pullRequest.issues.empty": "No hay issues abiertos",
|
||||
"gitView.pullRequest.issues.error.loadFailed": "Error al cargar los issues",
|
||||
"gitView.pullRequest.issues.listSectionTitle": "Issues abiertos",
|
||||
"gitView.pullRequest.tabs.issues": "Issues",
|
||||
"gitView.pullRequest.tabs.pullRequests": "Pull requests",
|
||||
"gitView.pullRequest.title": "PR",
|
||||
"gitView.tabs.worktree": "Árbol de trabajo",
|
||||
"gitView.toast.abortOperationFailed": "No se pudo abortar la operación",
|
||||
@@ -1123,6 +1131,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextPanel.editorEmpty.title": "Ningún archivo abierto",
|
||||
"contextPanel.editorEmpty.description": "Elige un archivo del árbol para empezar a editar.",
|
||||
"contextPanel.gitlabMr.title": "Solicitudes de fusión",
|
||||
"contextPanel.gitlabMr.tabs.mergeRequests": "Solicitudes de fusión",
|
||||
"contextPanel.gitlabMr.tabs.issues": "Issues",
|
||||
"contextPanel.gitlabMr.branchSectionTitle": "Rama actual",
|
||||
"contextPanel.gitlabMr.openMrTitle": "Solicitudes de fusión abiertas",
|
||||
"contextPanel.gitlabMr.listSectionTitle": "En este repositorio",
|
||||
@@ -1141,6 +1151,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextPanel.gitlabMr.error.notConnected": "GitLab no está conectado",
|
||||
"contextPanel.gitlabMr.empty.noActiveProject": "Sin proyecto activo",
|
||||
"contextPanel.gitlabMr.actions.openSettings": "Abrir ajustes",
|
||||
"contextPanel.gitlabMr.issues.detail.back": "Volver a issues",
|
||||
"contextPanel.gitlabMr.issues.detail.commentsEmpty": "Sin comentarios",
|
||||
"contextPanel.gitlabMr.issues.empty": "No hay issues abiertos",
|
||||
"contextPanel.gitlabMr.issues.error.loadFailed": "Error al cargar los issues",
|
||||
"contextPanel.gitlabMr.issues.listSectionTitle": "Issues abiertos",
|
||||
"contextPanel.gitlabMr.createMr.title": "Nueva solicitud de fusión",
|
||||
"contextPanel.gitlabMr.createMr.sourceBranch": "Rama de origen",
|
||||
"contextPanel.gitlabMr.createMr.targetBranch": "Rama de destino",
|
||||
|
||||
@@ -866,6 +866,14 @@ export const dict = {
|
||||
'gitView.pr.toast.updatePrFailed': 'Échec de la mise à jour de la PR',
|
||||
'gitView.pullRequest.createHint': 'Créez et gérez les PR depuis cette branche.',
|
||||
'gitView.pullRequest.availableOnFeatureBranches': 'Disponible lorsque la branche actuelle peut ouvrir une PR.',
|
||||
'gitView.pullRequest.issues.detail.back': 'Retour aux problèmes',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': 'Aucun commentaire',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': 'Ouvrir dans GitHub',
|
||||
'gitView.pullRequest.issues.empty': 'Aucun problème ouvert',
|
||||
'gitView.pullRequest.issues.error.loadFailed': 'Échec du chargement des problèmes',
|
||||
'gitView.pullRequest.issues.listSectionTitle': 'Problèmes ouverts',
|
||||
'gitView.pullRequest.tabs.issues': 'Problèmes',
|
||||
'gitView.pullRequest.tabs.pullRequests': 'Pull requests',
|
||||
'gitView.pullRequest.title': 'PR',
|
||||
'gitView.tabs.worktree': 'Worktree',
|
||||
'gitView.toast.abortOperationFailed': 'Échec de l\'annulation de l\'opération',
|
||||
@@ -942,6 +950,8 @@ export const dict = {
|
||||
'contextPanel.editorEmpty.title': 'Aucun fichier ouvert',
|
||||
'contextPanel.editorEmpty.description': 'Choisissez un fichier dans l’arborescence pour commencer.',
|
||||
'contextPanel.gitlabMr.title': 'Demandes de fusion',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': 'Demandes de fusion',
|
||||
'contextPanel.gitlabMr.tabs.issues': 'Problèmes',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': 'Branche actuelle',
|
||||
'contextPanel.gitlabMr.openMrTitle': 'Demandes de fusion ouvertes',
|
||||
'contextPanel.gitlabMr.listSectionTitle': 'Dans ce dépôt',
|
||||
@@ -960,6 +970,11 @@ export const dict = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab n\'est pas connecté',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': 'Aucun projet actif',
|
||||
'contextPanel.gitlabMr.actions.openSettings': 'Ouvrir les paramètres',
|
||||
'contextPanel.gitlabMr.issues.detail.back': 'Retour aux problèmes',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': 'Aucun commentaire',
|
||||
'contextPanel.gitlabMr.issues.empty': 'Aucun problème ouvert',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': 'Échec du chargement des problèmes',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': 'Problèmes ouverts',
|
||||
'contextPanel.gitlabMr.createMr.title': 'Nouvelle demande de fusion',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': 'Branche source',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': 'Branche cible',
|
||||
|
||||
@@ -1043,6 +1043,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'gitView.pr.toast.updatePrFailed': 'プルリクエストの更新に失敗しました',
|
||||
'gitView.pullRequest.createHint': 'このブランチからプルリクエストを作成・管理します。',
|
||||
'gitView.pullRequest.availableOnFeatureBranches': '現在のブランチがプルリクエストを開ける場合に利用可能です。',
|
||||
'gitView.pullRequest.issues.detail.back': 'Issueに戻る',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': 'コメントはありません',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': 'GitHubで開く',
|
||||
'gitView.pullRequest.issues.empty': '開いているIssueはありません',
|
||||
'gitView.pullRequest.issues.error.loadFailed': 'Issueの読み込みに失敗しました',
|
||||
'gitView.pullRequest.issues.listSectionTitle': '開いているIssue',
|
||||
'gitView.pullRequest.tabs.issues': 'Issue',
|
||||
'gitView.pullRequest.tabs.pullRequests': 'プルリクエスト',
|
||||
'gitView.pullRequest.title': 'プルリクエスト',
|
||||
'gitView.tabs.worktree': 'ワークツリー',
|
||||
'gitView.toast.abortOperationFailed': '操作の中止に失敗しました',
|
||||
@@ -1119,6 +1127,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.editorEmpty.title': 'ファイルが開かれていません',
|
||||
'contextPanel.editorEmpty.description': 'ツリーからファイルを選んで編集を始めましょう。',
|
||||
'contextPanel.gitlabMr.title': 'マージリクエスト',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': 'マージリクエスト',
|
||||
'contextPanel.gitlabMr.tabs.issues': 'Issue',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': '現在のブランチ',
|
||||
'contextPanel.gitlabMr.openMrTitle': '開いているマージリクエスト',
|
||||
'contextPanel.gitlabMr.listSectionTitle': 'このリポジトリ内',
|
||||
@@ -1137,6 +1147,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab に接続されていません',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': 'アクティブなプロジェクトがありません',
|
||||
'contextPanel.gitlabMr.actions.openSettings': '設定を開く',
|
||||
'contextPanel.gitlabMr.issues.detail.back': 'Issueに戻る',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': 'コメントはありません',
|
||||
'contextPanel.gitlabMr.issues.empty': '開いているIssueはありません',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': 'Issueの読み込みに失敗しました',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': '開いているIssue',
|
||||
'contextPanel.gitlabMr.createMr.title': '新しいマージリクエスト',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': 'ソースブランチ',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': 'ターゲットブランチ',
|
||||
|
||||
@@ -1047,6 +1047,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'gitView.pr.toast.updatePrFailed': 'PR 업데이트 실패',
|
||||
'gitView.pullRequest.createHint': '이 브랜치에서 PR을 만들고 관리하세요.',
|
||||
'gitView.pullRequest.availableOnFeatureBranches': '현재 브랜치에서 PR을 열 수 있을 때 사용할 수 있습니다.',
|
||||
'gitView.pullRequest.issues.detail.back': '이슈로 돌아가기',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': '댓글 없음',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': 'GitHub에서 열기',
|
||||
'gitView.pullRequest.issues.empty': '열린 이슈가 없습니다',
|
||||
'gitView.pullRequest.issues.error.loadFailed': '이슈를 불러오지 못했습니다',
|
||||
'gitView.pullRequest.issues.listSectionTitle': '열린 이슈',
|
||||
'gitView.pullRequest.tabs.issues': '이슈',
|
||||
'gitView.pullRequest.tabs.pullRequests': '풀 리퀘스트',
|
||||
'gitView.pullRequest.title': 'PR',
|
||||
'gitView.tabs.worktree': '워크트리',
|
||||
'gitView.toast.abortOperationFailed': '작업 중단 실패',
|
||||
@@ -1123,6 +1131,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.editorEmpty.title': '열린 파일 없음',
|
||||
'contextPanel.editorEmpty.description': '트리에서 파일을 선택해 편집을 시작하세요.',
|
||||
'contextPanel.gitlabMr.title': '병합 요청',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': '병합 요청',
|
||||
'contextPanel.gitlabMr.tabs.issues': '이슈',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': '현재 브랜치',
|
||||
'contextPanel.gitlabMr.openMrTitle': '열린 병합 요청',
|
||||
'contextPanel.gitlabMr.listSectionTitle': '이 저장소',
|
||||
@@ -1141,6 +1151,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab에 연결되지 않았습니다',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': '활성 프로젝트가 없습니다',
|
||||
'contextPanel.gitlabMr.actions.openSettings': '설정 열기',
|
||||
'contextPanel.gitlabMr.issues.detail.back': '이슈로 돌아가기',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': '댓글 없음',
|
||||
'contextPanel.gitlabMr.issues.empty': '열린 이슈가 없습니다',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': '이슈를 불러오지 못했습니다',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': '열린 이슈',
|
||||
'contextPanel.gitlabMr.createMr.title': '새 병합 요청',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': '소스 브랜치',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': '대상 브랜치',
|
||||
|
||||
@@ -1459,6 +1459,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.editorEmpty.title': 'Brak otwartego pliku',
|
||||
'contextPanel.editorEmpty.description': 'Wybierz plik z drzewa, aby rozpocząć edycję.',
|
||||
'contextPanel.gitlabMr.title': 'Żądania scalenia',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': 'Żądania scalenia',
|
||||
'contextPanel.gitlabMr.tabs.issues': 'Zgłoszenia',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': 'Bieżąca gałąź',
|
||||
'contextPanel.gitlabMr.openMrTitle': 'Otwarte żądania scalenia',
|
||||
'contextPanel.gitlabMr.listSectionTitle': 'W tym repozytorium',
|
||||
@@ -1477,6 +1479,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab nie jest połączony',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': 'Brak aktywnego projektu',
|
||||
'contextPanel.gitlabMr.actions.openSettings': 'Otwórz ustawienia',
|
||||
'contextPanel.gitlabMr.issues.detail.back': 'Wróć do zgłoszeń',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': 'Brak komentarzy',
|
||||
'contextPanel.gitlabMr.issues.empty': 'Brak otwartych zgłoszeń',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': 'Nie udało się załadować zgłoszeń',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': 'Otwarte zgłoszenia',
|
||||
'contextPanel.gitlabMr.createMr.title': 'Nowe żądanie scalenia',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': 'Gałąź źródłowa',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': 'Gałąź docelowa',
|
||||
@@ -2305,6 +2312,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'gitView.pr.toast.titleRequired': 'Tytuł jest wymagany',
|
||||
'gitView.pr.toast.updatePrFailed': 'Nie udało się zaktualizować pull requesta',
|
||||
'gitView.pullRequest.createHint': 'Utwórz i zarządzaj pull requestami z tej gałęzi.',
|
||||
'gitView.pullRequest.issues.detail.back': 'Wróć do zgłoszeń',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': 'Brak komentarzy',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': 'Otwórz w GitHub',
|
||||
'gitView.pullRequest.issues.empty': 'Brak otwartych zgłoszeń',
|
||||
'gitView.pullRequest.issues.error.loadFailed': 'Nie udało się załadować zgłoszeń',
|
||||
'gitView.pullRequest.issues.listSectionTitle': 'Otwarte zgłoszenia',
|
||||
'gitView.pullRequest.tabs.issues': 'Zgłoszenia',
|
||||
'gitView.pullRequest.tabs.pullRequests': 'Pull requesty',
|
||||
'gitView.pullRequest.title': 'Pull request',
|
||||
'gitView.stash.confirmButton': 'Potwierdź',
|
||||
'gitView.stash.description': 'Opis',
|
||||
|
||||
@@ -1047,6 +1047,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
"gitView.pr.toast.updatePrFailed": "Não foi possível atualizar a PR",
|
||||
"gitView.pullRequest.createHint": "Crie e gerencie PRs desta branch.",
|
||||
"gitView.pullRequest.availableOnFeatureBranches": "Disponível quando a branch atual pode abrir uma PR.",
|
||||
"gitView.pullRequest.issues.detail.back": "Voltar para issues",
|
||||
"gitView.pullRequest.issues.detail.commentsEmpty": "Sem comentários",
|
||||
"gitView.pullRequest.issues.detail.openInGitHub": "Abrir no GitHub",
|
||||
"gitView.pullRequest.issues.empty": "Nenhuma issue aberta",
|
||||
"gitView.pullRequest.issues.error.loadFailed": "Falha ao carregar issues",
|
||||
"gitView.pullRequest.issues.listSectionTitle": "Issues abertas",
|
||||
"gitView.pullRequest.tabs.issues": "Issues",
|
||||
"gitView.pullRequest.tabs.pullRequests": "Pull requests",
|
||||
"gitView.pullRequest.title": "PR",
|
||||
"gitView.tabs.worktree": "Worktree",
|
||||
"gitView.toast.abortOperationFailed": "Não foi possível abortar a operación",
|
||||
@@ -1123,6 +1131,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextPanel.editorEmpty.title": "Nenhum arquivo aberto",
|
||||
"contextPanel.editorEmpty.description": "Escolha um arquivo na árvore para começar a editar.",
|
||||
"contextPanel.gitlabMr.title": "Solicitações de merge",
|
||||
"contextPanel.gitlabMr.tabs.mergeRequests": "Solicitações de merge",
|
||||
"contextPanel.gitlabMr.tabs.issues": "Issues",
|
||||
"contextPanel.gitlabMr.branchSectionTitle": "Branch atual",
|
||||
"contextPanel.gitlabMr.openMrTitle": "Solicitações de merge abertas",
|
||||
"contextPanel.gitlabMr.listSectionTitle": "Neste repositório",
|
||||
@@ -1141,6 +1151,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextPanel.gitlabMr.error.notConnected": "GitLab não está conectado",
|
||||
"contextPanel.gitlabMr.empty.noActiveProject": "Nenhum projeto ativo",
|
||||
"contextPanel.gitlabMr.actions.openSettings": "Abrir configurações",
|
||||
"contextPanel.gitlabMr.issues.detail.back": "Voltar para issues",
|
||||
"contextPanel.gitlabMr.issues.detail.commentsEmpty": "Sem comentários",
|
||||
"contextPanel.gitlabMr.issues.empty": "Nenhuma issue aberta",
|
||||
"contextPanel.gitlabMr.issues.error.loadFailed": "Falha ao carregar issues",
|
||||
"contextPanel.gitlabMr.issues.listSectionTitle": "Issues abertas",
|
||||
"contextPanel.gitlabMr.createMr.title": "Nova solicitação de merge",
|
||||
"contextPanel.gitlabMr.createMr.sourceBranch": "Branch de origem",
|
||||
"contextPanel.gitlabMr.createMr.targetBranch": "Branch de destino",
|
||||
|
||||
@@ -1047,6 +1047,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
"gitView.pr.toast.updatePrFailed": "Не вдалося оновити PR",
|
||||
"gitView.pullRequest.createHint": "Створюйте PR з цієї гілки та керуйте ними.",
|
||||
"gitView.pullRequest.availableOnFeatureBranches": "Доступно, коли поточна гілка може відкрити PR.",
|
||||
"gitView.pullRequest.issues.detail.back": "Назад до issue",
|
||||
"gitView.pullRequest.issues.detail.commentsEmpty": "Немає коментарів",
|
||||
"gitView.pullRequest.issues.detail.openInGitHub": "Відкрити в GitHub",
|
||||
"gitView.pullRequest.issues.empty": "Немає відкритих issue",
|
||||
"gitView.pullRequest.issues.error.loadFailed": "Не вдалося завантажити issue",
|
||||
"gitView.pullRequest.issues.listSectionTitle": "Відкриті issue",
|
||||
"gitView.pullRequest.tabs.issues": "Issue",
|
||||
"gitView.pullRequest.tabs.pullRequests": "Pull requests",
|
||||
"gitView.pullRequest.title": "PR",
|
||||
"gitView.tabs.worktree": "Worktree",
|
||||
"gitView.toast.abortOperationFailed": "Не вдалося перервати операцію",
|
||||
@@ -1123,6 +1131,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextPanel.editorEmpty.title": "Файл не відкрито",
|
||||
"contextPanel.editorEmpty.description": "Виберіть файл у дереві, щоб почати редагування.",
|
||||
"contextPanel.gitlabMr.title": "Запити на злиття",
|
||||
"contextPanel.gitlabMr.tabs.mergeRequests": "Запити на злиття",
|
||||
"contextPanel.gitlabMr.tabs.issues": "Issue",
|
||||
"contextPanel.gitlabMr.branchSectionTitle": "Поточна гілка",
|
||||
"contextPanel.gitlabMr.openMrTitle": "Відкриті запити на злиття",
|
||||
"contextPanel.gitlabMr.listSectionTitle": "У цьому репозиторії",
|
||||
@@ -1141,6 +1151,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
"contextPanel.gitlabMr.error.notConnected": "GitLab не підключено",
|
||||
"contextPanel.gitlabMr.empty.noActiveProject": "Немає активного проєкту",
|
||||
"contextPanel.gitlabMr.actions.openSettings": "Відкрити налаштування",
|
||||
"contextPanel.gitlabMr.issues.detail.back": "Назад до issue",
|
||||
"contextPanel.gitlabMr.issues.detail.commentsEmpty": "Немає коментарів",
|
||||
"contextPanel.gitlabMr.issues.empty": "Немає відкритих issue",
|
||||
"contextPanel.gitlabMr.issues.error.loadFailed": "Не вдалося завантажити issue",
|
||||
"contextPanel.gitlabMr.issues.listSectionTitle": "Відкриті issue",
|
||||
"contextPanel.gitlabMr.createMr.title": "Новий запит на злиття",
|
||||
"contextPanel.gitlabMr.createMr.sourceBranch": "Вихідна гілка",
|
||||
"contextPanel.gitlabMr.createMr.targetBranch": "Цільова гілка",
|
||||
|
||||
@@ -1047,6 +1047,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'gitView.pr.toast.updatePrFailed': '更新拉取请求失败',
|
||||
'gitView.pullRequest.createHint': '从当前分支创建并管理拉取请求。',
|
||||
'gitView.pullRequest.availableOnFeatureBranches': '当前分支可以打开 PR 时可用。',
|
||||
'gitView.pullRequest.issues.detail.back': '返回 Issue 列表',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': '暂无评论',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': '在 GitHub 中打开',
|
||||
'gitView.pullRequest.issues.empty': '没有打开的 Issue',
|
||||
'gitView.pullRequest.issues.error.loadFailed': '加载 Issue 失败',
|
||||
'gitView.pullRequest.issues.listSectionTitle': '打开的 Issue',
|
||||
'gitView.pullRequest.tabs.issues': 'Issue',
|
||||
'gitView.pullRequest.tabs.pullRequests': '拉取请求',
|
||||
'gitView.pullRequest.title': '拉取请求',
|
||||
'gitView.tabs.worktree': '工作树',
|
||||
'gitView.toast.abortOperationFailed': '中止操作失败',
|
||||
@@ -1123,6 +1131,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.editorEmpty.title': '未打开文件',
|
||||
'contextPanel.editorEmpty.description': '从文件树中选择一个文件开始编辑。',
|
||||
'contextPanel.gitlabMr.title': '合并请求',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': '合并请求',
|
||||
'contextPanel.gitlabMr.tabs.issues': 'Issue',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': '当前分支',
|
||||
'contextPanel.gitlabMr.openMrTitle': '打开的合并请求',
|
||||
'contextPanel.gitlabMr.listSectionTitle': '在此仓库中',
|
||||
@@ -1141,6 +1151,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab 未连接',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': '没有活动的项目',
|
||||
'contextPanel.gitlabMr.actions.openSettings': '打开设置',
|
||||
'contextPanel.gitlabMr.issues.detail.back': '返回 Issue 列表',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': '暂无评论',
|
||||
'contextPanel.gitlabMr.issues.empty': '没有打开的 Issue',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': '加载 Issue 失败',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': '打开的 Issue',
|
||||
'contextPanel.gitlabMr.createMr.title': '新建合并请求',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': '源分支',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': '目标分支',
|
||||
|
||||
@@ -1059,6 +1059,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'gitView.pr.toast.updatePrFailed': '更新 Pull Request 失敗',
|
||||
'gitView.pullRequest.createHint': '從目前分支建立並管理 Pull Request。',
|
||||
'gitView.pullRequest.availableOnFeatureBranches': '目前分支可以開啟 PR 時可用。',
|
||||
'gitView.pullRequest.issues.detail.back': '返回 Issue 列表',
|
||||
'gitView.pullRequest.issues.detail.commentsEmpty': '沒有留言',
|
||||
'gitView.pullRequest.issues.detail.openInGitHub': '在 GitHub 中開啟',
|
||||
'gitView.pullRequest.issues.empty': '沒有開啟的 Issue',
|
||||
'gitView.pullRequest.issues.error.loadFailed': '載入 Issue 失敗',
|
||||
'gitView.pullRequest.issues.listSectionTitle': '開啟的 Issue',
|
||||
'gitView.pullRequest.tabs.issues': 'Issue',
|
||||
'gitView.pullRequest.tabs.pullRequests': 'Pull Request',
|
||||
'gitView.pullRequest.title': 'Pull Request',
|
||||
'gitView.tabs.worktree': 'Worktree',
|
||||
'gitView.toast.abortOperationFailed': '中止操作失敗',
|
||||
@@ -1135,6 +1143,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.editorEmpty.title': '未開啟檔案',
|
||||
'contextPanel.editorEmpty.description': '從檔案樹選擇檔案開始編輯。',
|
||||
'contextPanel.gitlabMr.title': '合併請求',
|
||||
'contextPanel.gitlabMr.tabs.mergeRequests': '合併請求',
|
||||
'contextPanel.gitlabMr.tabs.issues': 'Issue',
|
||||
'contextPanel.gitlabMr.branchSectionTitle': '目前分支',
|
||||
'contextPanel.gitlabMr.openMrTitle': '已開啟的合併請求',
|
||||
'contextPanel.gitlabMr.listSectionTitle': '在此存放庫中',
|
||||
@@ -1153,6 +1163,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
'contextPanel.gitlabMr.error.notConnected': 'GitLab 未連線',
|
||||
'contextPanel.gitlabMr.empty.noActiveProject': '沒有使用中的專案',
|
||||
'contextPanel.gitlabMr.actions.openSettings': '開啟設定',
|
||||
'contextPanel.gitlabMr.issues.detail.back': '返回 Issue 列表',
|
||||
'contextPanel.gitlabMr.issues.detail.commentsEmpty': '沒有留言',
|
||||
'contextPanel.gitlabMr.issues.empty': '沒有開啟的 Issue',
|
||||
'contextPanel.gitlabMr.issues.error.loadFailed': '載入 Issue 失敗',
|
||||
'contextPanel.gitlabMr.issues.listSectionTitle': '開啟的 Issue',
|
||||
'contextPanel.gitlabMr.createMr.title': '新增合併請求',
|
||||
'contextPanel.gitlabMr.createMr.sourceBranch': '來源分支',
|
||||
'contextPanel.gitlabMr.createMr.targetBranch': '目標分支',
|
||||
|
||||
Reference in New Issue
Block a user