From 5242f890f725c872a74b6ee45cd611092628690a Mon Sep 17 00:00:00 2001
From: Thomas Hallock
- Generate printable calendars with abacus date numbers + {t('pageSubtitle')}
diff --git a/apps/web/src/i18n/locales/calendar/de.json b/apps/web/src/i18n/locales/calendar/de.json new file mode 100644 index 00000000..f181a908 --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/de.json @@ -0,0 +1,61 @@ +{ + "calendar": { + "pageTitle": "Abakus-Kalender erstellen", + "pageSubtitle": "Druckbare Kalender mit Abakus-Datumszahlen erstellen", + "format": { + "title": "Kalenderformat", + "monthly": "Monatskalender (eine Seite pro Monat)", + "daily": "Tageskalender (eine Seite pro Tag)" + }, + "date": { + "title": "Datum", + "month": "Monat", + "year": "Jahr" + }, + "paperSize": { + "title": "Papiergröße", + "usLetter": "US Letter (8,5\" × 11\")", + "a4": "A4 (210mm × 297mm)", + "a3": "A3 (297mm × 420mm)", + "tabloid": "Tabloid (11\" × 17\")" + }, + "styling": { + "preview": "Kalender-Abakus-Stil Vorschau:" + }, + "generate": { + "button": "PDF-Kalender erstellen", + "generating": "PDF wird erstellt..." + }, + "preview": { + "loading": "Vorschau wird geladen...", + "noPreview": "Keine Vorschau verfügbar", + "generatedPdf": "Erstelltes PDF", + "livePreview": "Live-Vorschau", + "livePreviewFirstDay": "Live-Vorschau (Erster Tag)" + }, + "months": { + "january": "Januar", + "february": "Februar", + "march": "März", + "april": "April", + "may": "Mai", + "june": "Juni", + "july": "Juli", + "august": "August", + "september": "September", + "october": "Oktober", + "november": "November", + "december": "Dezember" + }, + "weekdays": { + "sunday": "Sonntag", + "monday": "Montag", + "tuesday": "Dienstag", + "wednesday": "Mittwoch", + "thursday": "Donnerstag", + "friday": "Freitag", + "saturday": "Samstag" + }, + "notes": "Notizen:" + } +} diff --git a/apps/web/src/i18n/locales/calendar/en.json b/apps/web/src/i18n/locales/calendar/en.json new file mode 100644 index 00000000..bbe6441a --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/en.json @@ -0,0 +1,61 @@ +{ + "calendar": { + "pageTitle": "Create Abacus Calendar", + "pageSubtitle": "Generate printable calendars with abacus date numbers", + "format": { + "title": "Calendar Format", + "monthly": "Monthly Calendar (one page per month)", + "daily": "Daily Calendar (one page per day)" + }, + "date": { + "title": "Date", + "month": "Month", + "year": "Year" + }, + "paperSize": { + "title": "Paper Size", + "usLetter": "US Letter (8.5\" × 11\")", + "a4": "A4 (210mm × 297mm)", + "a3": "A3 (297mm × 420mm)", + "tabloid": "Tabloid (11\" × 17\")" + }, + "styling": { + "preview": "Calendar abacus style preview:" + }, + "generate": { + "button": "Generate PDF Calendar", + "generating": "Generating PDF..." + }, + "preview": { + "loading": "Loading preview...", + "noPreview": "No preview available", + "generatedPdf": "Generated PDF", + "livePreview": "Live Preview", + "livePreviewFirstDay": "Live Preview (First Day)" + }, + "months": { + "january": "January", + "february": "February", + "march": "March", + "april": "April", + "may": "May", + "june": "June", + "july": "July", + "august": "August", + "september": "September", + "october": "October", + "november": "November", + "december": "December" + }, + "weekdays": { + "sunday": "Sunday", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday" + }, + "notes": "Notes:" + } +} diff --git a/apps/web/src/i18n/locales/calendar/es.json b/apps/web/src/i18n/locales/calendar/es.json new file mode 100644 index 00000000..7f173e96 --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/es.json @@ -0,0 +1,61 @@ +{ + "calendar": { + "pageTitle": "Crear Calendario de Ábaco", + "pageSubtitle": "Generar calendarios imprimibles con números de fecha de ábaco", + "format": { + "title": "Formato de Calendario", + "monthly": "Calendario Mensual (una página por mes)", + "daily": "Calendario Diario (una página por día)" + }, + "date": { + "title": "Fecha", + "month": "Mes", + "year": "Año" + }, + "paperSize": { + "title": "Tamaño de Papel", + "usLetter": "Carta US (8.5\" × 11\")", + "a4": "A4 (210mm × 297mm)", + "a3": "A3 (297mm × 420mm)", + "tabloid": "Tabloide (11\" × 17\")" + }, + "styling": { + "preview": "Vista previa del estilo de ábaco del calendario:" + }, + "generate": { + "button": "Generar Calendario PDF", + "generating": "Generando PDF..." + }, + "preview": { + "loading": "Cargando vista previa...", + "noPreview": "No hay vista previa disponible", + "generatedPdf": "PDF Generado", + "livePreview": "Vista Previa en Vivo", + "livePreviewFirstDay": "Vista Previa en Vivo (Primer Día)" + }, + "months": { + "january": "Enero", + "february": "Febrero", + "march": "Marzo", + "april": "Abril", + "may": "Mayo", + "june": "Junio", + "july": "Julio", + "august": "Agosto", + "september": "Septiembre", + "october": "Octubre", + "november": "Noviembre", + "december": "Diciembre" + }, + "weekdays": { + "sunday": "Domingo", + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado" + }, + "notes": "Notas:" + } +} diff --git a/apps/web/src/i18n/locales/calendar/goh.json b/apps/web/src/i18n/locales/calendar/goh.json new file mode 100644 index 00000000..b48681f5 --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/goh.json @@ -0,0 +1,61 @@ +{ + "calendar": { + "pageTitle": "Abacus Kalender Giscaffan", + "pageSubtitle": "Drucchāri kalendera mit abacus tagozalun giskaffen", + "format": { + "title": "Kalenderart", + "monthly": "Mānoðkalender (ein sīta per mānoð)", + "daily": "Tagkalender (ein sīta per tag)" + }, + "date": { + "title": "Tag", + "month": "Mānoð", + "year": "Jār" + }, + "paperSize": { + "title": "Papiergrōzi", + "usLetter": "US Brief (8.5\" × 11\")", + "a4": "A4 (210mm × 297mm)", + "a3": "A3 (297mm × 420mm)", + "tabloid": "Tabloid (11\" × 17\")" + }, + "styling": { + "preview": "Kalender abacus stil forasihti:" + }, + "generate": { + "button": "PDF Kalender Giskaffen", + "generating": "PDF wirdit giskaffan..." + }, + "preview": { + "loading": "Forasihti ladet...", + "noPreview": "Nein forasihti ferfuogbar", + "generatedPdf": "Giskaffan PDF", + "livePreview": "Lebenti Forasihti", + "livePreviewFirstDay": "Lebenti Forasihti (Ēristo Tag)" + }, + "months": { + "january": "Hartmānot", + "february": "Hornung", + "march": "Lentzinmānot", + "april": "Ōstarmānot", + "may": "Winnemānot", + "june": "Brāhmānot", + "july": "Hewimānot", + "august": "Aranmānot", + "september": "Witumanot", + "october": "Windurmānot", + "november": "Herbistmānot", + "december": "Heilagmānot" + }, + "weekdays": { + "sunday": "Sunnūntag", + "monday": "Mānetag", + "tuesday": "Ziostag", + "wednesday": "Mittawehha", + "thursday": "Donarestag", + "friday": "Frīatag", + "saturday": "Sambaztag" + }, + "notes": "Notiziun:" + } +} diff --git a/apps/web/src/i18n/locales/calendar/hi.json b/apps/web/src/i18n/locales/calendar/hi.json new file mode 100644 index 00000000..ff15cea8 --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/hi.json @@ -0,0 +1,61 @@ +{ + "calendar": { + "pageTitle": "अबेकस कैलेंडर बनाएं", + "pageSubtitle": "अबेकस तिथि संख्याओं के साथ मुद्रण योग्य कैलेंडर उत्पन्न करें", + "format": { + "title": "कैलेंडर प्रारूप", + "monthly": "मासिक कैलेंडर (प्रति माह एक पृष्ठ)", + "daily": "दैनिक कैलेंडर (प्रति दिन एक पृष्ठ)" + }, + "date": { + "title": "तिथि", + "month": "महीना", + "year": "वर्ष" + }, + "paperSize": { + "title": "कागज का आकार", + "usLetter": "यूएस लेटर (8.5\" × 11\")", + "a4": "A4 (210mm × 297mm)", + "a3": "A3 (297mm × 420mm)", + "tabloid": "टैबलॉइड (11\" × 17\")" + }, + "styling": { + "preview": "कैलेंडर अबेकस शैली पूर्वावलोकन:" + }, + "generate": { + "button": "पीडीएफ कैलेंडर उत्पन्न करें", + "generating": "पीडीएफ उत्पन्न हो रहा है..." + }, + "preview": { + "loading": "पूर्वावलोकन लोड हो रहा है...", + "noPreview": "कोई पूर्वावलोकन उपलब्ध नहीं", + "generatedPdf": "उत्पन्न पीडीएफ", + "livePreview": "लाइव पूर्वावलोकन", + "livePreviewFirstDay": "लाइव पूर्वावलोकन (पहला दिन)" + }, + "months": { + "january": "जनवरी", + "february": "फरवरी", + "march": "मार्च", + "april": "अप्रैल", + "may": "मई", + "june": "जून", + "july": "जुलाई", + "august": "अगस्त", + "september": "सितंबर", + "october": "अक्टूबर", + "november": "नवंबर", + "december": "दिसंबर" + }, + "weekdays": { + "sunday": "रविवार", + "monday": "सोमवार", + "tuesday": "मंगलवार", + "wednesday": "बुधवार", + "thursday": "गुरुवार", + "friday": "शुक्रवार", + "saturday": "शनिवार" + }, + "notes": "नोट्स:" + } +} diff --git a/apps/web/src/i18n/locales/calendar/ja.json b/apps/web/src/i18n/locales/calendar/ja.json new file mode 100644 index 00000000..e6eed0c2 --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/ja.json @@ -0,0 +1,61 @@ +{ + "calendar": { + "pageTitle": "そろばんカレンダーを作成", + "pageSubtitle": "そろばんの日付番号付き印刷可能なカレンダーを生成", + "format": { + "title": "カレンダー形式", + "monthly": "月間カレンダー(月ごとに1ページ)", + "daily": "日めくりカレンダー(日ごとに1ページ)" + }, + "date": { + "title": "日付", + "month": "月", + "year": "年" + }, + "paperSize": { + "title": "用紙サイズ", + "usLetter": "USレター (8.5\" × 11\")", + "a4": "A4 (210mm × 297mm)", + "a3": "A3 (297mm × 420mm)", + "tabloid": "タブロイド (11\" × 17\")" + }, + "styling": { + "preview": "カレンダーそろばんスタイルプレビュー:" + }, + "generate": { + "button": "PDFカレンダーを生成", + "generating": "PDFを生成中..." + }, + "preview": { + "loading": "プレビューを読み込み中...", + "noPreview": "プレビューがありません", + "generatedPdf": "生成されたPDF", + "livePreview": "ライブプレビュー", + "livePreviewFirstDay": "ライブプレビュー(初日)" + }, + "months": { + "january": "1月", + "february": "2月", + "march": "3月", + "april": "4月", + "may": "5月", + "june": "6月", + "july": "7月", + "august": "8月", + "september": "9月", + "october": "10月", + "november": "11月", + "december": "12月" + }, + "weekdays": { + "sunday": "日曜日", + "monday": "月曜日", + "tuesday": "火曜日", + "wednesday": "水曜日", + "thursday": "木曜日", + "friday": "金曜日", + "saturday": "土曜日" + }, + "notes": "メモ:" + } +} diff --git a/apps/web/src/i18n/locales/calendar/la.json b/apps/web/src/i18n/locales/calendar/la.json new file mode 100644 index 00000000..275b6a48 --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/la.json @@ -0,0 +1,61 @@ +{ + "calendar": { + "pageTitle": "Calendarium Abaci Creare", + "pageSubtitle": "Calendaria imprimibilia cum numeris diei abaci generare", + "format": { + "title": "Forma Calendarii", + "monthly": "Calendarium Mensuale (una pagina per mensem)", + "daily": "Calendarium Diurnum (una pagina per diem)" + }, + "date": { + "title": "Dies", + "month": "Mensis", + "year": "Annus" + }, + "paperSize": { + "title": "Magnitudo Chartae", + "usLetter": "US Epistula (8.5\" × 11\")", + "a4": "A4 (210mm × 297mm)", + "a3": "A3 (297mm × 420mm)", + "tabloid": "Tabloid (11\" × 17\")" + }, + "styling": { + "preview": "Praevisio stili abaci calendarii:" + }, + "generate": { + "button": "Calendarium PDF Generare", + "generating": "PDF Generatur..." + }, + "preview": { + "loading": "Praevisio cargatur...", + "noPreview": "Nulla praevisio disponibilis", + "generatedPdf": "PDF Generatum", + "livePreview": "Praevisio Viva", + "livePreviewFirstDay": "Praevisio Viva (Primus Dies)" + }, + "months": { + "january": "Ianuarius", + "february": "Februarius", + "march": "Martius", + "april": "Aprilis", + "may": "Maius", + "june": "Iunius", + "july": "Iulius", + "august": "Augustus", + "september": "September", + "october": "October", + "november": "November", + "december": "December" + }, + "weekdays": { + "sunday": "Dies Solis", + "monday": "Dies Lunae", + "tuesday": "Dies Martis", + "wednesday": "Dies Mercurii", + "thursday": "Dies Iovis", + "friday": "Dies Veneris", + "saturday": "Dies Saturni" + }, + "notes": "Notae:" + } +} diff --git a/apps/web/src/i18n/locales/calendar/messages.ts b/apps/web/src/i18n/locales/calendar/messages.ts new file mode 100644 index 00000000..a470d5be --- /dev/null +++ b/apps/web/src/i18n/locales/calendar/messages.ts @@ -0,0 +1,17 @@ +import de from './de.json' +import en from './en.json' +import es from './es.json' +import goh from './goh.json' +import hi from './hi.json' +import ja from './ja.json' +import la from './la.json' + +export const calendarMessages = { + en: en.calendar, + de: de.calendar, + ja: ja.calendar, + hi: hi.calendar, + es: es.calendar, + la: la.calendar, + goh: goh.calendar, +} as const diff --git a/apps/web/src/i18n/messages.ts b/apps/web/src/i18n/messages.ts index 369aa565..edb1374a 100644 --- a/apps/web/src/i18n/messages.ts +++ b/apps/web/src/i18n/messages.ts @@ -1,4 +1,5 @@ import { rithmomachiaMessages } from '@/arcade-games/rithmomachia/messages' +import { calendarMessages } from '@/i18n/locales/calendar/messages' import { gamesMessages } from '@/i18n/locales/games/messages' import { guideMessages } from '@/i18n/locales/guide/messages' import { homeMessages } from '@/i18n/locales/home/messages' @@ -40,6 +41,7 @@ export async function getMessages(locale: Locale) { { games: gamesMessages[locale] }, { guide: guideMessages[locale] }, { tutorial: tutorialMessages[locale] }, + { calendar: calendarMessages[locale] }, rithmomachiaMessages[locale] ) } diff --git a/apps/web/src/utils/calendar/generateCalendarComposite.tsx b/apps/web/src/utils/calendar/generateCalendarComposite.tsx index 360a3cd6..5fb61f8f 100644 --- a/apps/web/src/utils/calendar/generateCalendarComposite.tsx +++ b/apps/web/src/utils/calendar/generateCalendarComposite.tsx @@ -117,6 +117,10 @@ export function generateCalendarComposite(options: CalendarCompositeOptions): st showNumbers={false} frameVisible={true} compact={false} + hideInactiveBeads={true} + cropToActiveBeads={{ + padding: { top: 8, bottom: 2, left: 5, right: 5 } + }} /> ) } @@ -179,22 +183,41 @@ export function generateCalendarComposite(options: CalendarCompositeOptions): st const cellX = MARGIN + col * CELL_WIDTH const cellY = GRID_START_Y + WEEKDAY_ROW_HEIGHT + row * DAY_CELL_HEIGHT + // Render cropped abacus SVG + const abacusSVG = renderAbacusSVG(day, 2, 1) + + // Extract viewBox and dimensions from the cropped SVG + const viewBoxMatch = abacusSVG.match(/viewBox="([^"]*)"/) + const widthMatch = abacusSVG.match(/width="?([0-9.]+)"?/) + const heightMatch = abacusSVG.match(/height="?([0-9.]+)"?/) + + const croppedViewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 120 230' + const croppedWidth = widthMatch ? parseFloat(widthMatch[1]) : ABACUS_NATURAL_WIDTH + const croppedHeight = heightMatch ? parseFloat(heightMatch[1]) : ABACUS_NATURAL_HEIGHT + + // Calculate scale to fit cropped abacus in cell + const MAX_SCALE_X = (CELL_WIDTH - CELL_PADDING * 2) / croppedWidth + const MAX_SCALE_Y = (DAY_CELL_HEIGHT - CELL_PADDING * 2) / croppedHeight + const fitScale = Math.min(MAX_SCALE_X, MAX_SCALE_Y) * 0.95 // 95% to leave breathing room + + const scaledWidth = croppedWidth * fitScale + const scaledHeight = croppedHeight * fitScale + // Center abacus in cell const abacusCenterX = cellX + CELL_WIDTH / 2 const abacusCenterY = cellY + DAY_CELL_HEIGHT / 2 - // Offset to top-left corner of abacus (accounting for scaled size) - const abacusX = abacusCenterX - SCALED_ABACUS_WIDTH / 2 - const abacusY = abacusCenterY - SCALED_ABACUS_HEIGHT / 2 + // Offset to top-left corner of abacus + const abacusX = abacusCenterX - scaledWidth / 2 + const abacusY = abacusCenterY - scaledHeight / 2 - // Render at scale=1 and let the nested SVG handle scaling via viewBox - const abacusSVG = renderAbacusSVG(day, 2, 1) + // Extract SVG content (remove outer