86f705eae9
* [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 <chrox.huang@gmail.com>
209 lines
6.3 KiB
TypeScript
209 lines
6.3 KiB
TypeScript
import { EXTS } from '@/libs/document';
|
|
import { Book, BookConfig, BookProgress, WritingMode } from '@/types/book';
|
|
import { SUPPORTED_LANGS } from '@/services/constants';
|
|
import { getUserLang, makeSafeFilename } from './misc';
|
|
import { getStorageType } from './storage';
|
|
import { getDirFromLanguage } from './rtl';
|
|
import { code6392to6391, isValidLang, normalizedLangCode } from './lang';
|
|
|
|
export const getDir = (book: Book) => {
|
|
return `${book.hash}`;
|
|
};
|
|
export const getLibraryFilename = () => {
|
|
return 'library.json';
|
|
};
|
|
export const getLibraryBackupFilename = () => {
|
|
return 'library_backup.json';
|
|
};
|
|
export const getRemoteBookFilename = (book: Book) => {
|
|
// S3 storage: https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/object-keys.html
|
|
if (getStorageType() === 'r2') {
|
|
return `${book.hash}/${makeSafeFilename(book.sourceTitle || book.title)}.${EXTS[book.format]}`;
|
|
} else if (getStorageType() === 's3') {
|
|
return `${book.hash}/${book.hash}.${EXTS[book.format]}`;
|
|
} else {
|
|
return '';
|
|
}
|
|
};
|
|
export const getLocalBookFilename = (book: Book) => {
|
|
return `${book.hash}/${makeSafeFilename(book.sourceTitle || book.title)}.${EXTS[book.format]}`;
|
|
};
|
|
export const getCoverFilename = (book: Book) => {
|
|
return `${book.hash}/cover.png`;
|
|
};
|
|
export const getConfigFilename = (book: Book) => {
|
|
return `${book.hash}/config.json`;
|
|
};
|
|
export const isBookFile = (filename: string) => {
|
|
return Object.values(EXTS).includes(filename.split('.').pop()!);
|
|
};
|
|
|
|
export const INIT_BOOK_CONFIG: BookConfig = {
|
|
updatedAt: 0,
|
|
};
|
|
|
|
export interface LanguageMap {
|
|
[key: string]: string;
|
|
}
|
|
|
|
export interface Contributor {
|
|
name: LanguageMap;
|
|
}
|
|
|
|
const formatLanguageMap = (x: string | LanguageMap): string => {
|
|
const userLang = getUserLang();
|
|
if (!x) return '';
|
|
if (typeof x === 'string') return x;
|
|
const keys = Object.keys(x);
|
|
return x[userLang] || x[keys[0]!]!;
|
|
};
|
|
|
|
export const listFormater = (narrow = false, lang = '') => {
|
|
lang = lang ? lang : getUserLang();
|
|
if (narrow) {
|
|
return new Intl.ListFormat('en', { style: 'narrow', type: 'unit' });
|
|
} else {
|
|
return new Intl.ListFormat(lang, { style: 'long', type: 'conjunction' });
|
|
}
|
|
};
|
|
|
|
export const getBookLangCode = (lang: string | string[] | undefined) => {
|
|
try {
|
|
const bookLang = typeof lang === 'string' ? lang : lang?.[0];
|
|
return bookLang ? bookLang.split('-')[0]! : '';
|
|
} catch {
|
|
return '';
|
|
}
|
|
};
|
|
|
|
export const flattenContributors = (
|
|
contributors: string | string[] | Contributor | Contributor[],
|
|
) => {
|
|
if (!contributors) return '';
|
|
return Array.isArray(contributors)
|
|
? contributors
|
|
.map((contributor) =>
|
|
typeof contributor === 'string' ? contributor : formatLanguageMap(contributor?.name),
|
|
)
|
|
.join(', ')
|
|
: typeof contributors === 'string'
|
|
? contributors
|
|
: 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'
|
|
? formatAuthorName(contributor, lastNameFirst)
|
|
: formatAuthorName(formatLanguageMap(contributor?.name), lastNameFirst),
|
|
),
|
|
)
|
|
: typeof contributors === 'string'
|
|
? formatAuthorName(contributors, lastNameFirst)
|
|
: formatAuthorName(formatLanguageMap(contributors?.name), lastNameFirst);
|
|
};
|
|
|
|
export const formatTitle = (title: string | LanguageMap) => {
|
|
return typeof title === 'string' ? title : formatLanguageMap(title);
|
|
};
|
|
|
|
export const formatPublisher = (publisher: string | LanguageMap) => {
|
|
return typeof publisher === 'string' ? publisher : formatLanguageMap(publisher);
|
|
};
|
|
|
|
const langCodeToLangName = (langCode: string) => {
|
|
return SUPPORTED_LANGS[langCode] || langCode.toUpperCase();
|
|
};
|
|
|
|
export const formatLanguage = (lang: string | string[] | undefined): string => {
|
|
return Array.isArray(lang)
|
|
? lang.map(langCodeToLangName).join(', ')
|
|
: langCodeToLangName(lang || '');
|
|
};
|
|
|
|
// Should return valid ISO-639-1 language code, fallback to 'en' if not valid
|
|
export const getPrimaryLanguage = (lang: string | string[] | undefined) => {
|
|
const primaryLang = Array.isArray(lang) ? lang[0] : lang;
|
|
if (isValidLang(primaryLang)) {
|
|
const normalizedLang = normalizedLangCode(primaryLang);
|
|
return code6392to6391(normalizedLang) || normalizedLang;
|
|
}
|
|
return 'en';
|
|
};
|
|
|
|
export const formatDate = (date: string | number | Date | null | undefined, isUTC = false) => {
|
|
if (!date) return;
|
|
const userLang = getUserLang();
|
|
try {
|
|
return new Date(date).toLocaleDateString(userLang, {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
timeZone: isUTC ? 'UTC' : undefined,
|
|
});
|
|
} catch {
|
|
return;
|
|
}
|
|
};
|
|
|
|
export const formatFileSize = (size: number | null) => {
|
|
if (size === null) return '';
|
|
const formatter = new Intl.NumberFormat('en', {
|
|
style: 'unit',
|
|
unit: 'byte',
|
|
unitDisplay: 'narrow',
|
|
notation: 'compact',
|
|
compactDisplay: 'short',
|
|
});
|
|
return formatter.format(size);
|
|
};
|
|
|
|
export const getCurrentPage = (book: Book, progress: BookProgress) => {
|
|
const bookFormat = book.format;
|
|
const { section, pageinfo } = progress;
|
|
return bookFormat === 'PDF'
|
|
? section
|
|
? section.current + 1
|
|
: 0
|
|
: pageinfo
|
|
? pageinfo.current + 1
|
|
: 0;
|
|
};
|
|
|
|
export const getBookDirFromWritingMode = (writingMode: WritingMode) => {
|
|
switch (writingMode) {
|
|
case 'horizontal-tb':
|
|
return 'ltr';
|
|
case 'horizontal-rl':
|
|
case 'vertical-rl':
|
|
return 'rtl';
|
|
default:
|
|
return 'auto';
|
|
}
|
|
};
|
|
|
|
export const getBookDirFromLanguage = (language: string | string[] | undefined) => {
|
|
const lang = getPrimaryLanguage(language) || '';
|
|
return getDirFromLanguage(lang);
|
|
};
|