From 86f705eae9991fc53c3f33db164242d379aa6f0c Mon Sep 17 00:00:00 2001 From: German Shein Date: Fri, 22 Aug 2025 09:59:59 +0400 Subject: [PATCH] feat: support author sorting (last name first), closes #1799 (#1865) * [Issue #1799](https://github.com/readest/readest/issues/1799): Changing the name order when sorting the book by Author by placing the last name first in Arabic, Tibetan, German, English, Spanish, French, Hindi, Italian, Dutch, Polish, Portuguese, Russian, Thai, Turkish and Ukrainian languages * refactor author sort --------- Co-authored-by: Huang Xin --- .../src/app/library/components/Bookshelf.tsx | 4 ++-- apps/readest-app/src/utils/book.ts | 23 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/readest-app/src/app/library/components/Bookshelf.tsx b/apps/readest-app/src/app/library/components/Bookshelf.tsx index 0c8cd5de..9efb2b9d 100644 --- a/apps/readest-app/src/app/library/components/Bookshelf.tsx +++ b/apps/readest-app/src/app/library/components/Bookshelf.tsx @@ -239,8 +239,8 @@ const Bookshelf: React.FC = ({ const bTitle = formatTitle(b.title); return aTitle.localeCompare(bTitle, uiLanguage || navigator.language); case 'author': - const aAuthors = formatAuthors(a.author); - const bAuthors = formatAuthors(b.author); + const aAuthors = formatAuthors(a.author, a?.primaryLanguage || 'en', true); + const bAuthors = formatAuthors(b.author, b?.primaryLanguage || 'en', true); return aAuthors.localeCompare(bAuthors, uiLanguage || navigator.language); case 'updated': return new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime(); diff --git a/apps/readest-app/src/utils/book.ts b/apps/readest-app/src/utils/book.ts index f6dace1a..ddeeb9ff 100644 --- a/apps/readest-app/src/utils/book.ts +++ b/apps/readest-app/src/utils/book.ts @@ -91,21 +91,38 @@ export const flattenContributors = ( : formatLanguageMap(contributors?.name); }; +// prettier-ignore +const LASTNAME_AUTHOR_SORT_LANGS = [ 'ar', 'bo', 'de', 'en', 'es', 'fr', 'hi', 'it', 'nl', 'pl', 'pt', 'ru', 'th', 'tr', 'uk' ]; + +const formatAuthorName = (name: string, lastNameFirst: boolean) => { + if (!name) return ''; + const parts = name.split(' '); + if (lastNameFirst && parts.length > 1) { + return `${parts[parts.length - 1]}, ${parts.slice(0, -1).join(' ')}`; + } + return name; +}; + export const formatAuthors = ( contributors: string | string[] | Contributor | Contributor[], bookLang?: string | string[], + sortAs?: boolean, ) => { const langCode = getBookLangCode(bookLang) || 'en'; + const lastNameFirst = !!sortAs && LASTNAME_AUTHOR_SORT_LANGS.includes(langCode); return Array.isArray(contributors) ? listFormater(langCode === 'zh', langCode).format( contributors.map((contributor) => - typeof contributor === 'string' ? contributor : formatLanguageMap(contributor?.name), + typeof contributor === 'string' + ? formatAuthorName(contributor, lastNameFirst) + : formatAuthorName(formatLanguageMap(contributor?.name), lastNameFirst), ), ) : typeof contributors === 'string' - ? contributors - : formatLanguageMap(contributors?.name); + ? formatAuthorName(contributors, lastNameFirst) + : formatAuthorName(formatLanguageMap(contributors?.name), lastNameFirst); }; + export const formatTitle = (title: string | LanguageMap) => { return typeof title === 'string' ? title : formatLanguageMap(title); };