Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.d/3027-web-english-strings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Fixed
- **Search interval hint gave the wrong advice** (#3027). It told you to reduce the wanted search interval when indexers rate limit you, which makes Bindery search more often. It now says to pick a longer interval. Translations that carried the old English text now show the corrected English until they are translated.
- **Series, Calendar and Push all to Calibre can now be translated** (#3027). The Series page heading, buttons and card labels, the Calendar month and weekday names, and the Push all to Calibre button and progress window were always shown in English. Calendar dates now follow your chosen language, and book status badges on the Series page are now capitalised to match the rest of the app.
- **Consistent ellipses** (#3027). Loading and busy labels use the same ellipsis character in English and in the German, Spanish, French, Dutch, Tagalog, Indonesian and Korean translations. Leftover English text in those translations now falls back to English properly instead.
8 changes: 4 additions & 4 deletions web/src/components/AuthorMetadataLinkModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import type { Author } from '../api/client'
const tMock = vi.hoisted(() => (key: string, options?: string | Record<string, unknown>) => {
const strings: Record<string, string> = {
'authorMetadataLink.title': 'Link metadata',
'authorMetadataLink.searchPlaceholder': 'Search by author name...',
'authorMetadataLink.searchPlaceholder': 'Search by author name…',
'authorMetadataLink.search': 'Search',
'authorMetadataLink.searching': 'Searching...',
'authorMetadataLink.searching': 'Searching…',
'authorMetadataLink.link': 'Link',
'authorMetadataLink.linking': 'Linking...',
'authorMetadataLink.linking': 'Linking…',
'authorMetadataLink.noResults': 'No alternate metadata candidates found',
'authorMetadataLink.previouslyLinked': 'previously linked',
'common.cancel': 'Cancel',
Expand Down Expand Up @@ -96,7 +96,7 @@ describe('AuthorMetadataLinkModal', () => {

await waitFor(() => expect(api.searchAuthorLinkCandidates).toHaveBeenCalledWith(42, 'Emilia Jae'))

const input = screen.getByPlaceholderText('Search by author name...')
const input = screen.getByPlaceholderText('Search by author name…')
fireEvent.change(input, { target: { value: 'Manual Query' } })
const form = input.closest('form')
if (!form) throw new Error('search form not found')
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/AuthorMetadataLinkModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ export default function AuthorMetadataLinkModal({ author, onClose, onLinked }: P
value={query}
onChange={event => setQuery(event.target.value)}
className="flex-1 bg-slate-200 dark:bg-zinc-800 border border-slate-300 dark:border-zinc-700 rounded-md px-3 py-2 text-sm focus:outline-none focus:border-emerald-500"
placeholder={t('authorMetadataLink.searchPlaceholder', 'Search by author name...')}
placeholder={t('authorMetadataLink.searchPlaceholder', 'Search by author name…')}
autoFocus
/>
<button
type="submit"
disabled={searching || !query.trim()}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 rounded-md text-sm font-medium"
>
{searching ? t('authorMetadataLink.searching', 'Searching...') : t('authorMetadataLink.search', 'Search')}
{searching ? t('authorMetadataLink.searching', 'Searching…') : t('authorMetadataLink.search', 'Search')}
</button>
</form>

Expand Down Expand Up @@ -204,7 +204,7 @@ export default function AuthorMetadataLinkModal({ author, onClose, onLinked }: P
disabled={linking !== null}
className="px-3 py-1.5 bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 rounded text-xs font-medium flex-shrink-0"
>
{linking === candidate.foreignAuthorId ? t('authorMetadataLink.linking', 'Linking...') : t('authorMetadataLink.link', 'Link')}
{linking === candidate.foreignAuthorId ? t('authorMetadataLink.linking', 'Linking…') : t('authorMetadataLink.link', 'Link')}
</button>
</div>
))}
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/EditAuthorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export default function EditAuthorModal({ author, onClose, onSaved }: Props) {

<div className="p-4 flex-1 overflow-y-auto">
{loading ? (
<p className="text-sm text-slate-600 dark:text-zinc-500">{t('common.loading', 'Loading...')}</p>
<p className="text-sm text-slate-600 dark:text-zinc-500">{t('common.loading', 'Loading…')}</p>
) : (
<>
{qualityProfiles.length > 0 && (
Expand Down Expand Up @@ -262,7 +262,7 @@ export default function EditAuthorModal({ author, onClose, onSaved }: Props) {
<div className="mb-3">
<label className="block text-xs text-slate-600 dark:text-zinc-400 mb-1">{t('editAuthorModal.monitoredSeries', 'Monitored series')}</label>
{!seriesLoaded ? (
<p className="text-xs text-slate-500 dark:text-zinc-500">{t('common.loading', 'Loading...')}</p>
<p className="text-xs text-slate-500 dark:text-zinc-500">{t('common.loading', 'Loading…')}</p>
) : authorSeries.length === 0 ? (
<p className="text-xs text-slate-500 dark:text-zinc-500">
{t('editAuthorModal.monitoredSeriesEmpty', 'No series found for this author yet. Refresh the author to pull series data first.')}
Expand Down Expand Up @@ -376,7 +376,7 @@ export default function EditAuthorModal({ author, onClose, onSaved }: Props) {
disabled={loading || saving}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 rounded-md text-sm font-medium text-white"
>
{saving ? t('common.saving', 'Saving...') : t('common.save', 'Save')}
{saving ? t('common.saving', 'Saving…') : t('common.save', 'Save')}
</button>
</div>
</div>
Expand Down
14 changes: 8 additions & 6 deletions web/src/components/SeriesNameModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FormEvent, useState } from 'react'
import { useTranslation } from 'react-i18next'

interface Props {
title: string
Expand All @@ -9,6 +10,7 @@ interface Props {
}

export default function SeriesNameModal({ title, initialName = '', submitLabel, onClose, onSubmit }: Props) {
const { t } = useTranslation()
const [name, setName] = useState(initialName)
const [saving, setSaving] = useState(false)
const [error, setError] = useState<string | null>(null)
Expand All @@ -17,15 +19,15 @@ export default function SeriesNameModal({ title, initialName = '', submitLabel,
event.preventDefault()
const trimmed = name.trim()
if (!trimmed) {
setError('Series name is required')
setError(t('series.nameModal.required'))
return
}
setSaving(true)
setError(null)
try {
await onSubmit(trimmed)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to save series')
setError(err instanceof Error ? err.message : t('series.nameModal.failed'))
} finally {
setSaving(false)
}
Expand All @@ -46,14 +48,14 @@ export default function SeriesNameModal({ title, initialName = '', submitLabel,
</div>
<div className="p-4 space-y-3">
<label className="block text-sm font-medium text-slate-700 dark:text-zinc-300" htmlFor="series-name">
Name
{t('series.nameModal.name')}
</label>
<input
id="series-name"
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="Series name"
placeholder={t('series.nameModal.placeholder')}
className="w-full bg-slate-200 dark:bg-zinc-800 border border-slate-300 dark:border-zinc-700 rounded-md px-3 py-2 text-sm focus:outline-none focus:border-emerald-500"
autoFocus
/>
Expand All @@ -65,14 +67,14 @@ export default function SeriesNameModal({ title, initialName = '', submitLabel,
onClick={onClose}
className="px-4 py-2 text-sm text-slate-600 dark:text-zinc-400 hover:text-slate-900 dark:hover:text-white"
>
Cancel
{t('common.cancel')}
</button>
<button
type="submit"
disabled={saving || !name.trim()}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:opacity-50 rounded-md text-sm font-medium"
>
{saving ? 'Saving...' : submitLabel}
{saving ? t('common.saving') : submitLabel}
</button>
</div>
</form>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/import/BookPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const BookPicker = forwardRef<HTMLInputElement, Props>(function BookPicker({ onP
)}
</div>
<div className="mt-2 max-h-48 overflow-y-auto divide-y divide-slate-100 dark:divide-zinc-800">
{loading && <p className="py-2 text-xs text-slate-500 dark:text-zinc-500">{t('common.loading', 'Loading...')}</p>}
{loading && <p className="py-2 text-xs text-slate-500 dark:text-zinc-500">{t('common.loading', 'Loading…')}</p>}
{!loading && term.trim().length >= 2 && results.length === 0 && (
<p className="py-2 text-xs text-slate-500 dark:text-zinc-500">
{t('manualImport.noResults', 'No matching books in your library.')}
Expand Down
27 changes: 12 additions & 15 deletions web/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
{
"common": {
"loading": "Wird geladen...",
"loading": "Wird geladen…",
"viewOnSource": "Auf {{source}} ansehen ↗",
"links": "Links",
"cancel": "Abbrechen",
"delete": "Löschen",
"edit": "Bearbeiten",
"save": "Speichern",
"saving": "Wird gespeichert...",
"deleting": "Deleting...",
"saving": "Wird gespeichert…",
"test": "Testen",
"refresh": "Aktualisieren",
"all": "Alle",
Expand Down Expand Up @@ -85,7 +84,7 @@
"addAuthor": "+ Autor hinzufügen",
"merge": "Zusammenführen",
"mergeTip": "Zwei Autoren zusammenführen — Duplikate in eine kanonische Zeile zusammenklappen",
"searchPlaceholder": "Autoren suchen...",
"searchPlaceholder": "Autoren suchen…",
"sortAZ": "A–Z",
"sortZA": "Z–A",
"sortRecent": "Neueste",
Expand All @@ -110,7 +109,7 @@
},
"books": {
"title": "Bücher",
"searchPlaceholder": "Bücher oder Autoren suchen...",
"searchPlaceholder": "Bücher oder Autoren suchen…",
"sortLabel": "Sortieren:",
"typeLabel": "Typ:",
"sortTitleAZ": "A–Z",
Expand Down Expand Up @@ -141,13 +140,13 @@
},
"wanted": {
"title": "Gesucht",
"searchPlaceholder": "Nach Titel oder Autor suchen...",
"searchPlaceholder": "Nach Titel oder Autor suchen…",
"countLabel": "{{filtered}} von {{total}}",
"empty": "Keine gesuchten Bücher. Fügen Sie einen Autor hinzu, um zu beginnen.",
"noMatch": "Keine Bücher entsprechen Ihrer Suche.",
"selectAllPage": "Alle auf dieser Seite auswählen",
"search": "Suchen",
"searching": "Suche läuft...",
"searching": "Suche läuft…",
"unmonitor": "Nicht mehr überwachen",
"grab": "Herunterladen",
"grabbing": "Wird heruntergeladen…",
Expand Down Expand Up @@ -193,7 +192,7 @@
"empty": "Blockliste ist leer",
"emptyHint": "Fehlgeschlagene Downloads werden hier automatisch hinzugefügt, um erneutes Herunterladen zu verhindern",
"deleteSelected": "Auswahl löschen ({{count}})",
"deleting": "Wird gelöscht...",
"deleting": "Wird gelöscht…",
"colTitle": "Titel",
"colReason": "Grund",
"colDate": "Datum",
Expand Down Expand Up @@ -373,7 +372,7 @@
"backupCreate": "Sicherung aller Bindery-Konfigurationen erstellen",
"backupHint": "Enthält Autoren, Bücher, Indexer und Einstellungen",
"backupButton": "Sicherung erstellen",
"backupCreating": "Wird erstellt...",
"backupCreating": "Wird erstellt…",
"existingBackups": "Vorhandene Sicherungen:",
"security": "Sicherheit",
"logRetention": "Log Retention",
Expand All @@ -388,7 +387,6 @@
},
"search": "Search",
"searchIntervalLabel": "Wanted search interval",
"searchIntervalHint": "How often Bindery searches all wanted books against your indexers. Reduce if you have a large library and are hitting indexer API rate limits.",
"searchIntervalRestart": "Takes effect after the next Bindery restart."
},
"import": {
Expand Down Expand Up @@ -428,7 +426,7 @@
"inLibrary": "In deiner Bibliothek",
"open": "Öffnen",
"openBook": "{{title}} öffnen",
"searching": "Suche läuft...",
"searching": "Suche läuft…",
"select": "Auswählen",
"selectBook": "{{title}} auswählen",
"idMissing": "Dieses Ergebnis hat keine Buch-ID und kann nicht hinzugefügt werden",
Expand All @@ -439,8 +437,8 @@
"noResults": "Keine Ergebnisse gefunden",
"searchError": "Metadatenanbieter nicht erreichbar — {{error}}",
"backToResults": "Zurück zu den Ergebnissen",
"adding": "Wird hinzugefügt...",
"searchPlaceholderAuthor": "Nach Autorenname suchen...",
"adding": "Wird hinzugefügt…",
"searchPlaceholderAuthor": "Nach Autorenname suchen…",
"searchPlaceholderBook": "Titel, ISBN oder ASIN (zum Beispiel Dune, 9780441478125, B0DBJBFHGT)",
"author": {
"confirmAdd": "Autor hinzufügen",
Expand Down Expand Up @@ -483,7 +481,7 @@
"keepLabel": "Behalten",
"removeLabel": "Entfernen",
"mergeButton": "Zusammenführen",
"merging": "Wird zusammengeführt...",
"merging": "Wird zusammengeführt…",
"cancel": "Abbrechen"
},
"pagination": {
Expand All @@ -501,7 +499,6 @@
"title": "Discover",
"subtitle": "Based on your library",
"refresh": "Refresh",
"refreshing": "Refreshing...",
"addToWanted": "Add to Wanted",
"dismiss": "Dismiss",
"dontSuggestAuthor": "Don't suggest {{author}}",
Expand Down
Loading
Loading