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:
beabigegg
2026-01-08 23:38:21 +08:00
parent 4bc3c24360
commit 2796cbb42d
20 changed files with 401 additions and 197 deletions

View File

@@ -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} />
) : (