From bebaefe1f2ed4e7d16cfb7d33c2e92a37acb51f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sondre=20Engebr=C3=A5ten?= Date: Fri, 13 Feb 2026 09:49:12 +0100 Subject: [PATCH] fix(triage): VGAP-03..07 replace hardcoded strings with i18n in 5 components BulkActionBar: action labels, selected count, processing text EmptyStates: search empty, not connected, configure token, open settings IssueListHeader: title, open count, analyze/auto-fix labels, tooltips, filters LabelManager: add label, filter, no match AssigneeManager: assign, search, no match All 28+ hardcoded strings now use t() with keys in en + fr common.json. Updated BulkActionBar test expectations to match i18n keys. Co-Authored-By: Claude Opus 4.6 --- .../components/AssigneeManager.tsx | 8 ++- .../components/BulkActionBar.tsx | 24 +++---- .../github-issues/components/EmptyStates.tsx | 11 +-- .../components/IssueListHeader.tsx | 22 +++--- .../github-issues/components/LabelManager.tsx | 8 ++- .../__tests__/BulkActionBar.test.tsx | 12 ++-- .../src/shared/i18n/locales/en/common.json | 30 +++++++- .../src/shared/i18n/locales/fr/common.json | 30 +++++++- docs/verification-gap-tracker.md | 71 +++++++++---------- 9 files changed, 135 insertions(+), 81 deletions(-) diff --git a/apps/frontend/src/renderer/components/github-issues/components/AssigneeManager.tsx b/apps/frontend/src/renderer/components/github-issues/components/AssigneeManager.tsx index 7b6d62b6..382fe15a 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/AssigneeManager.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/AssigneeManager.tsx @@ -1,4 +1,5 @@ import { useState, useRef, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { Plus, X, Check } from 'lucide-react'; import { Button } from '../../ui/button'; @@ -17,6 +18,7 @@ export function AssigneeManager({ onRemoveAssignee, disabled, }: AssigneeManagerProps) { + const { t } = useTranslation('common'); const [dropdownOpen, setDropdownOpen] = useState(false); const [search, setSearch] = useState(''); const dropdownRef = useRef(null); @@ -85,7 +87,7 @@ export function AssigneeManager({ aria-label="Assign" > - Assign + {t('assignees.assign')} {/* Dropdown */} @@ -95,7 +97,7 @@ export function AssigneeManager({ type="text" value={search} onChange={(e) => setSearch(e.target.value)} - placeholder="Search collaborators..." + placeholder={t('assignees.search')} className="w-full px-2 py-1 text-xs border-b border-border bg-transparent focus:outline-none" aria-label="Search collaborators" /> @@ -121,7 +123,7 @@ export function AssigneeManager({ })} {filteredCollaborators.length === 0 && (
- No matching collaborators + {t('assignees.noMatch')}
)} diff --git a/apps/frontend/src/renderer/components/github-issues/components/BulkActionBar.tsx b/apps/frontend/src/renderer/components/github-issues/components/BulkActionBar.tsx index 5fe8dc33..07733081 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/BulkActionBar.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/BulkActionBar.tsx @@ -14,14 +14,14 @@ interface BulkActionBarProps { onDeselectAll?: () => void; } -const BULK_ACTIONS: Array<{ action: BulkActionType; label: string }> = [ - { action: 'close', label: 'Close' }, - { action: 'reopen', label: 'Reopen' }, - { action: 'add-label', label: 'Add Label' }, - { action: 'remove-label', label: 'Remove Label' }, - { action: 'add-assignee', label: 'Assign' }, - { action: 'remove-assignee', label: 'Unassign' }, - { action: 'transition', label: 'Transition' }, +const BULK_ACTIONS: Array<{ action: BulkActionType; labelKey: string }> = [ + { action: 'close', labelKey: 'bulk.actionClose' }, + { action: 'reopen', labelKey: 'bulk.actionReopen' }, + { action: 'add-label', labelKey: 'bulk.actionAddLabel' }, + { action: 'remove-label', labelKey: 'bulk.actionRemoveLabel' }, + { action: 'add-assignee', labelKey: 'bulk.actionAssign' }, + { action: 'remove-assignee', labelKey: 'bulk.actionUnassign' }, + { action: 'transition', labelKey: 'bulk.actionTransition' }, ]; export function BulkActionBar({ @@ -60,7 +60,7 @@ export function BulkActionBar({ className="flex items-center gap-2 rounded-md border border-border bg-muted/50 px-3 py-2" > - {selectedCount} selected + {t('bulk.selected', { count: selectedCount })} {onSelectAll && ( @@ -106,7 +106,7 @@ export function BulkActionBar({ ) : (
- {BULK_ACTIONS.map(({ action, label }) => ( + {BULK_ACTIONS.map(({ action, labelKey }) => ( ))}
@@ -159,7 +159,7 @@ export function BulkActionBar({ {isOperating && progress && ( - Processing {progress.processedItems}/{progress.totalItems}... + {t('bulk.processing', { current: progress.processedItems, total: progress.totalItems })} )} diff --git a/apps/frontend/src/renderer/components/github-issues/components/EmptyStates.tsx b/apps/frontend/src/renderer/components/github-issues/components/EmptyStates.tsx index ca2f182e..9fedae1f 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/EmptyStates.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/EmptyStates.tsx @@ -1,36 +1,39 @@ +import { useTranslation } from 'react-i18next'; import { Github, Settings2 } from 'lucide-react'; 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 (

- {searchQuery ? 'No issues match your search' : message} + {searchQuery ? t('issues.emptySearch') : message}

); } export function NotConnectedState({ error, onOpenSettings }: NotConnectedStateProps) { + const { t } = useTranslation('common'); return (

- GitHub Not Connected + {t('issues.notConnected')}

- {error || 'Configure your GitHub token and repository in project settings to sync issues.'} + {error || t('issues.configureToken')}

{onOpenSettings && ( )}
diff --git a/apps/frontend/src/renderer/components/github-issues/components/IssueListHeader.tsx b/apps/frontend/src/renderer/components/github-issues/components/IssueListHeader.tsx index fa61da6e..5d28cdc0 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/IssueListHeader.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/IssueListHeader.tsx @@ -54,7 +54,7 @@ export function IssueListHeader({

- GitHub Issues + {t('issues.title')}

{repoFullName} @@ -63,7 +63,7 @@ export function IssueListHeader({

- {openIssuesCount} open + {t('issues.openCount', { count: openIssuesCount })} {onToggleTriageMode && ( @@ -117,11 +117,11 @@ export function IssueListHeader({ ) : ( )} - Analyze & Group Issues + {t('issues.analyzeGroup')} -

Analyze up to 200 open issues, group similar ones, and review proposed batches before creating tasks.

+

{t('issues.analyzeGroupTooltip')}

@@ -140,7 +140,7 @@ export function IssueListHeader({ )} -

Automatically fix new issues as they come in.

+

{t('issues.autoFixTooltip')}

{autoFixRunning && autoFixProcessing !== undefined && autoFixProcessing > 0 && ( -

Processing {autoFixProcessing} issue{autoFixProcessing > 1 ? 's' : ''}...

+

{t('issues.autoFixProcessing', { count: autoFixProcessing })}

)}
@@ -167,7 +167,7 @@ export function IssueListHeader({
onSearchChange(e.target.value)} className="pl-9" @@ -179,9 +179,9 @@ export function IssueListHeader({ - Open - Closed - All + {t('issues.filterOpen')} + {t('issues.filterClosed')} + {t('issues.filterAll')} {onWorkflowFilterChange && ( diff --git a/apps/frontend/src/renderer/components/github-issues/components/LabelManager.tsx b/apps/frontend/src/renderer/components/github-issues/components/LabelManager.tsx index ed12ed83..5c542388 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/LabelManager.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/LabelManager.tsx @@ -1,4 +1,5 @@ import { useState, useRef, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { Plus, X, Check } from 'lucide-react'; import { Button } from '../../ui/button'; import { Badge } from '../../ui/badge'; @@ -22,6 +23,7 @@ export function LabelManager({ disabled, isLoading, }: LabelManagerProps) { + const { t } = useTranslation('common'); const [dropdownOpen, setDropdownOpen] = useState(false); const [search, setSearch] = useState(''); const dropdownRef = useRef(null); @@ -89,7 +91,7 @@ export function LabelManager({ aria-label="Add label" > - Add Label + {t('labels.add')} {/* Dropdown */} @@ -99,7 +101,7 @@ export function LabelManager({ type="text" value={search} onChange={(e) => setSearch(e.target.value)} - placeholder="Filter labels..." + placeholder={t('labels.filter')} className="w-full px-2 py-1 text-xs border-b border-border bg-transparent focus:outline-none" aria-label="Filter labels" /> @@ -129,7 +131,7 @@ export function LabelManager({ })} {filteredLabels.length === 0 && (
- No matching labels + {t('labels.noMatch')}
)}
diff --git a/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkActionBar.test.tsx b/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkActionBar.test.tsx index 1913c9f8..7b98c9c5 100644 --- a/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkActionBar.test.tsx +++ b/apps/frontend/src/renderer/components/github-issues/components/__tests__/BulkActionBar.test.tsx @@ -35,7 +35,7 @@ describe('BulkActionBar', () => { isOperating={false} />, ); - expect(screen.getByText('5 selected')).toBeDefined(); + expect(screen.getByText('bulk.selected')).toBeDefined(); }); it('Close button shows confirmation, confirm fires onBulkAction', () => { @@ -48,7 +48,7 @@ describe('BulkActionBar', () => { />, ); // Click Close — should show confirm prompt, NOT fire immediately - fireEvent.click(screen.getByText('Close')); + fireEvent.click(screen.getByText('bulk.actionClose')); expect(onBulkAction).not.toHaveBeenCalled(); expect(screen.getByText('bulk.confirmMessage')).toBeDefined(); // Click Confirm — should fire onBulkAction @@ -65,13 +65,13 @@ describe('BulkActionBar', () => { isOperating={false} />, ); - fireEvent.click(screen.getByText('Close')); + fireEvent.click(screen.getByText('bulk.actionClose')); expect(screen.getByText('bulk.confirmMessage')).toBeDefined(); // Click Cancel fireEvent.click(screen.getByText('bulk.cancel')); expect(onBulkAction).not.toHaveBeenCalled(); // Action buttons should be back - expect(screen.getByText('Close')).toBeDefined(); + expect(screen.getByText('bulk.actionClose')).toBeDefined(); }); it('confirmation dialog has role=alert', () => { @@ -82,7 +82,7 @@ describe('BulkActionBar', () => { isOperating={false} />, ); - fireEvent.click(screen.getByText('Close')); + fireEvent.click(screen.getByText('bulk.actionClose')); expect(screen.getByRole('alert')).toBeDefined(); }); @@ -142,7 +142,7 @@ describe('BulkActionBar', () => { }} />, ); - expect(screen.getByText('Processing 2/5...')).toBeDefined(); + expect(screen.getByText('bulk.processing')).toBeDefined(); }); it('renders Select All button when onSelectAll provided', () => { diff --git a/apps/frontend/src/shared/i18n/locales/en/common.json b/apps/frontend/src/shared/i18n/locales/en/common.json index e07386e7..b74b33a8 100644 --- a/apps/frontend/src/shared/i18n/locales/en/common.json +++ b/apps/frontend/src/shared/i18n/locales/en/common.json @@ -441,7 +441,23 @@ "loadingMore": "Loading more...", "scrollForMore": "Scroll for more", "allLoaded": "All issues loaded", - "listLabel": "Issues" + "listLabel": "Issues", + "title": "GitHub Issues", + "openCount": "{{count}} open", + "searchPlaceholder": "Search issues...", + "filterOpen": "Open", + "filterClosed": "Closed", + "filterAll": "All", + "analyzeGroup": "Analyze & Group Issues", + "analyzeGroupTooltip": "Analyze up to 200 open issues, group similar ones, and review proposed batches before creating tasks.", + "autoFixNew": "Auto-Fix New", + "autoFixTooltip": "Automatically fix new issues as they come in.", + "autoFixProcessing": "Processing {{count}} issue(s)...", + "emptySearch": "No issues match your search", + "emptyList": "No issues found", + "notConnected": "GitHub Not Connected", + "configureToken": "Configure your GitHub token and repository in project settings to sync issues.", + "openSettings": "Open Settings" }, "enrichment": { "states": { @@ -509,13 +525,16 @@ "add": "Add label", "remove": "Remove label", "search": "Search labels...", + "filter": "Filter labels...", + "noMatch": "No matching labels", "limit": "Maximum of {{limit}} labels reached" }, "assignees": { "manage": "Manage assignees", "assign": "Assign", "unassign": "Unassign", - "search": "Search collaborators..." + "search": "Search collaborators...", + "noMatch": "No matching collaborators" }, "bulk": { "selectAll": "Select all", @@ -526,6 +545,13 @@ "retryFailed": "Retry {{count}} failed", "dismiss": "Dismiss", "actions": "Bulk actions", + "actionClose": "Close", + "actionReopen": "Reopen", + "actionAddLabel": "Add Label", + "actionRemoveLabel": "Remove Label", + "actionAssign": "Assign", + "actionUnassign": "Unassign", + "actionTransition": "Transition", "confirmMessage": "{{action}} {{count}} issues?", "confirm": "Confirm", "cancel": "Cancel" diff --git a/apps/frontend/src/shared/i18n/locales/fr/common.json b/apps/frontend/src/shared/i18n/locales/fr/common.json index ae865173..ef70060d 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/common.json +++ b/apps/frontend/src/shared/i18n/locales/fr/common.json @@ -441,7 +441,23 @@ "loadingMore": "Chargement...", "scrollForMore": "Défiler pour plus", "allLoaded": "Toutes les issues chargées", - "listLabel": "Issues" + "listLabel": "Issues", + "title": "Issues GitHub", + "openCount": "{{count}} ouvertes", + "searchPlaceholder": "Rechercher des issues...", + "filterOpen": "Ouvertes", + "filterClosed": "Fermées", + "filterAll": "Toutes", + "analyzeGroup": "Analyser et regrouper", + "analyzeGroupTooltip": "Analyser jusqu'à 200 issues ouvertes, regrouper les similaires et vérifier les lots proposés avant de créer des tâches.", + "autoFixNew": "Auto-fix nouvelles", + "autoFixTooltip": "Corriger automatiquement les nouvelles issues.", + "autoFixProcessing": "Traitement de {{count}} issue(s)...", + "emptySearch": "Aucune issue ne correspond à votre recherche", + "emptyList": "Aucune issue trouvée", + "notConnected": "GitHub non connecté", + "configureToken": "Configurez votre token GitHub et le dépôt dans les paramètres du projet pour synchroniser les issues.", + "openSettings": "Ouvrir les paramètres" }, "enrichment": { "states": { @@ -509,13 +525,16 @@ "add": "Ajouter un label", "remove": "Supprimer le label", "search": "Rechercher des labels...", + "filter": "Filtrer les labels...", + "noMatch": "Aucun label correspondant", "limit": "Maximum de {{limit}} labels atteint" }, "assignees": { "manage": "Gérer les assignés", "assign": "Assigner", "unassign": "Désassigner", - "search": "Rechercher des collaborateurs..." + "search": "Rechercher des collaborateurs...", + "noMatch": "Aucun collaborateur correspondant" }, "bulk": { "selectAll": "Tout sélectionner", @@ -526,6 +545,13 @@ "retryFailed": "Réessayer {{count}} échoués", "dismiss": "Fermer", "actions": "Actions groupées", + "actionClose": "Fermer", + "actionReopen": "Rouvrir", + "actionAddLabel": "Ajouter un label", + "actionRemoveLabel": "Supprimer un label", + "actionAssign": "Assigner", + "actionUnassign": "Désassigner", + "actionTransition": "Transition", "confirmMessage": "{{action}} {{count}} issues ?", "confirm": "Confirmer", "cancel": "Annuler" diff --git a/docs/verification-gap-tracker.md b/docs/verification-gap-tracker.md index b1d5ea9a..2f014eb1 100644 --- a/docs/verification-gap-tracker.md +++ b/docs/verification-gap-tracker.md @@ -3,7 +3,7 @@ **Branch:** `terminal/enhancement-issues-tab` **Created:** 2026-02-13 **Total Gaps:** 46 confirmed (from 9-agent triple-verified audit) -**Status:** 2 / 17 complete +**Status:** 7 / 17 complete --- @@ -61,74 +61,69 @@ Each gap has: ID, description, status, files to modify, verification source, tes ## TIER 2 — i18n Hardcoded Strings ### VGAP-03: BulkActionBar.tsx hardcoded action labels (8 strings) -- **Status:** `PENDING` +- **Status:** `DONE` - **Priority:** SHOULD-FIX - **Scope:** Medium - **Verified by:** i18n agent + Verifier-2 (CONFIRMED) - **Doc ref:** CLAUDE.md > i18n required -- **Files to modify:** `renderer/components/github-issues/components/BulkActionBar.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` -- **Problem:** `BULK_ACTIONS` constant (lines 17-25) has hardcoded labels: 'Close', 'Reopen', 'Add Label', 'Remove Label', 'Assign', 'Unassign', 'Transition'. Line 63 has hardcoded `{selectedCount} selected`. Line 162 has hardcoded `Processing X/Y...` -- **Fix:** Add `useTranslation` hook, add i18n keys `bulk.actions.close`, `bulk.actions.reopen`, etc. Replace hardcoded text with `t()` calls. Add keys to both en + fr. -- **Tests:** Verify translated keys render (existing tests may need i18n mock update) -- **Test status:** — +- **Files modified:** `renderer/components/github-issues/components/BulkActionBar.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` +- **Fix:** Changed `BULK_ACTIONS` constant to use `labelKey` instead of `label`, rendering via `t(labelKey)`. Replaced `{selectedCount} selected` with `t('bulk.selected', { count })`. Replaced `Processing X/Y...` with `t('bulk.processing', { current, total })`. Updated test expectations to match i18n keys. +- **Tests:** 3860 pass, lint clean +- **Test status:** `PASS` - **Depends on:** None -- **Commit:** — +- **Commit:** VGAP-03..07 ### VGAP-04: EmptyStates.tsx hardcoded strings (4 strings) -- **Status:** `PENDING` +- **Status:** `DONE` - **Priority:** SHOULD-FIX - **Scope:** Small - **Verified by:** i18n agent + Verifier-2 (CONFIRMED) - **Doc ref:** CLAUDE.md > i18n required -- **Files to modify:** `renderer/components/github-issues/components/EmptyStates.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` -- **Problem:** Line 12: 'No issues match your search'. Line 25: 'GitHub Not Connected'. Line 28: 'Configure your GitHub token...'. Line 33: 'Open Settings'. -- **Fix:** Add `useTranslation` hook, add i18n keys `issues.emptySearch`, `issues.notConnected`, `issues.configureToken`, `issues.openSettings`. Replace with `t()`. Add to both en + fr. -- **Tests:** Verify translated keys render -- **Test status:** — +- **Files modified:** `renderer/components/github-issues/components/EmptyStates.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` +- **Fix:** Added `useTranslation('common')` to both EmptyState and NotConnectedState. Replaced 4 hardcoded strings with `t('issues.emptySearch')`, `t('issues.notConnected')`, `t('issues.configureToken')`, `t('issues.openSettings')`. +- **Tests:** 3860 pass, lint clean +- **Test status:** `PASS` - **Depends on:** None -- **Commit:** — +- **Commit:** VGAP-03..07 ### VGAP-05: IssueListHeader.tsx hardcoded strings (9+ strings) -- **Status:** `PENDING` +- **Status:** `DONE` - **Priority:** SHOULD-FIX - **Scope:** Medium - **Verified by:** i18n agent + Verifier-2 (CONFIRMED) - **Doc ref:** CLAUDE.md > i18n required -- **Files to modify:** `renderer/components/github-issues/components/IssueListHeader.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` -- **Problem:** Line 57: 'GitHub Issues'. Line 66: '{N} open'. Line 120: 'Analyze & Group Issues'. Line 124: tooltip text. Line 143: 'Auto-Fix New'. Line 154: tooltip. Line 156: 'Processing...'. Line 170: 'Search issues...'. Lines 182-184: 'Open', 'Closed', 'All'. -- **Fix:** Add i18n keys for all strings. Use `t()` with interpolation for counts. Add to both en + fr. -- **Tests:** Verify translated keys render -- **Test status:** — +- **Files modified:** `renderer/components/github-issues/components/IssueListHeader.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` +- **Fix:** Replaced 10 hardcoded strings with `t()` calls: title, openCount (with interpolation), analyzeGroup, analyzeGroupTooltip, autoFixNew, autoFixTooltip, autoFixProcessing (with interpolation), searchPlaceholder, filterOpen/Closed/All. +- **Tests:** 3860 pass, lint clean +- **Test status:** `PASS` - **Depends on:** None -- **Commit:** — +- **Commit:** VGAP-03..07 ### VGAP-06: LabelManager.tsx hardcoded strings (3 strings) -- **Status:** `PENDING` +- **Status:** `DONE` - **Priority:** SHOULD-FIX - **Scope:** Small - **Verified by:** i18n agent + Verifier-2 (CONFIRMED) - **Doc ref:** CLAUDE.md > i18n required -- **Files to modify:** `renderer/components/github-issues/components/LabelManager.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` -- **Problem:** Line 92: 'Add Label'. Line 102: 'Filter labels...'. Line 132: 'No matching labels'. Keys exist in common.json (`labels.add`, etc.) but component doesn't import or use `useTranslation`. -- **Fix:** Import `useTranslation`, replace hardcoded strings with `t('common:labels.add')`, `t('common:labels.filter')`, `t('common:labels.noMatch')`. Add any missing keys to both en + fr. -- **Tests:** Verify translated keys render -- **Test status:** — +- **Files modified:** `renderer/components/github-issues/components/LabelManager.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` +- **Fix:** Added `useTranslation('common')`. Replaced `'Add Label'` → `t('labels.add')`, `'Filter labels...'` → `t('labels.filter')`, `'No matching labels'` → `t('labels.noMatch')`. +- **Tests:** 3860 pass, lint clean +- **Test status:** `PASS` - **Depends on:** None -- **Commit:** — +- **Commit:** VGAP-03..07 ### VGAP-07: AssigneeManager.tsx hardcoded strings (3 strings) -- **Status:** `PENDING` +- **Status:** `DONE` - **Priority:** SHOULD-FIX - **Scope:** Small - **Verified by:** i18n agent + Verifier-2 (CONFIRMED) - **Doc ref:** CLAUDE.md > i18n required -- **Files to modify:** `renderer/components/github-issues/components/AssigneeManager.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` -- **Problem:** Line 88: 'Assign'. Line 98: 'Search collaborators...'. Line 124: 'No matching collaborators'. Keys exist in common.json (`assignees.assign`, etc.) but component doesn't use `useTranslation`. -- **Fix:** Import `useTranslation`, replace hardcoded strings with `t()` calls using existing keys. Add any missing keys to both en + fr. -- **Tests:** Verify translated keys render -- **Test status:** — +- **Files modified:** `renderer/components/github-issues/components/AssigneeManager.tsx`, `shared/i18n/locales/en/common.json`, `shared/i18n/locales/fr/common.json` +- **Fix:** Added `useTranslation('common')`. Replaced `'Assign'` → `t('assignees.assign')`, `'Search collaborators...'` → `t('assignees.search')`, `'No matching collaborators'` → `t('assignees.noMatch')`. +- **Tests:** 3860 pass, lint clean +- **Test status:** `PASS` - **Depends on:** None -- **Commit:** — +- **Commit:** VGAP-03..07 --- @@ -289,10 +284,10 @@ Each gap has: ID, description, status, files to modify, verification source, tes | Tier | Description | Total | Done | Remaining | |------|-------------|-------|------|-----------| | 1 | Critical Wiring | 2 | 2 | 0 | -| 2 | i18n Hardcoded Strings | 5 | 0 | 5 | +| 2 | i18n Hardcoded Strings | 5 | 5 | 0 | | 3 | Accessibility Keyboard | 2 | 0 | 2 | | 4 | IPC Consistency | 3 | 0 | 3 | | 5 | Phase 3 Audit Gaps | 5 | 0 | 5 | -| **Total** | | **17** | **0** | **17** | +| **Total** | | **17** | **7** | **10** | Note: VGAP-03 through VGAP-07 contain 28+ individual hardcoded strings grouped by component file. The 17 gap count represents work units (one per component/file), not individual string count.