diff --git a/documentation/frontend/components.md b/documentation/frontend/components.md
index 62f8e6f..fca5969 100644
--- a/documentation/frontend/components.md
+++ b/documentation/frontend/components.md
@@ -30,7 +30,7 @@ src/components/
**Audiobooks**
- **AudiobookCard** ✅ - Cover, title, author, narrator, duration, request button, clickable to open details modal. Shows "Requested by [username]" when someone else has requested the book, "Requested" when current user has requested it
- **AudiobookGrid** - Responsive grid (1/2/3/4 cols)
-- **AudiobookDetailsModal** ✅ - Full-screen modal with comprehensive metadata (description, genres, rating, release date, narrator, request functionality). Shows requesting user's name when applicable
+- **AudiobookDetailsModal** ✅ - Full-screen modal with comprehensive metadata (description, genres, rating, release date, narrator, language, format, publisher, request functionality). Shows requesting user's name when applicable
**Requests**
- **RequestCard** ✅ - Cover, title, author, status badge, progress bar, timestamps, action buttons (cancel, manual search, interactive search)
diff --git a/documentation/integrations/audible.md b/documentation/integrations/audible.md
index b7bac3b..b377b75 100644
--- a/documentation/integrations/audible.md
+++ b/documentation/integrations/audible.md
@@ -250,6 +250,9 @@ interface AudibleAudiobook {
series?: string;
seriesPart?: string;
seriesAsin?: string;
+ language?: string;
+ formatType?: string;
+ publisherName?: string;
}
interface EnrichedAudibleAudiobook extends AudibleAudiobook {
diff --git a/src/components/audiobooks/AudiobookDetailsModal.tsx b/src/components/audiobooks/AudiobookDetailsModal.tsx
index 5bf23b8..9a7658c 100644
--- a/src/components/audiobooks/AudiobookDetailsModal.tsx
+++ b/src/components/audiobooks/AudiobookDetailsModal.tsx
@@ -552,7 +552,7 @@ export function AudiobookDetailsModal({
{audiobook.language && (
Language
-
{audiobook.language.charAt(0).toUpperCase() + audiobook.language.slice(1)}
+
{audiobook.language}
)}
@@ -560,7 +560,7 @@ export function AudiobookDetailsModal({
{audiobook.formatType && (
Format
-
{audiobook.formatType.charAt(0).toUpperCase() + audiobook.formatType.slice(1)}
+
{audiobook.formatType}
)}
diff --git a/src/lib/integrations/audible.service.ts b/src/lib/integrations/audible.service.ts
index b19d721..9934171 100644
--- a/src/lib/integrations/audible.service.ts
+++ b/src/lib/integrations/audible.service.ts
@@ -108,6 +108,8 @@ interface CatalogProduct {
runtime_length_min?: number;
release_date?: string;
language?: string;
+ format_type?: string;
+ publisher_name?: string;
rating?: {
overall_distribution?: {
display_stars?: number;
@@ -198,6 +200,9 @@ function mapCatalogProduct(product: CatalogProduct): AudibleAudiobook {
series,
seriesPart,
seriesAsin,
+ language: product.language ?? undefined,
+ formatType: product.format_type ?? undefined,
+ publisherName: product.publisher_name ?? undefined,
};
}
diff --git a/tests/integrations/audible.service.test.ts b/tests/integrations/audible.service.test.ts
index f006031..ea4d955 100644
--- a/tests/integrations/audible.service.test.ts
+++ b/tests/integrations/audible.service.test.ts
@@ -49,6 +49,8 @@ interface ProductOverrides {
runtime_length_min?: number;
release_date?: string;
language?: string;
+ format_type?: string;
+ publisher_name?: string;
rating?: { overall_distribution?: { display_stars?: number } };
category_ladders?: Array<{ ladder: Array<{ name: string }> }>;
series?: Array<{ asin?: string; title?: string; sequence?: string }>;
@@ -615,6 +617,47 @@ describe('AudibleService', () => {
const genreSet = new Set(results[0].genres);
expect(genreSet.size).toBe(5);
});
+
+ it('maps language from catalog product', async () => {
+ const products = [makeProduct({ language: 'english' })];
+ apiClientMock.get.mockResolvedValue(apiResponse(makeProductsResponse(products)));
+
+ const service = new AudibleService();
+ const { results } = await service.search('test', 1);
+
+ expect(results[0].language).toBe('english');
+ });
+
+ it('maps format_type to formatType from catalog product', async () => {
+ const products = [makeProduct({ format_type: 'unabridged' })];
+ apiClientMock.get.mockResolvedValue(apiResponse(makeProductsResponse(products)));
+
+ const service = new AudibleService();
+ const { results } = await service.search('test', 1);
+
+ expect(results[0].formatType).toBe('unabridged');
+ });
+
+ it('maps publisher_name to publisherName from catalog product', async () => {
+ const products = [makeProduct({ publisher_name: 'Penguin Random House Audio' })];
+ apiClientMock.get.mockResolvedValue(apiResponse(makeProductsResponse(products)));
+
+ const service = new AudibleService();
+ const { results } = await service.search('test', 1);
+
+ expect(results[0].publisherName).toBe('Penguin Random House Audio');
+ });
+
+ it('leaves formatType and publisherName undefined when catalog product omits them', async () => {
+ const products = [makeProduct()];
+ apiClientMock.get.mockResolvedValue(apiResponse(makeProductsResponse(products)));
+
+ const service = new AudibleService();
+ const { results } = await service.search('test', 1);
+
+ expect(results[0].formatType).toBeUndefined();
+ expect(results[0].publisherName).toBeUndefined();
+ });
});
// -------------------------------------------------------------------------
@@ -1262,6 +1305,9 @@ describe('AudibleService', () => {
runtimeLengthMin: '300',
genres: ['Fiction'],
rating: '4.7',
+ language: 'english',
+ formatType: 'unabridged',
+ publisherName: 'Test Publisher',
},
});
@@ -1271,6 +1317,9 @@ describe('AudibleService', () => {
expect(details?.title).toBe('Audnexus Book');
expect(details?.author).toBe('Author A');
expect(details?.durationMinutes).toBe(300);
+ expect(details?.language).toBe('english');
+ expect(details?.formatType).toBe('unabridged');
+ expect(details?.publisherName).toBe('Test Publisher');
// Catalog API should NOT be called when Audnexus succeeds.
expect(apiClientMock.get).not.toHaveBeenCalled();
});