fix(i18n): translate hardcoded strings in JourneyDetailPage and fix ellipsis in all languages

- Replace all remaining hardcoded strings in JourneyDetailPage JourneySettingsDialog with t() calls
- Add 14 missing translation keys to all 13 non-English language files
  (trips.member*, common.expand/collapse, inspector.remove, memories.*, journey.*)
- Fix common.loading and common.saving to use Unicode ellipsis (…) instead of three dots (...)
- Update 4 test files that expected three-dot ellipsis to use Unicode ellipsis
- All 2541 tests passing
This commit is contained in:
Isaias Tavares
2026-04-12 17:29:11 -03:00
parent 0fe1c443e9
commit af789b7f7c
24 changed files with 248 additions and 70 deletions
@@ -47,7 +47,7 @@ describe('AuditLogPanel', () => {
}),
);
render(<AuditLogPanel serverTimezone="UTC" />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(screen.getByText('Loading')).toBeInTheDocument();
expect(document.querySelector('table')).not.toBeInTheDocument();
});
@@ -55,7 +55,7 @@ describe('GitHubPanel', () => {
it('FE-ADMIN-GH-002: all support links have correct href and target=_blank', async () => {
render(<GitHubPanel />);
await waitFor(() => expect(screen.queryByText('Loading...')).not.toBeInTheDocument());
await waitFor(() => expect(screen.queryByText('Loading')).not.toBeInTheDocument());
const kofi = screen.getByText('Ko-fi').closest('a')!;
expect(kofi).toHaveAttribute('href', 'https://ko-fi.com/mauriceboe');
@@ -272,7 +272,7 @@ describe('GitHubPanel', () => {
it('FE-ADMIN-GH-016: support card hover effects fire without error', async () => {
render(<GitHubPanel />);
await waitFor(() => expect(screen.queryByText('Loading...')).not.toBeInTheDocument());
await waitFor(() => expect(screen.queryByText('Loading')).not.toBeInTheDocument());
const kofiLink = screen.getByText('Ko-fi').closest('a')!;
fireEvent.mouseEnter(kofiLink);
@@ -189,7 +189,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
</div>
{!collapsed && formattedDate && <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 1 }}>{formattedDate}</div>}
</div>
<button onClick={(e) => { e.stopPropagation(); toggleCollapse() }} title={collapsed ? 'Expand' : 'Collapse'}
<button onClick={(e) => { e.stopPropagation(); toggleCollapse() }} title={collapsed ? t('common.expand') : t('common.collapse')}
style={{ background: 'var(--bg-secondary)', border: 'none', borderRadius: 10, width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0, transition: 'all 0.15s ease' }}
onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'}
onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-secondary)'}>
@@ -601,7 +601,7 @@ export default function PlaceInspector({
{selectedDayId && (
assignmentInDay ? (
<ActionButton onClick={() => onRemoveAssignment(selectedDayId, assignmentInDay.id)} variant="ghost" icon={<Minus size={13} />}
label={<><span className="hidden sm:inline">{t('inspector.removeFromDay')}</span><span className="sm:hidden">Remove</span></>} />
label={<><span className="hidden sm:inline">{t('inspector.removeFromDay')}</span><span className="sm:hidden">{t('inspector.remove')}</span></>} />
) : (
<ActionButton onClick={() => onAssignToDay(place.id)} variant="primary" icon={<Plus size={13} />} label={t('inspector.addToDay')} />
)
@@ -107,7 +107,7 @@ export default function NotificationsTab(): React.ReactElement {
}
const renderContent = () => {
if (!matrix) return <p style={{ fontSize: 12, color: 'var(--text-faint)', fontStyle: 'italic' }}>Loading</p>
if (!matrix) return <p style={{ fontSize: 12, color: 'var(--text-faint)', fontStyle: 'italic' }}>{t('common.loading')}</p>
if (visibleChannels.length === 0) {
return (
@@ -119,7 +119,7 @@ export default function NotificationsTab(): React.ReactElement {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
{saving && <p style={{ fontSize: 11, color: 'var(--text-faint)', marginBottom: 8 }}>Saving</p>}
{saving && <p style={{ fontSize: 11, color: 'var(--text-faint)', marginBottom: 8 }}>{t('common.saving')}</p>}
{matrix.available_channels.webhook && (
<div style={{ marginBottom: 16, padding: '12px', background: 'var(--bg-secondary)', borderRadius: 8, border: '1px solid var(--border-primary)' }}>
<label style={{ display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4 }}>
@@ -253,7 +253,7 @@ export default function PhotoProvidersSection(): React.ReactElement {
onClick={() => handleSaveProvider(provider)}
disabled={!canSave || !!saving[provider.id] || isProviderSaveDisabled(provider)}
className="flex items-center gap-2 px-4 py-2 bg-slate-900 text-white rounded-lg text-sm hover:bg-slate-700 disabled:bg-slate-400"
title={!canSave ? 'Save route is not configured for this provider' : isProviderSaveDisabled(provider) ? 'Please fill all required fields' : ''}
title={!canSave ? t('memories.saveRouteNotConfigured') : isProviderSaveDisabled(provider) ? t('memories.fillRequiredFields') : ''}
>
<Save className="w-4 h-4" /> {t('common.save')}
</button>
@@ -261,7 +261,7 @@ export default function PhotoProvidersSection(): React.ReactElement {
onClick={() => handleTestProvider(provider)}
disabled={!canTest || testing}
className="flex items-center gap-2 px-4 py-2 border border-slate-200 rounded-lg text-sm hover:bg-slate-50"
title={!canTest ? 'Test route is not configured for this provider' : ''}
title={!canTest ? t('memories.testRouteNotConfigured') : ''}
>
{testing
? <div className="w-4 h-4 border-2 border-slate-300 border-t-slate-700 rounded-full animate-spin" />
@@ -284,6 +284,6 @@ describe('TripFormModal', () => {
const submitBtns = screen.getAllByText('Create New Trip');
const submitBtn = submitBtns.find(el => el.closest('button'))!;
await user.click(submitBtn.closest('button')!);
await waitFor(() => expect(screen.getByText('Saving...')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('Saving')).toBeInTheDocument());
});
});
@@ -385,8 +385,8 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
try {
await tripsApi.removeMember(trip!.id, m.id)
setExistingMembers(prev => prev.filter(x => x.id !== m.id))
toast.success(`${m.username} removed`)
} catch { toast.error('Failed to remove') }
toast.success(t('trips.memberRemoved', { username: m.username }))
} catch { toast.error(t('trips.memberRemoveError')) }
}}
style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 99,
@@ -431,8 +431,8 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
try {
await tripsApi.addMember(trip.id, user.username)
setExistingMembers(prev => [...prev, { id: user.id, username: user.username }])
toast.success(`${user.username} added`)
} catch { toast.error('Failed to add') }
toast.success(t('trips.memberAdded', { username: user.username }))
} catch { toast.error(t('trips.memberAddError')) }
}
} else {
setSelectedMembers(prev => prev.includes(Number(value)) ? prev : [...prev, Number(value)])