fix(i18n): translate remaining German hardcoded strings in PhotoUpload

Replace 6 hardcoded German strings in PhotoUpload.tsx with t() calls:
- 'Tag verknüpfen' → t('photos.linkDay')
- 'Kein Tag' / 'Tag N' → t('photos.noDay') / t('photos.dayLabel')
- '{N} Foto(s) ausgewählt' → t('photos.photoSelected/photosSelected')
- 'bis zu 30 Fotos' hint → t('photos.fileTypeHint')
- 'Wird hochgeladen...' → t('common.uploading')

Add all 6 new keys to all 14 language files and update test
assertions from German strings to English equivalents.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Isaias Tavares
2026-04-12 16:53:50 -03:00
parent ecbb1de8de
commit 0fe1c443e9
16 changed files with 96 additions and 12 deletions
@@ -50,7 +50,7 @@ describe('PhotoUpload', () => {
it('FE-COMP-PHOTOUPLOAD-002: options section hidden before files are selected', () => {
render(<PhotoUpload {...defaultProps} />)
expect(screen.queryByText('Tag verknüpfen')).not.toBeInTheDocument()
expect(screen.queryByText('Link Day')).not.toBeInTheDocument()
expect(screen.queryByPlaceholderText('Optional caption...')).not.toBeInTheDocument()
})
@@ -65,27 +65,27 @@ describe('PhotoUpload', () => {
render(<PhotoUpload {...defaultProps} />)
await uploadFiles([makeFile()])
expect(screen.getByAltText('photo.jpg')).toBeInTheDocument()
expect(screen.getByText('Tag verknüpfen')).toBeInTheDocument()
expect(screen.getByText('Link Day')).toBeInTheDocument()
expect(screen.getByPlaceholderText('Optional caption...')).toBeInTheDocument()
})
it('FE-COMP-PHOTOUPLOAD-005: file count label updates correctly', async () => {
render(<PhotoUpload {...defaultProps} />)
await uploadFiles([makeFile('photo1.jpg'), makeFile('photo2.jpg')])
expect(screen.getByText('2 Fotos ausgewählt')).toBeInTheDocument()
expect(screen.getByText('2 Photos selected')).toBeInTheDocument()
})
it('FE-COMP-PHOTOUPLOAD-006: remove button removes a file from preview', async () => {
render(<PhotoUpload {...defaultProps} />)
await uploadFiles([makeFile('photo1.jpg'), makeFile('photo2.jpg')])
expect(screen.getByText('2 Fotos ausgewählt')).toBeInTheDocument()
expect(screen.getByText('2 Photos selected')).toBeInTheDocument()
// Remove buttons are inside `.relative.aspect-square` wrappers in the preview grid
const removeButtons = document.querySelectorAll('.relative.aspect-square button')
expect(removeButtons.length).toBe(2)
await userEvent.click(removeButtons[0])
expect(screen.getByText('1 Foto ausgewählt')).toBeInTheDocument()
expect(screen.getByText('1 Photo selected')).toBeInTheDocument()
expect(screen.getAllByRole('img').length).toBe(1)
})
@@ -146,7 +146,7 @@ describe('PhotoUpload', () => {
await userEvent.click(getSubmitButton())
await waitFor(() => {
expect(screen.getByText(/wird hochgeladen/i)).toBeInTheDocument()
expect(screen.getAllByText(/uploading/i).length).toBeGreaterThan(0)
})
expect(getSubmitButton()).toBeDisabled()
+6 -6
View File
@@ -90,7 +90,7 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }: PhotoUp
<>
<p className="text-gray-600 font-medium">{t('photos.dropHereActive')}</p>
<p className="text-gray-400 text-sm mt-1">{t('photos.clickToSelect')}</p>
<p className="text-gray-400 text-xs mt-2">JPG, PNG, WebP · max. 10 MB · bis zu 30 Fotos</p>
<p className="text-gray-400 text-xs mt-2">{t('photos.fileTypeHint')}</p>
</>
)}
</div>
@@ -98,7 +98,7 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }: PhotoUp
{/* Preview grid */}
{files.length > 0 && (
<div>
<p className="text-sm font-medium text-gray-700 mb-2">{files.length} Foto{files.length !== 1 ? 's' : ''} ausgewählt</p>
<p className="text-sm font-medium text-gray-700 mb-2">{files.length} {t(files.length !== 1 ? 'photos.photosSelected' : 'photos.photoSelected')}</p>
<div className="grid grid-cols-4 sm:grid-cols-6 gap-2 max-h-48 overflow-y-auto">
{files.map((file, idx) => (
<div key={idx} className="relative aspect-square group">
@@ -126,15 +126,15 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }: PhotoUp
{files.length > 0 && (
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Tag verknüpfen</label>
<label className="block text-xs font-medium text-gray-700 mb-1">{t('photos.linkDay')}</label>
<select
value={dayId}
onChange={e => setDayId(e.target.value)}
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-slate-900"
>
<option value="">Kein Tag</option>
<option value="">{t('photos.noDay')}</option>
{(days || []).map(day => (
<option key={day.id} value={day.id}>Tag {day.day_number}</option>
<option key={day.id} value={day.id}>{t('photos.dayLabel', { number: day.day_number })}</option>
))}
</select>
</div>
@@ -169,7 +169,7 @@ export function PhotoUpload({ tripId, days, places, onUpload, onClose }: PhotoUp
<div className="bg-slate-50 rounded-lg p-3">
<div className="flex items-center gap-2 mb-2">
<div className="w-4 h-4 border-2 border-slate-900 border-t-transparent rounded-full animate-spin" />
<span className="text-sm text-slate-900">Wird hochgeladen...</span>
<span className="text-sm text-slate-900">{t('common.uploading')}</span>
</div>
<div className="w-full bg-slate-200 rounded-full h-1.5">
<div
+6
View File
@@ -1357,6 +1357,12 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'ربط بمكان',
'photos.noPlace': 'بلا مكان',
'photos.uploadN': 'رفع {n} صورة',
'photos.linkDay': 'ربط اليوم',
'photos.noDay': 'لا يوم',
'photos.dayLabel': 'اليوم {number}',
'photos.photoSelected': 'صورة محددة',
'photos.photosSelected': 'صور محددة',
'photos.fileTypeHint': 'JPG, PNG, WebP · الحد الأقصى 10 ميغابايت · حتى 30 صورة',
// Backup restore modal
'backup.restoreConfirmTitle': 'استعادة النسخة الاحتياطية؟',
+6
View File
@@ -1329,6 +1329,12 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'Vincular lugar',
'photos.noPlace': 'Sem lugar',
'photos.uploadN': 'Enviar {n} foto(s)',
'photos.linkDay': 'Vincular dia',
'photos.noDay': 'Nenhum dia',
'photos.dayLabel': 'Dia {number}',
'photos.photoSelected': 'Foto selecionada',
'photos.photosSelected': 'Fotos selecionadas',
'photos.fileTypeHint': 'JPG, PNG, WebP · máx. 10 MB · até 30 fotos',
// Backup restore modal
'backup.restoreConfirmTitle': 'Restaurar backup?',
+6
View File
@@ -1355,6 +1355,12 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'Propojit s místem',
'photos.noPlace': 'Žádné místo',
'photos.uploadN': 'Nahrát {n} fotek',
'photos.linkDay': 'Propojit den',
'photos.noDay': 'Žádný den',
'photos.dayLabel': 'Den {number}',
'photos.photoSelected': 'Fotografie vybrána',
'photos.photosSelected': 'Fotografie vybrány',
'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · až 30 fotografií',
// Obnovení zálohy
'backup.restoreConfirmTitle': 'Obnovit zálohu?',
+6
View File
@@ -1356,6 +1356,12 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'Ort verknüpfen',
'photos.noPlace': 'Kein Ort',
'photos.uploadN': '{n} Foto(s) hochladen',
'photos.linkDay': 'Tag verknüpfen',
'photos.noDay': 'Kein Tag',
'photos.dayLabel': 'Tag {number}',
'photos.photoSelected': 'Foto ausgewählt',
'photos.photosSelected': 'Fotos ausgewählt',
'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · bis zu 30 Fotos',
// Backup restore modal
'backup.restoreConfirmTitle': 'Backup wiederherstellen?',
+6
View File
@@ -1378,6 +1378,12 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'Link Place',
'photos.noPlace': 'No Place',
'photos.uploadN': '{n} photo(s) upload',
'photos.linkDay': 'Link Day',
'photos.noDay': 'No Day',
'photos.dayLabel': 'Day {number}',
'photos.photoSelected': 'Photo selected',
'photos.photosSelected': 'Photos selected',
'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · up to 30 photos',
// Backup restore modal
'backup.restoreConfirmTitle': 'Restore Backup?',
+6
View File
@@ -1294,6 +1294,12 @@ const es: Record<string, string> = {
'photos.linkPlace': 'Vincular lugar',
'photos.noPlace': 'Sin lugar',
'photos.uploadN': 'Subida de {n} foto(s)',
'photos.linkDay': 'Vincular día',
'photos.noDay': 'Ningún día',
'photos.dayLabel': 'Día {number}',
'photos.photoSelected': 'Foto seleccionada',
'photos.photosSelected': 'Fotos seleccionadas',
'photos.fileTypeHint': 'JPG, PNG, WebP · máx. 10 MB · hasta 30 fotos',
'admin.addons.catalog.memories.name': 'Fotos (Immich)',
'admin.addons.catalog.memories.description': 'Comparte fotos de viaje a través de tu instancia de Immich',
'admin.addons.catalog.mcp.name': 'MCP',
+6
View File
@@ -1356,6 +1356,12 @@ const fr: Record<string, string> = {
'photos.linkPlace': 'Lier au lieu',
'photos.noPlace': 'Aucun lieu',
'photos.uploadN': '{n} photo(s) importée(s)',
'photos.linkDay': 'Lier le jour',
'photos.noDay': 'Aucun jour',
'photos.dayLabel': 'Jour {number}',
'photos.photoSelected': 'Photo sélectionnée',
'photos.photosSelected': 'Photos sélectionnées',
'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 Mo · jusqu'à 30 photos",
// Backup restore modal
'backup.restoreConfirmTitle': 'Restaurer la sauvegarde ?',
+6
View File
@@ -1354,6 +1354,12 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'Hely társítása',
'photos.noPlace': 'Nincs hely',
'photos.uploadN': '{n} fotó feltöltése',
'photos.linkDay': 'Nap csatolása',
'photos.noDay': 'Nincs nap',
'photos.dayLabel': '{number}. nap',
'photos.photoSelected': 'Fotó kiválasztva',
'photos.photosSelected': 'Fotók kiválasztva',
'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · legfeljebb 30 fotó',
// Mentés visszaállítása modal
'backup.restoreConfirmTitle': 'Mentés visszaállítása?',
+6
View File
@@ -1354,6 +1354,12 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'Collega luogo',
'photos.noPlace': 'Nessun luogo',
'photos.uploadN': 'Caricamento di {n} foto',
'photos.linkDay': 'Collega giorno',
'photos.noDay': 'Nessun giorno',
'photos.dayLabel': 'Giorno {number}',
'photos.photoSelected': 'Foto selezionata',
'photos.photosSelected': 'Foto selezionate',
'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · fino a 30 foto',
// Backup restore modal
'backup.restoreConfirmTitle': 'Ripristinare il backup?',
+6
View File
@@ -1353,6 +1353,12 @@ const nl: Record<string, string> = {
'photos.linkPlace': 'Koppel plaats',
'photos.noPlace': 'Geen plaats',
'photos.uploadN': '{n} foto(\'s) uploaden',
'photos.linkDay': 'Dag koppelen',
'photos.noDay': 'Geen dag',
'photos.dayLabel': 'Dag {number}',
'photos.photoSelected': 'Foto geselecteerd',
'photos.photosSelected': "Foto's geselecteerd",
'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 MB · tot 30 foto's",
// Backup restore modal
'backup.restoreConfirmTitle': 'Back-up herstellen?',
+6
View File
@@ -1314,6 +1314,12 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'photos.linkPlace': 'Połącz z miejscem',
'photos.noPlace': 'Brak miejsca',
'photos.uploadN': 'Prześlij {n} zdjęć',
'photos.linkDay': 'Połącz dzień',
'photos.noDay': 'Brak dnia',
'photos.dayLabel': 'Dzień {number}',
'photos.photoSelected': 'Zdjęcie wybrane',
'photos.photosSelected': 'Zdjęcia wybrane',
'photos.fileTypeHint': 'JPG, PNG, WebP · maks. 10 MB · do 30 zdjęć',
// Backup restore modal
'backup.restoreConfirmTitle': 'Przywrócić kopię zapasową?',
+6
View File
@@ -1353,6 +1353,12 @@ const ru: Record<string, string> = {
'photos.linkPlace': 'Привязать место',
'photos.noPlace': 'Без места',
'photos.uploadN': '{n} фото загружено',
'photos.linkDay': 'Связать день',
'photos.noDay': 'Нет дня',
'photos.dayLabel': 'День {number}',
'photos.photoSelected': 'Фото выбрано',
'photos.photosSelected': 'Фото выбраны',
'photos.fileTypeHint': 'JPG, PNG, WebP · макс. 10 МБ · до 30 фото',
// Backup restore modal
'backup.restoreConfirmTitle': 'Восстановить копию?',
+6
View File
@@ -1353,6 +1353,12 @@ const zh: Record<string, string> = {
'photos.linkPlace': '关联地点',
'photos.noPlace': '无地点',
'photos.uploadN': '上传 {n} 张照片',
'photos.linkDay': '关联天数',
'photos.noDay': '无天数',
'photos.dayLabel': '第 {number} 天',
'photos.photoSelected': '张照片已选择',
'photos.photosSelected': '张照片已选择',
'photos.fileTypeHint': 'JPG, PNG, WebP · 最大 10 MB · 最多 30 张照片',
// Backup restore modal
'backup.restoreConfirmTitle': '恢复备份?',
+6
View File
@@ -1379,6 +1379,12 @@ const zhTw: Record<string, string> = {
'photos.linkPlace': '關聯地點',
'photos.noPlace': '無地點',
'photos.uploadN': '上傳 {n} 張照片',
'photos.linkDay': '關聯天數',
'photos.noDay': '無天數',
'photos.dayLabel': '第 {number} 天',
'photos.photoSelected': '張照片已選擇',
'photos.photosSelected': '張照片已選擇',
'photos.fileTypeHint': 'JPG, PNG, WebP · 最大 10 MB · 最多 30 張照片',
// Backup restore modal
'backup.restoreConfirmTitle': '恢復備份?',