Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/frontend/src/renderer/components/GitHubIssues.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useCallback, useMemo, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useProjectStore } from "../stores/project-store";
import { useTaskStore } from "../stores/task-store";
import {
Expand All @@ -22,6 +23,7 @@ import type { GitHubIssue } from "../../shared/types";
import type { GitHubIssuesProps } from "./github-issues/types";

export function GitHubIssues({ onOpenSettings, onNavigateToTask }: GitHubIssuesProps) {
const { t } = useTranslation();
const projects = useProjectStore((state) => state.projects);
const selectedProjectId = useProjectStore((state) => state.selectedProjectId);
const selectedProject = projects.find((p) => p.id === selectedProjectId);
Expand Down Expand Up @@ -197,7 +199,7 @@ export function GitHubIssues({ onOpenSettings, onNavigateToTask }: GitHubIssuesP
autoFixQueueItem={getAutoFixQueueItem(selectedIssue.number)}
/>
) : (
<EmptyState message="Select an issue to view details" />
<EmptyState message={t('common:issues.selectIssue')} />
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
import { Github, Settings2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../ui/button';
import type { EmptyStateProps, NotConnectedStateProps } from '../types';

export function EmptyState({ searchQuery, icon: Icon = Github, message }: EmptyStateProps) {
const { t } = useTranslation('common');
return (
<div className="flex-1 flex flex-col items-center justify-center p-8 text-center">
<div className="w-12 h-12 rounded-full bg-muted/50 flex items-center justify-center mb-3">
<Icon className="h-6 w-6 text-muted-foreground" />
</div>
<p className="text-sm text-muted-foreground">
{searchQuery ? 'No issues match your search' : message}
{searchQuery ? t('issues.noMatch') : message}
</p>
</div>
);
}

export function NotConnectedState({ error, onOpenSettings }: NotConnectedStateProps) {
const { t } = useTranslation('common');
return (
<div className="flex-1 flex flex-col items-center justify-center p-8 text-center">
<div className="w-16 h-16 rounded-full bg-muted/50 flex items-center justify-center mb-4">
<Github className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="text-lg font-semibold text-foreground mb-2">
GitHub Not Connected
{t('issues.notConnectedTitle')}
</h3>
<p className="text-sm text-muted-foreground mb-4 max-w-md">
{error || 'Configure your GitHub token and repository in project settings to sync issues.'}
{error || t('issues.notConnectedDescription')}
</p>
{onOpenSettings && (
<Button onClick={onOpenSettings} variant="outline">
<Settings2 className="h-4 w-4 mr-2" />
Open Settings
{t('issues.openSettings')}
</Button>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Sparkles, Loader2, CheckCircle2, MessageCircle } from 'lucide-react';
import { Button } from '../../ui/button';
import { Progress } from '../../ui/progress';
Expand Down Expand Up @@ -32,6 +33,7 @@ export function InvestigationDialog({
onClose,
projectId
}: InvestigationDialogProps) {
const { t } = useTranslation('common');
const [comments, setComments] = useState<GitHubComment[]>([]);
const [selectedCommentIds, setSelectedCommentIds] = useState<number[]>([]);
const [loadingComments, setLoadingComments] = useState(false);
Expand Down Expand Up @@ -60,7 +62,7 @@ export function InvestigationDialog({
if (!isMounted) return;
console.error('Failed to fetch comments:', err);
setFetchCommentsError(
err instanceof Error ? err.message : 'Failed to load comments'
err instanceof Error ? err.message : t('issues.investigation.failedToLoadComments')
);
})
.finally(() => {
Expand Down Expand Up @@ -101,12 +103,12 @@ export function InvestigationDialog({
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-info" />
Create Task from Issue
{t('issues.investigation.title')}
</DialogTitle>
<DialogDescription>
{selectedIssue && (
<span>
Issue #{selectedIssue.number}: {selectedIssue.title}
{t('issues.investigation.subtitle', { number: selectedIssue.number, title: selectedIssue.title })}
</span>
)}
</DialogDescription>
Expand All @@ -115,7 +117,7 @@ export function InvestigationDialog({
{investigationStatus.phase === 'idle' ? (
<div className="space-y-4 flex-1 min-h-0 flex flex-col">
<p className="text-sm text-muted-foreground">
Create a task from this GitHub issue. The task will be added to your Kanban board in the Backlog column.
{t('issues.investigation.description')}
</p>

{/* Comments section */}
Expand All @@ -125,23 +127,23 @@ export function InvestigationDialog({
</div>
) : fetchCommentsError ? (
<div className="rounded-lg bg-destructive/10 border border-destructive/30 p-4">
<p className="text-sm text-destructive font-medium">Failed to load comments</p>
<p className="text-sm text-destructive font-medium">{t('issues.investigation.failedToLoadComments')}</p>
<p className="text-xs text-destructive/80 mt-1">{fetchCommentsError}</p>
</div>
) : comments.length > 0 ? (
<div className="space-y-2 flex-1 min-h-0 flex flex-col">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium flex items-center gap-2">
<MessageCircle className="h-4 w-4" />
Select Comments to Include ({selectedCommentIds.length}/{comments.length})
{t('issues.investigation.selectComments', { selected: selectedCommentIds.length, total: comments.length })}
</h4>
<Button
variant="ghost"
size="sm"
onClick={toggleAllComments}
className="text-xs"
>
{selectedCommentIds.length === comments.length ? 'Deselect All' : 'Select All'}
{selectedCommentIds.length === comments.length ? t('issues.investigation.deselectAll') : t('issues.investigation.selectAll')}
</Button>
</div>
<ScrollArea
Expand Down Expand Up @@ -177,12 +179,12 @@ export function InvestigationDialog({
</div>
) : (
<div className="rounded-lg border border-border bg-muted/30 p-4">
<h4 className="text-sm font-medium mb-2">The task will include:</h4>
<h4 className="text-sm font-medium mb-2">{t('issues.investigation.willInclude')}</h4>
<ul className="text-sm text-muted-foreground space-y-1">
<li>• Issue title and description</li>
<li>• Link back to the GitHub issue</li>
<li>• Labels and metadata from the issue</li>
<li>• No comments (this issue has no comments)</li>
<li>• {t('issues.investigation.includeTitle')}</li>
<li>• {t('issues.investigation.includeLink')}</li>
<li>• {t('issues.investigation.includeLabels')}</li>
<li>• {t('issues.investigation.noComments')}</li>
</ul>
</div>
)}
Expand All @@ -206,7 +208,7 @@ export function InvestigationDialog({
{investigationStatus.phase === 'complete' && (
<div className="rounded-lg bg-success/10 border border-success/30 p-3 flex items-center gap-2 text-sm text-success">
<CheckCircle2 className="h-4 w-4" />
Task created! View it in your Kanban board.
{t('issues.investigation.taskCreated')}
</div>
)}
</div>
Expand All @@ -216,23 +218,23 @@ export function InvestigationDialog({
{investigationStatus.phase === 'idle' && (
<>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
{t('buttons.cancel')}
</Button>
<Button onClick={handleStartInvestigation}>
<Sparkles className="h-4 w-4 mr-2" />
Create Task
{t('issues.createTask')}
</Button>
</>
)}
{investigationStatus.phase !== 'idle' && investigationStatus.phase !== 'complete' && (
<Button variant="outline" disabled>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Creating...
{t('issues.investigation.creating')}
</Button>
)}
{investigationStatus.phase === 'complete' && (
<Button onClick={onClose}>
Done
{t('selection.done')}
</Button>
)}
</DialogFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card';
import { ScrollArea } from '../../ui/scroll-area';
import {
GITHUB_ISSUE_STATE_COLORS,
GITHUB_ISSUE_STATE_LABELS,
GITHUB_COMPLEXITY_COLORS
} from '../../../../shared/constants';
import { formatDate } from '../utils';
Expand Down Expand Up @@ -47,7 +46,7 @@ export function IssueDetail({
variant="outline"
className={`${GITHUB_ISSUE_STATE_COLORS[issue.state]}`}
>
{GITHUB_ISSUE_STATE_LABELS[issue.state]}
{{ open: t('issues.stateOpen'), closed: t('issues.stateClosed') }[issue.state] ?? issue.state}
</Badge>
<span className="text-sm text-muted-foreground">#{issue.number}</span>
</div>
Expand Down Expand Up @@ -75,7 +74,7 @@ export function IssueDetail({
{issue.commentsCount > 0 && (
<div className="flex items-center gap-1">
<MessageCircle className="h-4 w-4" />
{issue.commentsCount} comments
{t('issues.commentsCount', { count: issue.commentsCount })}
</div>
)}
</div>
Expand Down Expand Up @@ -104,13 +103,13 @@ export function IssueDetail({
{hasLinkedTask ? (
<Button onClick={handleViewTask} className="flex-1" variant="secondary">
<Eye className="h-4 w-4 mr-2" />
View Task
{t('issues.viewTask')}
</Button>
) : (
<>
<Button onClick={onInvestigate} className="flex-1">
<Sparkles className="h-4 w-4 mr-2" />
Create Task
{t('issues.createTask')}
</Button>
{projectId && autoFixConfig?.enabled && (
<AutoFixButton
Expand All @@ -130,7 +129,7 @@ export function IssueDetail({
<CardHeader className="pb-2">
<CardTitle className="text-sm flex items-center gap-2 text-success">
<CheckCircle2 className="h-4 w-4" />
Task Linked
{t('issues.taskLinked')}
</CardTitle>
</CardHeader>
<CardContent className="text-sm space-y-2">
Expand All @@ -142,14 +141,14 @@ export function IssueDetail({
{investigationResult.analysis.estimatedComplexity}
</Badge>
<span className="text-xs text-muted-foreground">
Task ID: {taskId}
{t('issues.taskId')}: {taskId}
</span>
</div>
</>
) : (
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">
Task ID: {taskId}
{t('issues.taskId')}: {taskId}
</span>
Comment on lines 143 to 152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize the full task-ID phrase.

Lines 144 and 151 still hardcode the : {taskId} layout outside i18n, so the phrase structure cannot vary by locale. Move the whole string into a translation key with interpolation, e.g. t('issues.taskIdValue', { taskId }), and add that key to en/common.json, fr/common.json, and zh/common.json.

💡 Suggested change
-                      {t('issues.taskId')}: {taskId}
+                      {t('issues.taskIdValue', { taskId })}
-                    {t('issues.taskId')}: {taskId}
+                    {t('issues.taskIdValue', { taskId })}

As per coding guidelines, "All frontend user-facing text must use react-i18next translation keys. Never hardcode strings in JSX/TSX. Add keys to both en/*.json and fr/*.json translation files."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/frontend/src/renderer/components/github-issues/components/IssueDetail.tsx`
around lines 143 - 152, The JSX in IssueDetail.tsx currently concatenates a
colon and taskId outside i18n (uses {t('issues.taskId')}: {taskId}), preventing
locale-specific ordering; change both occurrences to use a single translation
with interpolation (e.g., t('issues.taskIdValue', { taskId })) and add the
corresponding key "issues.taskIdValue" to en/common.json, fr/common.json, and
zh/common.json (value examples: "Task ID: {{taskId}}", localized equivalents) so
all user-facing text uses react-i18next and supports locale-specific phrase
structure.

</div>
)}
Expand All @@ -160,7 +159,7 @@ export function IssueDetail({
{/* Body */}
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm">Description</CardTitle>
<CardTitle className="text-sm">{t('issues.description')}</CardTitle>
</CardHeader>
<CardContent>
{issue.body ? (
Expand All @@ -169,7 +168,7 @@ export function IssueDetail({
</div>
) : (
<p className="text-sm text-muted-foreground italic">
No description provided.
{t('issues.noDescription')}
</p>
)}
</CardContent>
Expand All @@ -179,7 +178,7 @@ export function IssueDetail({
{issue.assignees.length > 0 && (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm">Assignees</CardTitle>
<CardTitle className="text-sm">{t('issues.assignees')}</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
Expand All @@ -198,7 +197,7 @@ export function IssueDetail({
{issue.milestone && (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm">Milestone</CardTitle>
<CardTitle className="text-sm">{t('issues.milestone')}</CardTitle>
</CardHeader>
<CardContent>
<Badge variant="outline">{issue.milestone.title}</Badge>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function IssueList({
}

if (issues.length === 0) {
return <EmptyState message="No issues found" />;
return <EmptyState message={t('issues.noIssues')} />;
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function IssueListHeader({
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">
GitHub Issues
{t('issues.githubTitle')}
</h2>
<p className="text-xs text-muted-foreground">
{repoFullName}
Expand All @@ -56,7 +56,7 @@ export function IssueListHeader({
</div>
<div className="flex items-center gap-2">
<Badge variant="outline" className="text-xs">
{openIssuesCount} open
{t('issues.openCount', { count: openIssuesCount })}
</Badge>
<Button
variant="ghost"
Expand Down Expand Up @@ -89,11 +89,11 @@ export function IssueListHeader({
) : (
<Layers className="h-4 w-4 mr-2" />
)}
Analyze & Group Issues
{t('issues.analyzeGroup')}
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
<p>Analyze up to 200 open issues, group similar ones, and review proposed batches before creating tasks.</p>
<p>{t('issues.analyzeGroupTip')}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
Expand All @@ -112,7 +112,7 @@ export function IssueListHeader({
<Wand2 className="h-4 w-4 text-muted-foreground" />
)}
<Label htmlFor="auto-fix-toggle" className="text-sm cursor-pointer whitespace-nowrap">
Auto-Fix New
{t('issues.autoFixNew')}
</Label>
<Switch
id="auto-fix-toggle"
Expand All @@ -123,9 +123,9 @@ export function IssueListHeader({
</div>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-xs">
<p>Automatically fix new issues as they come in.</p>
<p>{t('issues.autoFixTip')}</p>
{autoFixRunning && autoFixProcessing !== undefined && autoFixProcessing > 0 && (
<p className="mt-1 text-primary">Processing {autoFixProcessing} issue{autoFixProcessing > 1 ? 's' : ''}...</p>
<p className="mt-1 text-primary">{t('issues.autoFixProcessing', { count: autoFixProcessing })}</p>
)}
</TooltipContent>
</Tooltip>
Expand All @@ -139,7 +139,7 @@ export function IssueListHeader({
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search issues..."
placeholder={t('issues.searchPlaceholder')}
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
className="pl-9"
Expand All @@ -151,9 +151,9 @@ export function IssueListHeader({
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
<SelectItem value="all">All</SelectItem>
<SelectItem value="open">{t('issues.filterOpen')}</SelectItem>
<SelectItem value="closed">{t('issues.filterClosed')}</SelectItem>
<SelectItem value="all">{t('issues.filterAll')}</SelectItem>
</SelectContent>
</Select>
</div>
Expand Down
Loading
Loading