@jjlmoya/utils-books 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm-publish.yml +40 -0
- package/.gitignore +6 -0
- package/.stylelintrc.json +98 -0
- package/astro.config.mjs +19 -0
- package/eslint.config.js +201 -0
- package/package.json +79 -0
- package/prompts/create_tool.md +98 -0
- package/prompts/i18n/de.md +16 -0
- package/prompts/i18n/en.md +16 -0
- package/prompts/i18n/es.md +16 -0
- package/prompts/i18n/fr.md +16 -0
- package/prompts/i18n/id.md +16 -0
- package/prompts/i18n/it.md +16 -0
- package/prompts/i18n/ja.md +16 -0
- package/prompts/i18n/ko.md +16 -0
- package/prompts/i18n/nl.md +16 -0
- package/prompts/i18n/pl.md +16 -0
- package/prompts/i18n/pt.md +16 -0
- package/prompts/i18n/ru.md +16 -0
- package/prompts/i18n/sv.md +16 -0
- package/prompts/i18n/tr.md +16 -0
- package/prompts/i18n/zh.md +16 -0
- package/prompts/seo.md +58 -0
- package/prompts/translations/french.md +33 -0
- package/scripts/postinstall.mjs +27 -0
- package/src/category/BooksCategorySEO.astro +9 -0
- package/src/category/i18n/de.ts +21 -0
- package/src/category/i18n/en.ts +21 -0
- package/src/category/i18n/es.ts +21 -0
- package/src/category/i18n/fr.ts +21 -0
- package/src/category/i18n/id.ts +21 -0
- package/src/category/i18n/it.ts +21 -0
- package/src/category/i18n/ja.ts +21 -0
- package/src/category/i18n/ko.ts +21 -0
- package/src/category/i18n/nl.ts +21 -0
- package/src/category/i18n/pl.ts +21 -0
- package/src/category/i18n/pt.ts +21 -0
- package/src/category/i18n/ru.ts +21 -0
- package/src/category/i18n/sv.ts +21 -0
- package/src/category/i18n/tr.ts +21 -0
- package/src/category/i18n/zh.ts +21 -0
- package/src/category/index.ts +24 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +10 -0
- package/src/entries.ts +6 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +20 -0
- package/src/layouts/PreviewLayout.astro +118 -0
- package/src/pages/[locale]/[slug].astro +164 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/bibliography_wellformed_export.test.ts +46 -0
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/faq_count.test.ts +18 -0
- package/src/tests/i18n_coverage.test.ts +34 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/locale_completeness.test.ts +23 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_em_dash.test.ts +47 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/qa-test-helpers.ts +32 -0
- package/src/tests/qa_bibliography_links.test.ts +54 -0
- package/src/tests/qa_claim_evidence.test.ts +69 -0
- package/src/tests/qa_logic_reference_coverage.test.ts +46 -0
- package/src/tests/qa_runtime_i18n.test.ts +100 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tests/seo_length.test.ts +23 -0
- package/src/tests/seo_parity.test.ts +60 -0
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/seo_wellformed_export.test.ts +65 -0
- package/src/tests/shared-test-helpers.ts +56 -0
- package/src/tests/slug_language_code_format.test.ts +23 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tests/spanish_leakage.test.ts +175 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_exports.test.ts +34 -0
- package/src/tests/tool_validation.test.ts +16 -0
- package/src/tests/translation_copy.test.ts +127 -0
- package/src/tool/book-pagination-and-spine-calculator/bibliography.astro +16 -0
- package/src/tool/book-pagination-and-spine-calculator/bibliography.ts +7 -0
- package/src/tool/book-pagination-and-spine-calculator/book-pagination-and-spine-calculator.css +298 -0
- package/src/tool/book-pagination-and-spine-calculator/component.astro +40 -0
- package/src/tool/book-pagination-and-spine-calculator/controller.ts +87 -0
- package/src/tool/book-pagination-and-spine-calculator/dom-views.ts +21 -0
- package/src/tool/book-pagination-and-spine-calculator/entry.ts +27 -0
- package/src/tool/book-pagination-and-spine-calculator/evaluator.ts +12 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/de.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/en.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/es.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/fr.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/id.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/it.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/ja.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/ko.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/nl.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/pl.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/pt.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/ru.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/sv.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/tr.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/i18n/zh.ts +78 -0
- package/src/tool/book-pagination-and-spine-calculator/index.ts +11 -0
- package/src/tool/book-pagination-and-spine-calculator/logic.test.ts +21 -0
- package/src/tool/book-pagination-and-spine-calculator/logic.ts +52 -0
- package/src/tool/book-pagination-and-spine-calculator/seo.astro +16 -0
- package/src/tool/book-pagination-and-spine-calculator/storage.ts +22 -0
- package/src/tool/book-pagination-and-spine-calculator/ui.ts +35 -0
- package/src/tools.ts +5 -0
- package/src/types.ts +69 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
2
|
+
import type { BookPaginationUI } from '../ui';
|
|
3
|
+
|
|
4
|
+
const ui: BookPaginationUI = {
|
|
5
|
+
title: 'Kitap sayfası ve sırt hesaplayıcı',
|
|
6
|
+
scenarioLabel: 'Senaryo adı',
|
|
7
|
+
scenarioPlaceholder: 'Kitabım',
|
|
8
|
+
wordsLabel: 'El yazması kelimeleri',
|
|
9
|
+
widthLabel: 'Kesim genişliği',
|
|
10
|
+
heightLabel: 'Kesim yüksekliği',
|
|
11
|
+
marginLabel: 'İç kenar boşluğu',
|
|
12
|
+
fontSizeLabel: 'Yazı boyutu (pt)',
|
|
13
|
+
lineHeightLabel: 'Satır aralığı (×)',
|
|
14
|
+
paperLabel: 'Kitap ayarları',
|
|
15
|
+
caliperLabel: 'Kâğıt kalınlığı',
|
|
16
|
+
bindingLabel: 'Cilt',
|
|
17
|
+
softcoverLabel: 'Karton kapak',
|
|
18
|
+
hardcoverLabel: 'Sert kapak',
|
|
19
|
+
saddleLabel: 'Tel dikiş',
|
|
20
|
+
presetLabel: 'Bir formatla başlayın',
|
|
21
|
+
novelPreset: 'Roman',
|
|
22
|
+
largePrintPreset: 'Büyük punto',
|
|
23
|
+
workbookPreset: 'Çalışma kitabı',
|
|
24
|
+
metricLabel: 'Metrik mm',
|
|
25
|
+
imperialLabel: 'İngiliz ölçüsü inç',
|
|
26
|
+
resetLabel: 'Roman ayarlarına dön',
|
|
27
|
+
pagesLabel: 'Tahmini sayfa',
|
|
28
|
+
spineLabel: 'Sırt genişliği',
|
|
29
|
+
coverLabel: 'Kapak açılımı',
|
|
30
|
+
wordsPerPageLabel: 'Sayfa başına kelime',
|
|
31
|
+
linesLabel: 'Sayfa başına satır',
|
|
32
|
+
previewLabel: 'Kitap maketi',
|
|
33
|
+
previewHint: 'Kâğıt bloğu büyüdükçe sırt da genişler. Sonucu üretim tahmini olarak kullanın.',
|
|
34
|
+
methodTitle: 'Baskıdan önce kitabın biçimini belirleyin',
|
|
35
|
+
methodHint: 'Bir format seçin, ardından sayfa sayısı ile kapak ölçüsünün birlikte nasıl değiştiğini görmek için yazıyı ve kâğıdı ayarlayın.',
|
|
36
|
+
sourceLabel: 'Tahmin, girdiğiniz değerleri kullanır. Son sırt ölçüsünü matbaa, kâğıt ve cilt yöntemiyle doğrulayın.',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const seo: ToolLocaleContent<BookPaginationUI>['seo'] = [
|
|
40
|
+
{ type: 'title', text: 'El yazmasından kitap sayfası ve sırt genişliği hesaplayın', level: 2 },
|
|
41
|
+
{ type: 'paragraph', html: 'Kelime sayısını ve sayfa tasarımını basılı bir kitap için pratik bir ilk tahmine dönüştürün. Bu hesaplayıcı, kullanılabilir metin alanını, yaklaşık sayfa başına kelime sayısını, cilt katlama kurallarını, kâğıt kalınlığını, sırt genişliğini ve tam kapak açılımını tek bir görsel çalışma alanında modeller.' },
|
|
42
|
+
{ type: 'title', text: 'Hesaplayıcının tahmin ettikleri', level: 2 },
|
|
43
|
+
{ type: 'paragraph', html: 'Sayfa tahmini; kesim ölçülerini, kenar boşluklarını, yazı boyutunu ve satır aralığını kullanarak satır başına karakter ve sayfa başına satır sayısını yaklaşık hesaplar. Sonuç, karton ve sert kapak için çift sayıya, tel dikişli çalışma kitapları için dördün katına yuvarlanır. Sırt genişliği daha sonra kâğıt bloğuna küçük bir cilt payı eklenerek bulunur.' },
|
|
44
|
+
{ type: 'list', items: ['Kompakt bir cep kitabı için roman ön ayarını başlangıç noktası olarak kullanın.', 'Büyük yazı, sayfa başına kelime sayısını değiştiriyorsa büyük puntoyu seçin.', 'Daha geniş sayfa ve dördün katı sayfa sayısı için çalışma kitabını kullanın.', 'Kitabın fiziksel ölçüsünü değiştirmeden milimetre ve inç arasında geçiş yapın.'] },
|
|
45
|
+
{ type: 'title', text: 'Matbaa provası değil üretim tahmini', level: 2 },
|
|
46
|
+
{ type: 'paragraph', html: 'Kâğıt üreticileri kalınlığı farklı biçimlerde belirtir, yazı karakterlerinin genişliği değişir ve matbaalar kendi forma yerleştirme ve cilt toleranslarını uygular. Sonucu yazarlar, editörler ve tasarımcılar için hızlı bir planlama aracı olarak değerlendirin. Sipariş vermeden veya dosya dışa aktarmadan önce son kapak şablonunu matbaa özellikleriyle karşılaştırın.' },
|
|
47
|
+
{ type: 'tip', title: 'Okunabilir yazı ayarlarını koruyun', html: 'Daha küçük yazı veya daha sıkı satır aralığı, sırtı inceltse bile kitabı daha ucuz gösterebilir ve okumayı zorlaştırabilir. Ön ayarları görsel olarak karşılaştırın ve kararı okuma deneyimine göre verin.' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const content: ToolLocaleContent<BookPaginationUI> = {
|
|
51
|
+
slug: 'kitap-sayfa-ve-sirt-hesaplayici',
|
|
52
|
+
title: 'Kitap sayfası ve sırt hesaplayıcı',
|
|
53
|
+
description: 'El yazması, kesim ölçüsü, tipografi, kâğıt ve ciltten basılı sayfa, sırt genişliği ve kapak açılımı tahmini alın.',
|
|
54
|
+
ui,
|
|
55
|
+
seo,
|
|
56
|
+
faq: [
|
|
57
|
+
{ question: 'Sayfa tahmini hangi verileri kullanır?', answer: 'El yazması kelimelerini, kesim ölçüsünü, kenar boşluklarını, yazı boyutunu ve satır aralığını kullanarak karakter, satır ve sayfa başına kelime sayısını tahmin eder.' },
|
|
58
|
+
{ question: 'Sayfalar neden yuvarlanır?', answer: 'Karton ve sert kapak tahminleri çift sayıya yuvarlanır. Tel dikişli kitaplar, katlanmış formalar nedeniyle dördün katına yuvarlanır.' },
|
|
59
|
+
{ question: 'Sırt genişliği nasıl tahmin edilir?', answer: 'Hesaplayıcı sayfa sayısını yaprak başına kâğıt kalınlığıyla çarpar, bloğu ikiye böler ve seçilen cilt için küçük bir pay ekler.' },
|
|
60
|
+
{ question: 'Sonuç matbaaya gönderilmeye hazır mı?', answer: 'Hayır. Bu bir planlama tahminidir. Kâğıt kalınlığını, kapak ölçülerini, taşmayı ve cilt toleranslarını matbaanızla doğrulayın.' },
|
|
61
|
+
],
|
|
62
|
+
bibliography: [
|
|
63
|
+
{ name: 'ISO 216:2007 Kâğıt boyutları', url: 'https://www.iso.org/standard/36631.html' },
|
|
64
|
+
{ name: 'Instituto Nacional de Estadística. Producción editorial de libros', url: 'https://www.ine.es/dyngs/INEbase/es/operacion.htm?c=Estadistica_C&cid=1254736176767&idp=1254735573113&menu=ultiDatos' },
|
|
65
|
+
{ name: 'UNESCO küresel yayıncılık sektörü raporu', url: 'https://doi.org/10.58337/SRUH6078' },
|
|
66
|
+
],
|
|
67
|
+
howTo: [
|
|
68
|
+
{ name: 'Bir format seçin', text: 'Roman, büyük punto veya çalışma kitabı ön ayarıyla başlayın.' },
|
|
69
|
+
{ name: 'El yazmasını ayarlayın', text: 'Kelime sayısını girin; ölçü, kenar boşluğu, yazı tipi ve satır aralığını ayarlayın.' },
|
|
70
|
+
{ name: 'Kâğıt ve cilt seçin', text: 'Kâğıt kalınlığını girin ve karton kapak, sert kapak veya tel dikişi seçin.' },
|
|
71
|
+
{ name: 'Kapağı inceleyin', text: 'Tahmini sırt genişliğini ve kapak açılımını tasarım dosyanız için başlangıç olarak kullanın.' },
|
|
72
|
+
],
|
|
73
|
+
schemas: [
|
|
74
|
+
{ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Kitap sayfası ve sırt hesaplayıcı', applicationCategory: 'DesignApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'TRY' } },
|
|
75
|
+
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: [{ '@type': 'Question', name: 'Sayfa tahmini hangi verileri kullanır?', acceptedAnswer: { '@type': 'Answer', text: 'Kelime, ölçü, kenar boşluğu, yazı boyutu ve satır aralığını kullanır.' } }, { '@type': 'Question', name: 'Sırt genişliği nasıl tahmin edilir?', acceptedAnswer: { '@type': 'Answer', text: 'Sayfa sayısını, kâğıt kalınlığını ve cilt payını kullanır.' } }] },
|
|
76
|
+
{ '@context': 'https://schema.org', '@type': 'HowTo', name: 'Kitap sayfası ve sırt genişliği tahmin etmek', step: [{ '@type': 'HowToStep', name: 'Bir format seçin', text: 'Bir ön ayar seçin.' }, { '@type': 'HowToStep', name: 'El yazmasını ayarlayın', text: 'Kelime ve tipografi değerlerini girin.' }, { '@type': 'HowToStep', name: 'Kâğıt ve cilt seçin', text: 'Kalınlık ve cilt yöntemini ayarlayın.' }, { '@type': 'HowToStep', name: 'Kapağı inceleyin', text: 'Sırt ve kapak tahminini kullanın.' }] },
|
|
77
|
+
],
|
|
78
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
2
|
+
import type { BookPaginationUI } from '../ui';
|
|
3
|
+
|
|
4
|
+
const ui: BookPaginationUI = {
|
|
5
|
+
title: '图书页数与书脊宽度计算器',
|
|
6
|
+
scenarioLabel: '方案名称',
|
|
7
|
+
scenarioPlaceholder: '我的书',
|
|
8
|
+
wordsLabel: '手稿字数',
|
|
9
|
+
widthLabel: '成品宽度',
|
|
10
|
+
heightLabel: '成品高度',
|
|
11
|
+
marginLabel: '内侧页边距',
|
|
12
|
+
fontSizeLabel: '字号 (pt)',
|
|
13
|
+
lineHeightLabel: '行距 (×)',
|
|
14
|
+
paperLabel: '图书设置',
|
|
15
|
+
caliperLabel: '纸张厚度',
|
|
16
|
+
bindingLabel: '装订方式',
|
|
17
|
+
softcoverLabel: '平装',
|
|
18
|
+
hardcoverLabel: '精装',
|
|
19
|
+
saddleLabel: '骑马钉',
|
|
20
|
+
presetLabel: '从一种格式开始',
|
|
21
|
+
novelPreset: '小说',
|
|
22
|
+
largePrintPreset: '大字版',
|
|
23
|
+
workbookPreset: '练习册',
|
|
24
|
+
metricLabel: '公制 mm',
|
|
25
|
+
imperialLabel: '英制 inch',
|
|
26
|
+
resetLabel: '重置为小说',
|
|
27
|
+
pagesLabel: '预计页数',
|
|
28
|
+
spineLabel: '书脊宽度',
|
|
29
|
+
coverLabel: '书封展开尺寸',
|
|
30
|
+
wordsPerPageLabel: '每页字数',
|
|
31
|
+
linesLabel: '每页行数',
|
|
32
|
+
previewLabel: '图书模型',
|
|
33
|
+
previewHint: '纸张叠层越厚,书脊就越宽。请把结果作为制作估算使用。',
|
|
34
|
+
methodTitle: '在印刷前确定图书形态',
|
|
35
|
+
methodHint: '选择一种格式,再调整文字和纸张,观察页数与书封尺寸如何一起变化。',
|
|
36
|
+
sourceLabel: '估算基于你的输入。请向印刷厂确认最终书脊,并核对纸张和装订方式。',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const seo: ToolLocaleContent<BookPaginationUI>['seo'] = [
|
|
40
|
+
{ type: 'title', text: '根据手稿计算图书页数和书脊宽度', level: 2 },
|
|
41
|
+
{ type: 'paragraph', html: '把字数和页面设计转换为印刷图书的实用初步估算。这个计算器会在同一个可视化工作区中模拟可用正文区域、每页大致字数、装订折页规则、纸张厚度、书脊宽度和整张书封的展开尺寸。' },
|
|
42
|
+
{ type: 'title', text: '计算器会估算什么', level: 2 },
|
|
43
|
+
{ type: 'paragraph', html: '页数估算使用成品尺寸、页边距、字号和行距,近似计算每行字数与每页行数。平装和精装会取整为偶数页,骑马钉练习册会取整为四的倍数。随后,计算器会把纸张叠层和少量装订余量结合起来得到书脊宽度。' },
|
|
44
|
+
{ type: 'list', items: ['使用小说预设,作为紧凑型平装书的起点。', '当更大的字号改变每页字数时,使用大字版预设。', '需要更宽页面和四的倍数页数时,使用练习册预设。', '在毫米和英寸之间切换,不会改变图书的实际尺寸。'] },
|
|
45
|
+
{ type: 'title', text: '制作估算而不是印刷打样', level: 2 },
|
|
46
|
+
{ type: 'paragraph', html: '不同纸张制造商对厚度的标示方式不同,不同字体的字面宽度也不同,印刷厂还会使用自己的拼版和装订公差。请把结果当作作者、编辑和设计师的快速规划工具。下单或导出设计文件前,请根据印刷厂规格检查最终书封模板。' },
|
|
47
|
+
{ type: 'tip', title: '保持文字设置易读', html: '较小的字号或过紧的行距虽然能缩小书脊,却可能让图书显得廉价并降低阅读舒适度。请用视觉方式比较各个预设,并以阅读体验为依据做决定。' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const content: ToolLocaleContent<BookPaginationUI> = {
|
|
51
|
+
slug: 'book-pagination-and-spine-calculator',
|
|
52
|
+
title: '图书页数与书脊宽度计算器',
|
|
53
|
+
description: '根据手稿、成品尺寸、字体、纸张和装订方式,估算印刷页数、书脊宽度与书封展开尺寸。',
|
|
54
|
+
ui,
|
|
55
|
+
seo,
|
|
56
|
+
faq: [
|
|
57
|
+
{ question: '页数估算使用哪些数据?', answer: '它使用手稿字数、成品尺寸、页边距、字号和行距,估算每页的字数和行数。' },
|
|
58
|
+
{ question: '为什么要对页数取整?', answer: '平装和精装会取整为偶数页。骑马钉图书会因为折叠书帖取整为四的倍数。' },
|
|
59
|
+
{ question: '书脊宽度如何估算?', answer: '计算器将页数乘以每张纸的厚度,把纸张叠层除以二,再加上所选装订方式的少量余量。' },
|
|
60
|
+
{ question: '结果可以直接交给印刷厂吗?', answer: '不可以。这是规划用估算值。请向印刷厂确认纸张厚度、书封尺寸、出血和装订公差。' },
|
|
61
|
+
],
|
|
62
|
+
bibliography: [
|
|
63
|
+
{ name: 'ISO 216:2007 纸张尺寸', url: 'https://www.iso.org/standard/36631.html' },
|
|
64
|
+
{ name: 'Instituto Nacional de Estadística. Producción editorial de libros', url: 'https://www.ine.es/dyngs/INEbase/es/operacion.htm?c=Estadistica_C&cid=1254736176767&idp=1254735573113&menu=ultiDatos' },
|
|
65
|
+
{ name: '联合国教科文组织全球出版业报告', url: 'https://doi.org/10.58337/SRUH6078' },
|
|
66
|
+
],
|
|
67
|
+
howTo: [
|
|
68
|
+
{ name: '选择格式', text: '从小说、大字版或练习册预设开始。' },
|
|
69
|
+
{ name: '设置手稿', text: '输入字数,并调整尺寸、页边距、字体和行距。' },
|
|
70
|
+
{ name: '选择纸张和装订', text: '输入纸张厚度,然后选择平装、精装或骑马钉。' },
|
|
71
|
+
{ name: '检查书封', text: '把预计书脊和展开尺寸作为设计文件的起点。' },
|
|
72
|
+
],
|
|
73
|
+
schemas: [
|
|
74
|
+
{ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: '图书页数与书脊宽度计算器', applicationCategory: 'DesignApplication', operatingSystem: 'Any', offers: { '@type': 'Offer', price: '0', priceCurrency: 'CNY' } },
|
|
75
|
+
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: [{ '@type': 'Question', name: '页数估算使用哪些数据?', acceptedAnswer: { '@type': 'Answer', text: '它使用字数、尺寸、页边距、字号和行距。' } }, { '@type': 'Question', name: '书脊宽度如何估算?', acceptedAnswer: { '@type': 'Answer', text: '它使用页数、纸张厚度和装订余量。' } }] },
|
|
76
|
+
{ '@context': 'https://schema.org', '@type': 'HowTo', name: '估算图书页数和书脊宽度', step: [{ '@type': 'HowToStep', name: '选择格式', text: '选择一个预设。' }, { '@type': 'HowToStep', name: '设置手稿', text: '输入字数和文字设置。' }, { '@type': 'HowToStep', name: '选择纸张和装订', text: '设置厚度和装订方式。' }, { '@type': 'HowToStep', name: '检查书封', text: '使用书脊和书封估算值。' }] },
|
|
77
|
+
],
|
|
78
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { bookPaginationAndSpineCalculator } from './entry';
|
|
3
|
+
|
|
4
|
+
export * from './entry';
|
|
5
|
+
|
|
6
|
+
export const BOOK_PAGINATION_AND_SPINE_CALCULATOR_TOOL: ToolDefinition = {
|
|
7
|
+
entry: bookPaginationAndSpineCalculator,
|
|
8
|
+
Component: () => import('./component.astro'),
|
|
9
|
+
SEOComponent: () => import('./seo.astro'),
|
|
10
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
11
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { LARGE_PRINT_PRESET, NOVEL_PRESET, WORKBOOK_PRESET, calculateBook } from './logic';
|
|
3
|
+
|
|
4
|
+
describe('book pagination and spine logic', () => {
|
|
5
|
+
it('estimates a novel as an even page count', () => {
|
|
6
|
+
const result = calculateBook(NOVEL_PRESET);
|
|
7
|
+
expect(result.pages % 2).toBe(0);
|
|
8
|
+
expect(result.pages).toBeGreaterThan(200);
|
|
9
|
+
expect(result.spineMm).toBeGreaterThan(1);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('rounds saddle stitch books to a multiple of four', () => {
|
|
13
|
+
expect(calculateBook(WORKBOOK_PRESET).pages % 4).toBe(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('reduces words per page for large print', () => {
|
|
17
|
+
const novel = calculateBook(NOVEL_PRESET);
|
|
18
|
+
const largePrint = calculateBook(LARGE_PRINT_PRESET);
|
|
19
|
+
expect(largePrint.wordsPerPage).toBeLessThan(novel.wordsPerPage);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type Binding = 'softcover' | 'hardcover' | 'saddle-stitch';
|
|
2
|
+
|
|
3
|
+
export interface BookInputs {
|
|
4
|
+
words: number;
|
|
5
|
+
widthMm: number;
|
|
6
|
+
heightMm: number;
|
|
7
|
+
marginMm: number;
|
|
8
|
+
fontSizePt: number;
|
|
9
|
+
lineHeight: number;
|
|
10
|
+
caliperMm: number;
|
|
11
|
+
binding: Binding;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BookCalculation {
|
|
15
|
+
linesPerPage: number;
|
|
16
|
+
charactersPerLine: number;
|
|
17
|
+
wordsPerPage: number;
|
|
18
|
+
pages: number;
|
|
19
|
+
sheets: number;
|
|
20
|
+
spineMm: number;
|
|
21
|
+
coverWidthMm: number;
|
|
22
|
+
coverHeightMm: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const NOVEL_PRESET: BookInputs = { words: 80000, widthMm: 127, heightMm: 203, marginMm: 18, fontSizePt: 11, lineHeight: 1.2, caliperMm: 0.09, binding: 'softcover' };
|
|
26
|
+
export const LARGE_PRINT_PRESET: BookInputs = { words: 60000, widthMm: 152, heightMm: 229, marginMm: 22, fontSizePt: 16, lineHeight: 1.35, caliperMm: 0.09, binding: 'softcover' };
|
|
27
|
+
export const WORKBOOK_PRESET: BookInputs = { words: 30000, widthMm: 210, heightMm: 297, marginMm: 18, fontSizePt: 10, lineHeight: 1.15, caliperMm: 0.1, binding: 'saddle-stitch' };
|
|
28
|
+
|
|
29
|
+
export function calculateBook(inputs: BookInputs): BookCalculation {
|
|
30
|
+
const contentWidth = Math.max(inputs.widthMm - inputs.marginMm * 2, 1);
|
|
31
|
+
const contentHeight = Math.max(inputs.heightMm - inputs.marginMm * 2, 1);
|
|
32
|
+
const fontMm = inputs.fontSizePt * 0.3528;
|
|
33
|
+
const charactersPerLine = Math.max(1, Math.floor(contentWidth / (fontMm * 0.5)));
|
|
34
|
+
const linesPerPage = Math.max(1, Math.floor(contentHeight / (fontMm * inputs.lineHeight)));
|
|
35
|
+
const wordsPerPage = Math.max(1, Math.floor((charactersPerLine * linesPerPage) / 6));
|
|
36
|
+
const rawPages = Math.max(1, Math.ceil(inputs.words / wordsPerPage));
|
|
37
|
+
const pages = roundPages(rawPages, inputs.binding);
|
|
38
|
+
const sheets = Math.ceil(pages / 2);
|
|
39
|
+
const spineMm = pages * inputs.caliperMm / 2 + bindingAllowance(inputs.binding);
|
|
40
|
+
return { linesPerPage, charactersPerLine, wordsPerPage, pages, sheets, spineMm, coverWidthMm: inputs.widthMm * 2 + spineMm, coverHeightMm: inputs.heightMm };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function roundPages(pages: number, binding: Binding): number {
|
|
44
|
+
const multiple = binding === 'saddle-stitch' ? 4 : 2;
|
|
45
|
+
return Math.ceil(pages / multiple) * multiple;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function bindingAllowance(binding: Binding): number {
|
|
49
|
+
if (binding === 'hardcover') return 3;
|
|
50
|
+
if (binding === 'softcover') return 1.2;
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { bookPaginationAndSpineCalculator } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props as Props;
|
|
11
|
+
const loader = bookPaginationAndSpineCalculator.i18n[locale] || bookPaginationAndSpineCalculator.i18n.en;
|
|
12
|
+
const content = await loader?.();
|
|
13
|
+
if (!content) return null;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<SEORenderer content={{ locale, sections: content.seo }} />
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const STORAGE_KEY = 'book-pagination-and-spine-calculator-state';
|
|
2
|
+
|
|
3
|
+
export interface StoredBookState {
|
|
4
|
+
inputs: string;
|
|
5
|
+
unit: 'metric' | 'imperial';
|
|
6
|
+
scenarioName: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function loadBookState(fallback: StoredBookState): StoredBookState {
|
|
10
|
+
try {
|
|
11
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
12
|
+
return raw ? { ...fallback, ...JSON.parse(raw) } as StoredBookState : fallback;
|
|
13
|
+
} catch {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function saveBookState(state: StoredBookState): void {
|
|
19
|
+
try {
|
|
20
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
21
|
+
} catch {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface BookPaginationUI {
|
|
2
|
+
[key: string]: string;
|
|
3
|
+
title: string;
|
|
4
|
+
scenarioLabel: string;
|
|
5
|
+
scenarioPlaceholder: string;
|
|
6
|
+
wordsLabel: string;
|
|
7
|
+
widthLabel: string;
|
|
8
|
+
heightLabel: string;
|
|
9
|
+
marginLabel: string;
|
|
10
|
+
fontSizeLabel: string;
|
|
11
|
+
lineHeightLabel: string;
|
|
12
|
+
paperLabel: string;
|
|
13
|
+
caliperLabel: string;
|
|
14
|
+
bindingLabel: string;
|
|
15
|
+
softcoverLabel: string;
|
|
16
|
+
hardcoverLabel: string;
|
|
17
|
+
saddleLabel: string;
|
|
18
|
+
presetLabel: string;
|
|
19
|
+
novelPreset: string;
|
|
20
|
+
largePrintPreset: string;
|
|
21
|
+
workbookPreset: string;
|
|
22
|
+
metricLabel: string;
|
|
23
|
+
imperialLabel: string;
|
|
24
|
+
resetLabel: string;
|
|
25
|
+
pagesLabel: string;
|
|
26
|
+
spineLabel: string;
|
|
27
|
+
coverLabel: string;
|
|
28
|
+
wordsPerPageLabel: string;
|
|
29
|
+
linesLabel: string;
|
|
30
|
+
previewLabel: string;
|
|
31
|
+
previewHint: string;
|
|
32
|
+
methodTitle: string;
|
|
33
|
+
methodHint: string;
|
|
34
|
+
sourceLabel: string;
|
|
35
|
+
}
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ALL_ENTRIES } from './entries';
|
|
2
|
+
import type { ToolDefinition } from './types';
|
|
3
|
+
import { BOOK_PAGINATION_AND_SPINE_CALCULATOR_TOOL } from './tool/book-pagination-and-spine-calculator';
|
|
4
|
+
|
|
5
|
+
export const ALL_TOOLS: ToolDefinition[] = [BOOK_PAGINATION_AND_SPINE_CALCULATOR_TOOL];
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SEOSection } from '@jjlmoya/utils-shared';
|
|
2
|
+
|
|
3
|
+
export type { SEOSection };
|
|
4
|
+
|
|
5
|
+
export type KnownLocale =
|
|
6
|
+
| 'ar' | 'da' | 'de' | 'en' | 'es' | 'fi'
|
|
7
|
+
| 'fr' | 'id' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
|
|
8
|
+
| 'pl' | 'pt' | 'ru' | 'sv' | 'tr' | 'zh';
|
|
9
|
+
|
|
10
|
+
export interface FAQItem {
|
|
11
|
+
question: string;
|
|
12
|
+
answer: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BibliographyEntry {
|
|
16
|
+
name: string;
|
|
17
|
+
url: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HowToStep {
|
|
21
|
+
name: string;
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ToolLocaleContent<TUI extends Record<string, string> = Record<string, string>> {
|
|
26
|
+
slug: string;
|
|
27
|
+
title: string;
|
|
28
|
+
description: string;
|
|
29
|
+
ui: TUI;
|
|
30
|
+
seo: SEOSection[];
|
|
31
|
+
faq: FAQItem[];
|
|
32
|
+
bibliography: BibliographyEntry[];
|
|
33
|
+
howTo: HowToStep[];
|
|
34
|
+
schemas: Record<string, unknown>[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CategoryLocaleContent {
|
|
38
|
+
slug: string;
|
|
39
|
+
title: string;
|
|
40
|
+
description: string;
|
|
41
|
+
seo: SEOSection[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type LocaleLoader<T> = () => Promise<T>;
|
|
45
|
+
|
|
46
|
+
export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
|
|
47
|
+
|
|
48
|
+
export interface BooksToolEntry<TUI extends Record<string, string> = Record<string, string>> {
|
|
49
|
+
id: string;
|
|
50
|
+
icons: {
|
|
51
|
+
bg: string;
|
|
52
|
+
fg: string;
|
|
53
|
+
};
|
|
54
|
+
i18n: LocaleMap<ToolLocaleContent<TUI>>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BooksCategoryEntry {
|
|
58
|
+
icon: string;
|
|
59
|
+
tools: BooksToolEntry[];
|
|
60
|
+
i18n: LocaleMap<CategoryLocaleContent>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ToolDefinition {
|
|
64
|
+
entry: BooksToolEntry;
|
|
65
|
+
Component: unknown;
|
|
66
|
+
SEOComponent: unknown;
|
|
67
|
+
BibliographyComponent: unknown;
|
|
68
|
+
}
|
|
69
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "astro/tsconfigs/strictest",
|
|
3
|
+
"include": ["src/**/*", ".astro/types.d.ts"],
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"baseUrl": ".",
|
|
6
|
+
"strictNullChecks": true,
|
|
7
|
+
"noImplicitAny": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"types": ["astro/client", "astro/jsx-runtime"],
|
|
10
|
+
"jsx": "preserve",
|
|
11
|
+
"jsxImportSource": "astro"
|
|
12
|
+
},
|
|
13
|
+
"exclude": ["node_modules", "dist"]
|
|
14
|
+
}
|
|
15
|
+
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
environment: 'node',
|
|
7
|
+
},
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: [
|
|
10
|
+
{
|
|
11
|
+
find: /^(.*)\.astro$/,
|
|
12
|
+
replacement: path.resolve(__dirname, './src/tests/mocks/astro_mock.js')
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
ssr: {
|
|
17
|
+
external: ['astro']
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|