feat: localize number in progress info in vertical layout (#2255)
This commit is contained in:
@@ -622,5 +622,6 @@
|
||||
"Opacity": "شفافية",
|
||||
"Size": "حجم",
|
||||
"Cover": "غطاء",
|
||||
"Contain": "احتواء"
|
||||
"Contain": "احتواء",
|
||||
"{{number}} pages left in chapter": "تبقّت {{number}} صفحات في هذا الفصل"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "স্বচ্ছতা",
|
||||
"Size": "আকার",
|
||||
"Cover": "ঢাকা",
|
||||
"Contain": "ধারণ"
|
||||
"Contain": "ধারণ",
|
||||
"{{number}} pages left in chapter": "অধ্যায়ে {{number}} পৃষ্ঠা বাকি"
|
||||
}
|
||||
|
||||
@@ -602,5 +602,6 @@
|
||||
"Opacity": "སྤོ་སྐོར་གྱི་འོད་ཟེར།",
|
||||
"Size": "ཨང་",
|
||||
"Cover": "དེབ་གྱི་སྤྱོད་བྱས་མ་ཐུབ།",
|
||||
"Contain": "འབྱོར་བ།"
|
||||
"Contain": "འབྱོར་བ།",
|
||||
"{{number}} pages left in chapter": "དོན་ཚན་ནང་ཤོག་ཨང་ {{number}} ལོག་གི་འདུག།"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "Opazität",
|
||||
"Size": "Größe",
|
||||
"Cover": "Cover",
|
||||
"Contain": "Inhalt"
|
||||
"Contain": "Inhalt",
|
||||
"{{number}} pages left in chapter": "{{number}} Seiten verbleibend im Kapitel"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "Αδιαφάνεια",
|
||||
"Size": "Μέγεθος",
|
||||
"Cover": "Εξώφυλλο",
|
||||
"Contain": "Περιέχει"
|
||||
"Contain": "Περιέχει",
|
||||
"{{number}} pages left in chapter": "Μένουν {{number}} σελίδες στο κεφάλαιο"
|
||||
}
|
||||
|
||||
@@ -610,5 +610,6 @@
|
||||
"Opacity": "Opacidad",
|
||||
"Size": "Tamaño",
|
||||
"Cover": "Cubierta",
|
||||
"Contain": "Contener"
|
||||
"Contain": "Contener",
|
||||
"{{number}} pages left in chapter": "{{number}} páginas restantes en el capítulo"
|
||||
}
|
||||
|
||||
@@ -610,5 +610,6 @@
|
||||
"Opacity": "Opacité",
|
||||
"Size": "Taille",
|
||||
"Cover": "Couverture",
|
||||
"Contain": "Contenir"
|
||||
"Contain": "Contenir",
|
||||
"{{number}} pages left in chapter": "{{number}} pages restantes dans le chapitre"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "अस्पष्टता",
|
||||
"Size": "आकार",
|
||||
"Cover": "कवर",
|
||||
"Contain": "समाहित करें"
|
||||
"Contain": "समाहित करें",
|
||||
"{{number}} pages left in chapter": "इस अध्याय में {{number}} पृष्ठ शेष हैं"
|
||||
}
|
||||
|
||||
@@ -602,5 +602,6 @@
|
||||
"Opacity": "Keburaman",
|
||||
"Size": "Ukuran",
|
||||
"Cover": "Sampul",
|
||||
"Contain": "Mengandung"
|
||||
"Contain": "Mengandung",
|
||||
"{{number}} pages left in chapter": "{{number}} halaman tersisa di bab"
|
||||
}
|
||||
|
||||
@@ -610,5 +610,6 @@
|
||||
"Opacity": "Opacità",
|
||||
"Size": "Dimensione",
|
||||
"Cover": "Copertura",
|
||||
"Contain": "Contenere"
|
||||
"Contain": "Contenere",
|
||||
"{{number}} pages left in chapter": "{{number}} pagine rimaste nel capitolo"
|
||||
}
|
||||
|
||||
@@ -602,5 +602,6 @@
|
||||
"Opacity": "不透明度",
|
||||
"Size": "サイズ",
|
||||
"Cover": "カバー",
|
||||
"Contain": "含む"
|
||||
"Contain": "含む",
|
||||
"{{number}} pages left in chapter": "章に{{number}}ページ残り"
|
||||
}
|
||||
|
||||
@@ -602,5 +602,6 @@
|
||||
"Opacity": "불투명도",
|
||||
"Size": "크기",
|
||||
"Cover": "표지",
|
||||
"Contain": "포함"
|
||||
"Contain": "포함",
|
||||
"{{number}} pages left in chapter": "남은 페이지 {{number}}장"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "Doorzichtigheid",
|
||||
"Size": "Grootte",
|
||||
"Cover": "Omslag",
|
||||
"Contain": "Bevatten"
|
||||
"Contain": "Bevatten",
|
||||
"{{number}} pages left in chapter": "Nog {{number}} pagina’s in hoofdstuk"
|
||||
}
|
||||
|
||||
@@ -614,5 +614,6 @@
|
||||
"Opacity": "Przezroczystość",
|
||||
"Size": "Rozmiar",
|
||||
"Cover": "Okładka",
|
||||
"Contain": "Zawierać"
|
||||
"Contain": "Zawierać",
|
||||
"{{number}} pages left in chapter": "Pozostało {{number}} stron w rozdziale"
|
||||
}
|
||||
|
||||
@@ -610,5 +610,6 @@
|
||||
"Opacity": "Opacidade",
|
||||
"Size": "Tamanho",
|
||||
"Cover": "Capa",
|
||||
"Contain": "Contém"
|
||||
"Contain": "Contém",
|
||||
"{{number}} pages left in chapter": "Faltam {{number}} páginas neste capítulo"
|
||||
}
|
||||
|
||||
@@ -614,5 +614,6 @@
|
||||
"Opacity": "Непрозрачность",
|
||||
"Size": "Размер",
|
||||
"Cover": "Обложка",
|
||||
"Contain": "Содержать"
|
||||
"Contain": "Содержать",
|
||||
"{{number}} pages left in chapter": "Осталось {{number}} страниц в главе"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "පැහැදිලිත්වය",
|
||||
"Size": "ප්රමාණය",
|
||||
"Cover": "ආවරණය",
|
||||
"Contain": "අඩංගු වන්න"
|
||||
"Contain": "අඩංගු වන්න",
|
||||
"{{number}} pages left in chapter": "පරිච්ඡේදයේ පිටු {{number}} ඉතිරිව ඇත"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "Opacitet",
|
||||
"Size": "Storlek",
|
||||
"Cover": "Omslag",
|
||||
"Contain": "Innehålla"
|
||||
"Contain": "Innehålla",
|
||||
"{{number}} pages left in chapter": "{{number}} sidor kvar i kapitlet"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "பார்வை",
|
||||
"Size": "அளவு",
|
||||
"Cover": "மூடு",
|
||||
"Contain": "அடங்கும்"
|
||||
"Contain": "அடங்கும்",
|
||||
"{{number}} pages left in chapter": "அத்தியாயத்தில் {{number}} பக்கங்கள் மீதமுள்ளன"
|
||||
}
|
||||
|
||||
@@ -602,5 +602,6 @@
|
||||
"Opacity": "ความโปร่งใส",
|
||||
"Size": "ขนาด",
|
||||
"Cover": "ปก",
|
||||
"Contain": "บรรจุ"
|
||||
"Contain": "บรรจุ",
|
||||
"{{number}} pages left in chapter": "เหลือ {{number}} หน้าในบท"
|
||||
}
|
||||
|
||||
@@ -606,5 +606,6 @@
|
||||
"Opacity": "Opaklık",
|
||||
"Size": "Boyut",
|
||||
"Cover": "Kapak",
|
||||
"Contain": "İçerir"
|
||||
"Contain": "İçerir",
|
||||
"{{number}} pages left in chapter": "Bu bölümde {{number}} sayfa kaldı"
|
||||
}
|
||||
|
||||
@@ -614,5 +614,6 @@
|
||||
"Opacity": "Непрозорість",
|
||||
"Size": "Розмір",
|
||||
"Cover": "Обкладинка",
|
||||
"Contain": "Вміщати"
|
||||
"Contain": "Вміщати",
|
||||
"{{number}} pages left in chapter": "Залишилось {{number}} сторінок у розділі"
|
||||
}
|
||||
|
||||
@@ -602,5 +602,6 @@
|
||||
"Opacity": "Độ mờ",
|
||||
"Size": "Kích thước",
|
||||
"Cover": "Bìa",
|
||||
"Contain": "Chứa"
|
||||
"Contain": "Chứa",
|
||||
"{{number}} pages left in chapter": "Còn {{number}} trang trong chương"
|
||||
}
|
||||
|
||||
@@ -601,6 +601,7 @@
|
||||
"Import Image": "导入图片",
|
||||
"Opacity": "不透明度",
|
||||
"Size": "大小",
|
||||
"Cover": "封面",
|
||||
"Contain": "包含"
|
||||
"Cover": "覆盖",
|
||||
"Contain": "适应",
|
||||
"{{number}} pages left in chapter": "本章剩余{{number}}页"
|
||||
}
|
||||
|
||||
@@ -601,6 +601,7 @@
|
||||
"Import Image": "匯入圖片",
|
||||
"Opacity": "不透明度",
|
||||
"Size": "大小",
|
||||
"Cover": "封面",
|
||||
"Contain": "包含"
|
||||
"Cover": "覆蓋",
|
||||
"Contain": "適應",
|
||||
"{{number}} pages left in chapter": "本章剩餘{{number}}頁"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useReaderStore } from '@/store/readerStore';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useBookDataStore } from '@/store/bookDataStore';
|
||||
import { formatReadingProgress } from '@/utils/progress';
|
||||
import { formatNumber, formatProgress } from '@/utils/progress';
|
||||
|
||||
interface PageInfoProps {
|
||||
bookKey: string;
|
||||
@@ -40,24 +40,34 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
const isVertical = viewSettings.vertical;
|
||||
const { progressStyle: readingProgressStyle } = viewSettings;
|
||||
|
||||
const formatTemplate =
|
||||
const template =
|
||||
readingProgressStyle === 'fraction'
|
||||
? isVertical
|
||||
? '{current} · {total}'
|
||||
: '{current} / {total}'
|
||||
: '{percent}%';
|
||||
|
||||
const pageProgress = bookData?.isFixedLayout ? section : pageinfo;
|
||||
const progressInfo = bookData?.isFixedLayout
|
||||
? formatReadingProgress(pageProgress?.current, pageProgress?.total, formatTemplate)
|
||||
: formatReadingProgress(pageProgress?.current, pageProgress?.total, formatTemplate);
|
||||
const lang = localStorage?.getItem('i18nextLng') || '';
|
||||
const localize = isVertical && lang.toLowerCase().startsWith('zh');
|
||||
const progress = bookData?.isFixedLayout ? section : pageinfo;
|
||||
const progressInfo = formatProgress(progress?.current, progress?.total, template, localize, lang);
|
||||
|
||||
const timeLeft = timeinfo
|
||||
? _('{{time}} min left in chapter', { time: Math.round(timeinfo.section) })
|
||||
? _('{{time}} min left in chapter', {
|
||||
time: formatNumber(Math.round(timeinfo.section), localize, lang),
|
||||
})
|
||||
: '';
|
||||
const { page = 0, pages = 0 } = view?.renderer || {};
|
||||
const pageLeft =
|
||||
pages - 1 > page ? _('{{count}} pages left in chapter', { count: pages - 1 - page }) : '';
|
||||
pages - 1 > page
|
||||
? localize
|
||||
? _('{{number}} pages left in chapter', {
|
||||
number: formatNumber(pages - page - 1, localize, lang),
|
||||
})
|
||||
: _('{{count}} pages left in chapter', {
|
||||
count: pages - page - 1,
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -68,10 +78,10 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
isScrolled && !isVertical && 'bg-base-100',
|
||||
)}
|
||||
aria-label={[
|
||||
pageProgress
|
||||
progress
|
||||
? _('On {{current}} of {{total}} page', {
|
||||
current: pageProgress.current + 1,
|
||||
total: pageProgress.total,
|
||||
current: progress.current + 1,
|
||||
total: progress.total,
|
||||
})
|
||||
: '',
|
||||
timeLeft,
|
||||
@@ -99,7 +109,7 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
<div
|
||||
aria-hidden='true'
|
||||
className={clsx(
|
||||
'flex items-center justify-center',
|
||||
'flex items-center justify-between',
|
||||
isVertical ? 'h-full' : 'h-[52px] w-full',
|
||||
)}
|
||||
>
|
||||
@@ -108,7 +118,11 @@ const ProgressInfoView: React.FC<PageInfoProps> = ({
|
||||
) : viewSettings.showRemainingPages ? (
|
||||
<span className='text-start'>{pageLeft}</span>
|
||||
) : null}
|
||||
{viewSettings.showProgressInfo && <span className='ms-auto text-end'>{progressInfo}</span>}
|
||||
{viewSettings.showProgressInfo && (
|
||||
<span className={clsx('text-end', isVertical ? 'mt-auto' : 'ms-auto')}>
|
||||
{progressInfo}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
export const localizeNumber = (num: number, language: string, isIndex = false): string => {
|
||||
const lang = language.toLowerCase();
|
||||
const isChinese = lang.startsWith('zh');
|
||||
if (!isChinese) {
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
const isTraditional = lang.includes('tw') || lang.includes('hk');
|
||||
|
||||
const baseDigits = isTraditional ? '零壹貳叁肆伍陸柒捌玖' : '零一二三四五六七八九';
|
||||
const zeroChar = isIndex ? '〇' : '零';
|
||||
const digits = baseDigits.replace('零', zeroChar);
|
||||
const units = isTraditional
|
||||
? ['', '拾', '佰', '仟', '萬', '億']
|
||||
: ['', '十', '百', '千', '万', '亿'];
|
||||
|
||||
const str = String(num);
|
||||
const len = str.length;
|
||||
let result = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
const n = Number(str[i]);
|
||||
const pos = len - i - 1;
|
||||
if (n !== 0) {
|
||||
result += digits[n]! + units[pos % 4]!;
|
||||
} else if (!result.endsWith(zeroChar) && i < len - 1) {
|
||||
result += zeroChar;
|
||||
}
|
||||
if (pos === 4) result += units[4];
|
||||
if (pos === 8) result += units[5];
|
||||
}
|
||||
result = result
|
||||
.replace(new RegExp(`${zeroChar}+$`), '')
|
||||
.replace(`${zeroChar}萬`, '萬')
|
||||
.replace(`${zeroChar}万`, '万')
|
||||
.replace(new RegExp(`${zeroChar}+`, 'g'), zeroChar);
|
||||
|
||||
if (!isIndex) {
|
||||
if (isTraditional) {
|
||||
result = result.replace(
|
||||
/^貳$|(?<![壹貳叁肆伍陸柒捌玖])貳(?![佰仟萬億壹貳叁肆伍陸柒捌玖])/g,
|
||||
'兩',
|
||||
);
|
||||
} else {
|
||||
result = result.replace(
|
||||
/^二$|(?<![一二三四五六七八九])二(?![百千万亿一二三四五六七八九])/g,
|
||||
'两',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -1,14 +1,31 @@
|
||||
export function formatReadingProgress(
|
||||
import { localizeNumber } from './number';
|
||||
|
||||
export function formatProgress(
|
||||
current: number | undefined,
|
||||
total: number | undefined,
|
||||
template: string,
|
||||
localize: boolean = false,
|
||||
language: string = 'en',
|
||||
): string {
|
||||
if (current !== undefined && total !== undefined && total > 0 && current >= 0) {
|
||||
const currentStr = localize ? localizeNumber(current + 1, language, true) : String(current + 1);
|
||||
const totalStr = localize ? localizeNumber(total, language, true) : String(total);
|
||||
return template
|
||||
.replace('{current}', String(current + 1))
|
||||
.replace('{total}', String(total))
|
||||
.replace('{current}', currentStr)
|
||||
.replace('{total}', totalStr)
|
||||
.replace('{percent}', (((current + 1) / total) * 100).toFixed(1));
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function formatNumber(
|
||||
number: number | undefined,
|
||||
localize: boolean = false,
|
||||
language: string = 'en',
|
||||
): string {
|
||||
if (number === undefined || number < 0) {
|
||||
return '';
|
||||
}
|
||||
return localize ? localizeNumber(number, language) : String(number);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user