feat: complete i18n translation for remaining components
- Translate pages: Projects, ProjectHealthPage, ProjectSettings - Translate components: TaskDetailModal, KanbanBoard, Comments, SubtaskList, CalendarView, BlockerDialog - Add translation keys for tasks namespace: kanban, calendar, subtasks.error, comments.error, blockers (full translation) - Add common.labels.task translation key - Fix task creation: use original_estimate instead of time_estimate Translation coverage: - 10 locale files updated (zh-TW & en) - 6 page/component files translated - ~100 new translation keys added 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import api from '../services/api'
|
||||
import { Comments } from './Comments'
|
||||
import { TaskAttachments } from './TaskAttachments'
|
||||
@@ -50,6 +51,7 @@ export function TaskDetailModal({
|
||||
onUpdate,
|
||||
onSubtaskClick,
|
||||
}: TaskDetailModalProps) {
|
||||
const { t } = useTranslation('tasks')
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [editForm, setEditForm] = useState({
|
||||
@@ -263,7 +265,7 @@ export function TaskDetailModal({
|
||||
<div style={styles.headerActions}>
|
||||
{!isEditing ? (
|
||||
<button onClick={() => setIsEditing(true)} style={styles.editButton}>
|
||||
Edit
|
||||
{t('common:buttons.edit')}
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
@@ -272,18 +274,18 @@ export function TaskDetailModal({
|
||||
style={styles.cancelButton}
|
||||
disabled={saving}
|
||||
>
|
||||
Cancel
|
||||
{t('common:buttons.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
style={styles.saveButton}
|
||||
disabled={saving || !editForm.title.trim()}
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save'}
|
||||
{saving ? t('common:labels.loading') : t('common:buttons.save')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button onClick={onClose} style={styles.closeButton} aria-label="Close">
|
||||
<button onClick={onClose} style={styles.closeButton} aria-label={t('common:buttons.close')}>
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
@@ -293,7 +295,7 @@ export function TaskDetailModal({
|
||||
<div style={styles.mainSection}>
|
||||
{/* Description */}
|
||||
<div style={styles.field}>
|
||||
<label style={styles.fieldLabel}>Description</label>
|
||||
<label style={styles.fieldLabel}>{t('fields.description')}</label>
|
||||
{isEditing ? (
|
||||
<textarea
|
||||
value={editForm.description}
|
||||
@@ -301,11 +303,11 @@ export function TaskDetailModal({
|
||||
setEditForm({ ...editForm, description: e.target.value })
|
||||
}
|
||||
style={styles.textarea}
|
||||
placeholder="Add a description..."
|
||||
placeholder={t('fields.descriptionPlaceholder')}
|
||||
/>
|
||||
) : (
|
||||
<div style={styles.descriptionText}>
|
||||
{task.description || 'No description'}
|
||||
{task.description || t('common:labels.noData')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -334,7 +336,7 @@ export function TaskDetailModal({
|
||||
<div style={styles.sidebar}>
|
||||
{/* Status */}
|
||||
<div style={styles.sidebarField}>
|
||||
<label style={styles.sidebarLabel}>Status</label>
|
||||
<label style={styles.sidebarLabel}>{t('fields.status')}</label>
|
||||
{isEditing ? (
|
||||
<select
|
||||
value={editForm.status_id}
|
||||
@@ -343,7 +345,7 @@ export function TaskDetailModal({
|
||||
}
|
||||
style={styles.select}
|
||||
>
|
||||
<option value="">No Status</option>
|
||||
<option value="">{t('status.noStatus')}</option>
|
||||
{statuses.map((status) => (
|
||||
<option key={status.id} value={status.id}>
|
||||
{status.name}
|
||||
@@ -357,14 +359,14 @@ export function TaskDetailModal({
|
||||
backgroundColor: task.status_color || '#e0e0e0',
|
||||
}}
|
||||
>
|
||||
{task.status_name || 'No Status'}
|
||||
{task.status_name || t('status.noStatus')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Priority */}
|
||||
<div style={styles.sidebarField}>
|
||||
<label style={styles.sidebarLabel}>Priority</label>
|
||||
<label style={styles.sidebarLabel}>{t('fields.priority')}</label>
|
||||
{isEditing ? (
|
||||
<select
|
||||
value={editForm.priority}
|
||||
@@ -373,10 +375,10 @@ export function TaskDetailModal({
|
||||
}
|
||||
style={styles.select}
|
||||
>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="urgent">Urgent</option>
|
||||
<option value="low">{t('priority.low')}</option>
|
||||
<option value="medium">{t('priority.medium')}</option>
|
||||
<option value="high">{t('priority.high')}</option>
|
||||
<option value="urgent">{t('priority.urgent')}</option>
|
||||
</select>
|
||||
) : (
|
||||
<div
|
||||
@@ -386,30 +388,30 @@ export function TaskDetailModal({
|
||||
color: getPriorityColor(task.priority),
|
||||
}}
|
||||
>
|
||||
{task.priority.charAt(0).toUpperCase() + task.priority.slice(1)}
|
||||
{t(`priority.${task.priority}`)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Assignee */}
|
||||
<div style={styles.sidebarField}>
|
||||
<label style={styles.sidebarLabel}>Assignee</label>
|
||||
<label style={styles.sidebarLabel}>{t('fields.assignee')}</label>
|
||||
{isEditing ? (
|
||||
<UserSelect
|
||||
value={editForm.assignee_id}
|
||||
onChange={handleAssigneeChange}
|
||||
placeholder="Select assignee..."
|
||||
placeholder={t('common:labels.selectAssignee')}
|
||||
/>
|
||||
) : (
|
||||
<div style={styles.assigneeDisplay}>
|
||||
{task.assignee_name || 'Unassigned'}
|
||||
{task.assignee_name || t('status.unassigned')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Due Date */}
|
||||
<div style={styles.sidebarField}>
|
||||
<label style={styles.sidebarLabel}>Due Date</label>
|
||||
<label style={styles.sidebarLabel}>{t('fields.dueDate')}</label>
|
||||
{isEditing ? (
|
||||
<input
|
||||
type="date"
|
||||
@@ -423,14 +425,14 @@ export function TaskDetailModal({
|
||||
<div style={styles.dueDateDisplay}>
|
||||
{task.due_date
|
||||
? new Date(task.due_date).toLocaleDateString()
|
||||
: 'No due date'}
|
||||
: t('status.noDueDate')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Time Estimate */}
|
||||
<div style={styles.sidebarField}>
|
||||
<label style={styles.sidebarLabel}>Time Estimate (hours)</label>
|
||||
<label style={styles.sidebarLabel}>{t('fields.estimatedHours')}</label>
|
||||
{isEditing ? (
|
||||
<input
|
||||
type="number"
|
||||
@@ -445,7 +447,7 @@ export function TaskDetailModal({
|
||||
/>
|
||||
) : (
|
||||
<div style={styles.timeEstimateDisplay}>
|
||||
{task.time_estimate ? `${task.time_estimate} hours` : 'Not estimated'}
|
||||
{task.time_estimate ? t('fields.hours', { count: task.time_estimate }) : t('status.notEstimated')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -453,8 +455,8 @@ export function TaskDetailModal({
|
||||
{/* Subtasks Info */}
|
||||
{task.subtask_count > 0 && (
|
||||
<div style={styles.sidebarField}>
|
||||
<label style={styles.sidebarLabel}>Subtasks</label>
|
||||
<div style={styles.subtaskInfo}>{task.subtask_count} subtask(s)</div>
|
||||
<label style={styles.sidebarLabel}>{t('subtasks.title')}</label>
|
||||
<div style={styles.subtaskInfo}>{t('subtasks.count', { count: task.subtask_count })}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -462,7 +464,7 @@ export function TaskDetailModal({
|
||||
{customFields.length > 0 && (
|
||||
<>
|
||||
<div style={styles.customFieldsDivider} />
|
||||
<div style={styles.customFieldsHeader}>Custom Fields</div>
|
||||
<div style={styles.customFieldsHeader}>{t('settings:customFields.title')}</div>
|
||||
{loadingCustomFields ? (
|
||||
<SkeletonList count={3} showAvatar={false} />
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user