feat(annotation): export annotations with style and color fields in annotation template, closes #1734 (#3060)

This commit is contained in:
Huang Xin
2026-01-24 17:44:40 +01:00
committed by GitHub
parent 45e57c3943
commit 42fee90a27
32 changed files with 2119 additions and 192 deletions
+2
View File
@@ -125,6 +125,7 @@
"marked": "^15.0.12",
"nanoid": "^5.1.6",
"next": "16.0.10",
"nunjucks": "^3.2.4",
"overlayscrollbars": "^2.11.4",
"overlayscrollbars-react": "^0.5.6",
"posthog-js": "^1.246.0",
@@ -158,6 +159,7 @@
"@types/cssbeautify": "^0.3.5",
"@types/lunr": "^2.3.7",
"@types/node": "^22.15.31",
"@types/nunjucks": "^3.2.6",
"@types/react": "^19.0.0",
"@types/react-color": "^3.0.13",
"@types/react-dom": "^19.0.0",
@@ -914,7 +914,6 @@
"Array of annotations": "مصفوفة التعليقات",
"Highlighted text": "النص المميز",
"Annotation note": "ملاحظة التعليق",
"Update time": "وقت التحديث",
"Date Format Tokens:": "رموز تنسيق التاريخ:",
"Year (4 digits)": "السنة (4 أرقام)",
"Month (01-12)": "الشهر (01-12)",
@@ -932,5 +931,50 @@
"Show on Discord": "عرض على Discord",
"Instant {{action}}": "{{action}} فوري",
"Instant {{action}} Disabled": "تم تعطيل {{action}} فوري",
"Annotation": "التعليق"
"Annotation": "التعليق",
"Reset Template": "إعادة تعيين القالب",
"Annotation style": "نمط التعليق",
"Annotation color": "لون التعليق",
"Annotation time": "وقت التعليق",
"AI": "الذكاء الاصطناعي",
"Are you sure you want to re-index this book?": "هل أنت متأكد من إعادة فهرسة هذا الكتاب؟",
"Enable AI in Settings": "تفعيل الذكاء الاصطناعي في الإعدادات",
"Index This Book": "فهرسة هذا الكتاب",
"Enable AI search and chat for this book": "تفعيل البحث والدردشة بالذكاء الاصطناعي لهذا الكتاب",
"Start Indexing": "بدء الفهرسة",
"Indexing book...": "جاري فهرسة الكتاب...",
"Preparing...": "جاري التحضير...",
"Delete this conversation?": "حذف هذه المحادثة؟",
"No conversations yet": "لا توجد محادثات بعد",
"Start a new chat to ask questions about this book": "ابدأ محادثة جديدة لطرح أسئلة حول هذا الكتاب",
"Rename": "إعادة التسمية",
"New Chat": "محادثة جديدة",
"Chat": "محادثة",
"Please enter a model ID": "الرجاء إدخال معرف النموذج",
"Model not available or invalid": "النموذج غير متاح أو غير صالح",
"Failed to validate model": "فشل التحقق من النموذج",
"Couldn't connect to Ollama. Is it running?": "تعذر الاتصال بـ Ollama. هل يعمل؟",
"Invalid API key or connection failed": "مفتاح API غير صالح أو فشل الاتصال",
"Connection failed": "فشل الاتصال",
"AI Assistant": "مساعد الذكاء الاصطناعي",
"Enable AI Assistant": "تفعيل مساعد الذكاء الاصطناعي",
"Provider": "المزود",
"Ollama (Local)": "Ollama (محلي)",
"AI Gateway (Cloud)": "بوابة الذكاء الاصطناعي (سحابي)",
"Ollama Configuration": "إعدادات Ollama",
"Refresh Models": "تحديث النماذج",
"AI Model": "نموذج الذكاء الاصطناعي",
"No models detected": "لم يتم اكتشاف نماذج",
"AI Gateway Configuration": "إعدادات بوابة الذكاء الاصطناعي",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "اختر من مجموعة من نماذج الذكاء الاصطناعي عالية الجودة والاقتصادية. يمكنك أيضًا استخدام نموذجك الخاص بتحديد \"نموذج مخصص\" أدناه.",
"API Key": "مفتاح API",
"Get Key": "احصل على المفتاح",
"Model": "النموذج",
"Custom Model...": "نموذج مخصص...",
"Custom Model ID": "معرف النموذج المخصص",
"Validate": "تحقق",
"Model available": "النموذج متاح",
"Connection": "الاتصال",
"Test Connection": "اختبار الاتصال",
"Connected": "متصل"
}
@@ -874,7 +874,6 @@
"Array of annotations": "টীকাগুলির তালিকা",
"Highlighted text": "হাইলাইট করা পাঠ্য",
"Annotation note": "টীকা নোট",
"Update time": "আপডেট সময়",
"Date Format Tokens:": "তারিখ বিন্যাস টোকেন:",
"Year (4 digits)": "বছর (4 সংখ্যা)",
"Month (01-12)": "মাস (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Discord-এ দেখান",
"Instant {{action}}": "তাৎক্ষণিক {{action}}",
"Instant {{action}} Disabled": "তাৎক্ষণিক {{action}} নিষ্ক্রিয়",
"Annotation": "টীকা"
"Annotation": "টীকা",
"Reset Template": "টেমপ্লেট রিসেট করুন",
"Annotation style": "টীকা শৈলী",
"Annotation color": "টীকা রঙ",
"Annotation time": "টীকা সময়",
"AI": "কৃত্রিম বুদ্ধিমত্তা (AI)",
"Are you sure you want to re-index this book?": "আপনি কি নিশ্চিত যে আপনি এই বইটি পুনরায় ইনডেক্স করতে চান?",
"Enable AI in Settings": "সেটিংস থেকে AI সক্রিয় করুন",
"Index This Book": "এই বইটি ইনডেক্স করুন",
"Enable AI search and chat for this book": "এই বইটির জন্য AI অনুসন্ধান এবং চ্যাট সক্রিয় করুন",
"Start Indexing": "ইনডেক্সিং শুরু করুন",
"Indexing book...": "বই ইনডেক্স করা হচ্ছে...",
"Preparing...": "প্রস্তুত করা হচ্ছে...",
"Delete this conversation?": "এই কথোপকথনটি মুছবেন?",
"No conversations yet": "এখনও কোনও কথোপকথন নেই",
"Start a new chat to ask questions about this book": "এই বইটি সম্পর্কে প্রশ্ন জিজ্ঞাসা করতে একটি নতুন চ্যাট শুরু করুন",
"Rename": "নাম পরিবর্তন",
"New Chat": "নতুন চ্যাট",
"Chat": "চ্যাট",
"Please enter a model ID": "দয়া করে একটি মডেল আইডি লিখুন",
"Model not available or invalid": "মডেল উপলব্ধ নয় বা অবৈধ",
"Failed to validate model": "মডেল যাচাই করতে ব্যর্থ হয়েছে",
"Couldn't connect to Ollama. Is it running?": "Ollama এর সাথে সংযোগ করা যায়নি। এটি কি চলছে?",
"Invalid API key or connection failed": "অবৈধ API কী বা সংযোগ ব্যর্থ হয়েছে",
"Connection failed": "সংযোগ ব্যর্থ হয়েছে",
"AI Assistant": "AI সহকারী",
"Enable AI Assistant": "AI সহকারী সক্রিয় করুন",
"Provider": "প্রদানকারী",
"Ollama (Local)": "Ollama (স্থানীয়)",
"AI Gateway (Cloud)": "AI গেটওয়ে (ক্লাউড)",
"Ollama Configuration": "Ollama কনফিগারেশন",
"Refresh Models": "মডেল রিফ্রেশ করুন",
"AI Model": "AI মডেল",
"No models detected": "কোনও মডেল শনাক্ত করা যায়নি",
"AI Gateway Configuration": "AI গেটওয়ে কনফিগারেশন",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "উচ্চ-মানের, সাশ্রয়ী AI মডেলগুলি থেকে নির্বাচন করুন। আপনি নিচে \"কাস্টম মডেল\" নির্বাচন করে আপনার নিজস্ব মডেলও আনতে পারেন।",
"API Key": "API কী",
"Get Key": "কী পান",
"Model": "মডেল",
"Custom Model...": "কাস্টম মডেল...",
"Custom Model ID": "কাস্টম মডেল আইডি",
"Validate": "যাচাই করুন",
"Model available": "মডেল উপলব্ধ",
"Connection": "সংযোগ",
"Test Connection": "সংযোগ পরীক্ষা করুন",
"Connected": "সংযুক্ত"
}
@@ -864,7 +864,6 @@
"Array of annotations": "གསལ་བཤད་ལེབ་ངོས།",
"Highlighted text": "གཙོ་གནད་ཡིག་རྐྱང་།",
"Annotation note": "གསལ་བཤད་ཟིན་བྲིས།",
"Update time": "གསར་བཅོས་དུས་ཚོད།",
"Date Format Tokens:": "ཚེས་གྲངས་རྣམ་གཞག་རྟགས།:",
"Year (4 digits)": "ལོ། (གྲངས་ཐང་4)",
"Month (01-12)": "ཟླ། (01-12)",
@@ -882,5 +881,50 @@
"Show on Discord": "Discord ཐོག་སྟོན།",
"Instant {{action}}": "འཕྲལ་མར་{{action}}",
"Instant {{action}} Disabled": "འཕྲལ་མར་{{action}}་ལྕོགས་མིན་བཟོས།",
"Annotation": "མཆན་འགྲེལ།"
"Annotation": "མཆན་འགྲེལ།",
"Reset Template": "དཔེ་གཞི་བསྐྱར་སྒྲིག",
"Annotation style": "མཆན་འགྲེལ་གྱི་བཟོ་ལྟ།",
"Annotation color": "མཆན་འགྲེལ་གྱི་ཚོན་མདོག",
"Annotation time": "མཆན་འགྲེལ་གྱི་དུས་ཚོད།",
"AI": "རིག་ནུས་མིས་བཟོས། (AI)",
"Are you sure you want to re-index this book?": "ཁྱེད་ཀྱིས་དཔེ་དེབ་འདི་བསྐྱར་དུ་དཀར་ཆག་བཟོ་རྒྱུ་གཏན་འཁེལ་ཡིན་ནམ།",
"Enable AI in Settings": "སྒྲིག་བཀོད་ནང་ AI སྤྱོད་པར་བྱོས།",
"Index This Book": "དཔེ་དེབ་འདི་དཀར་ཆག་བཟོ་བ།",
"Enable AI search and chat for this book": "དཔེ་དེབ་འདིར་ AI འཚོལ་བཤེར་དང་ཁ་བརྡ་སྤྱོད་པར་བྱོས།",
"Start Indexing": "དཀར་ཆག་བཟོ་འགོ་བཙུགས་པ།",
"Indexing book...": "དཔེ་དེབ་དཀར་ཆག་བཟོ་བཞིན་པ།...",
"Preparing...": "གྲ་སྒྲིག་བྱེད་བཞིན་པ།...",
"Delete this conversation?": "ཁ་བརྡ་འདི་བསུབ་དགོས་སམ།",
"No conversations yet": "ད་ལྟའི་བར་ཁ་བརྡ་མེད།",
"Start a new chat to ask questions about this book": "དཔེ་དེབ་འདིའི་སྐོར་ལ་དྲི་བ་དྲི་བར་ཁ་བརྡ་གསར་པ་ཞིག་འགོ་བཙུགས་པ།",
"Rename": "མིང་བརྗེ་བ།",
"New Chat": "ཁ་བརྡ་གསར་པ།",
"Chat": "ཁ་བརྡ།",
"Please enter a model ID": "དཔེ་གཞིའི་ཨང་རྟགས་ནང་འཇུག་བྱོས།",
"Model not available or invalid": "དཔེ་གཞི་མེད་པ་འམ་ནུས་མེད།",
"Failed to validate model": "དཔེ་གཞི་བདེན་སྦྱོར་བྱེད་ཐུབ་མེད།",
"Couldn't connect to Ollama. Is it running?": "Ollama ལ་འབྲེལ་མཐུད་བྱེད་ཐུབ་མེད། དེ་འཁོར་བཞིན་ཡོད་དམ།",
"Invalid API key or connection failed": "API ལྡེ་མིག་ནུས་མེད་དམ་འབྲེལ་མཐུད་ཕམ་པ།",
"Connection failed": "འབྲེལ་མཐུད་ཕམ་པ།",
"AI Assistant": "AI རོགས་པ།",
"Enable AI Assistant": "AI རོགས་པ་སྤྱོད་པར་བྱོས།",
"Provider": "མཁོ་འདོན་པ།",
"Ollama (Local)": "Ollama (ས་གནས།)",
"AI Gateway (Cloud)": "AI འཛུལ་སྒོ། (སྤྲིན་གནས།)",
"Ollama Configuration": "Ollama སྒྲིག་བཀོད།",
"Refresh Models": "དཔེ་གཞི་བསྐྱར་བརྗེ།",
"AI Model": "AI དཔེ་གཞི།",
"No models detected": "དཔེ་གཞི་མ་རྙེད།",
"AI Gateway Configuration": "AI འཛུལ་སྒོའི་སྒྲིག་བཀོད།",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "སྤུས་ལེགས་དང་ཁེ་ཕན་ཆེ་བའི་ AI དཔེ་གཞི་འདེམས་དགོས། ཁྱེད་ཀྱིས་འོག་གི་ \"རང་བཟོའི་དཔེ་གཞི།\" བདམས་ནས་རང་གི་དཔེ་གཞི་བེད་སྤྱོད་ཀྱང་ཆོག",
"API Key": "API ལྡེ་མིག་",
"Get Key": "ལྡེ་མིག་ལེན་པ།",
"Model": "དཔེ་གཞི།",
"Custom Model...": "རང་བཟོའི་དཔེ་གཞི།...",
"Custom Model ID": "རང་བཟོའི་དཔེ་གཞིའི་ཨང་རྟགས།",
"Validate": "བདེན་སྦྱོར།",
"Model available": "དཔེ་གཞི་ཡོད།",
"Connection": "འབྲེལ་མཐུད།",
"Test Connection": "འབྲེལ་མཐུད་ཚོད་ལྟ།",
"Connected": "འབྲེལ་མཐུད་ཟིན།"
}
@@ -874,7 +874,6 @@
"Array of annotations": "Array von Anmerkungen",
"Highlighted text": "Markierter Text",
"Annotation note": "Anmerkungsnotiz",
"Update time": "Aktualisierungszeit",
"Date Format Tokens:": "Datumsformat-Token:",
"Year (4 digits)": "Jahr (4 Ziffern)",
"Month (01-12)": "Monat (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Auf Discord zeigen",
"Instant {{action}}": "Sofort {{action}}",
"Instant {{action}} Disabled": "Sofort {{action}} deaktiviert",
"Annotation": "Anmerkung"
"Annotation": "Anmerkung",
"Reset Template": "Vorlage zurücksetzen",
"Annotation style": "Anmerkungsstil",
"Annotation color": "Anmerkungsfarbe",
"Annotation time": "Anmerkungszeit",
"AI": "KI",
"Are you sure you want to re-index this book?": "Möchten Sie dieses Buch wirklich neu indizieren?",
"Enable AI in Settings": "KI in den Einstellungen aktivieren",
"Index This Book": "Dieses Buch indizieren",
"Enable AI search and chat for this book": "KI-Suche und Chat für dieses Buch aktivieren",
"Start Indexing": "Indizierung starten",
"Indexing book...": "Buch wird indiziert...",
"Preparing...": "Vorbereitung...",
"Delete this conversation?": "Diese Unterhaltung löschen?",
"No conversations yet": "Noch keine Unterhaltungen",
"Start a new chat to ask questions about this book": "Starten Sie einen neuen Chat, um Fragen zu diesem Buch zu stellen",
"Rename": "Umbenennen",
"New Chat": "Neuer Chat",
"Chat": "Chat",
"Please enter a model ID": "Bitte geben Sie eine Modell-ID ein",
"Model not available or invalid": "Modell nicht verfügbar oder ungültig",
"Failed to validate model": "Modellvalidierung fehlgeschlagen",
"Couldn't connect to Ollama. Is it running?": "Verbindung zu Ollama fehlgeschlagen. Läuft es?",
"Invalid API key or connection failed": "Ungültiger API-Schlüssel oder Verbindung fehlgeschlagen",
"Connection failed": "Verbindung fehlgeschlagen",
"AI Assistant": "KI-Assistent",
"Enable AI Assistant": "KI-Assistent aktivieren",
"Provider": "Anbieter",
"Ollama (Local)": "Ollama (Lokal)",
"AI Gateway (Cloud)": "KI-Gateway (Cloud)",
"Ollama Configuration": "Ollama-Konfiguration",
"Refresh Models": "Modelle aktualisieren",
"AI Model": "KI-Modell",
"No models detected": "Keine Modelle erkannt",
"AI Gateway Configuration": "KI-Gateway-Konfiguration",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Wählen Sie aus einer Auswahl hochwertiger, wirtschaftlicher KI-Modelle. Sie können auch Ihr eigenes Modell verwenden, indem Sie unten \"Benutzerdefiniertes Modell\" auswählen.",
"API Key": "API-Schlüssel",
"Get Key": "Schlüssel erhalten",
"Model": "Modell",
"Custom Model...": "Benutzerdefiniertes Modell...",
"Custom Model ID": "Benutzerdefinierte Modell-ID",
"Validate": "Validieren",
"Model available": "Modell verfügbar",
"Connection": "Verbindung",
"Test Connection": "Verbindung testen",
"Connected": "Verbunden"
}
@@ -874,7 +874,6 @@
"Array of annotations": "Πίνακας σχολιασμών",
"Highlighted text": "Επισημασμένο κείμενο",
"Annotation note": "Σημείωση σχολιασμού",
"Update time": "Ώρα ενημέρωσης",
"Date Format Tokens:": "Σύμβολα μορφοποίησης ημερομηνίας:",
"Year (4 digits)": "Έτος (4 ψηφία)",
"Month (01-12)": "Μήνας (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Εμφάνιση στο Discord",
"Instant {{action}}": "Άμεση {{action}}",
"Instant {{action}} Disabled": "Άμεση {{action}} απενεργοποιημένη",
"Annotation": "Σημείωση"
"Annotation": "Σημείωση",
"Reset Template": "Επαναφορά προτύπου",
"Annotation style": "Στυλ σχολίου",
"Annotation color": "Χρώμα σχολίου",
"Annotation time": "Ώρα σχολίου",
"AI": "AI",
"Are you sure you want to re-index this book?": "Είστε σίγουροι ότι θέλετε να επαναδημιουργήσετε το ευρετήριο αυτού του βιβλίου;",
"Enable AI in Settings": "Ενεργοποίηση AI στις ρυθμίσεις",
"Index This Book": "Δημιουργία ευρετηρίου για αυτό το βιβλίο",
"Enable AI search and chat for this book": "Ενεργοποίηση αναζήτησης AI και συνομιλίας για αυτό το βιβλίο",
"Start Indexing": "Έναρξη δημιουργίας ευρετηρίου",
"Indexing book...": "Δημιουργία ευρετηρίου βιβλίου...",
"Preparing...": "Προετοιμασία...",
"Delete this conversation?": "Διαγραφή αυτής της συνομιλίας;",
"No conversations yet": "Δεν υπάρχουν συνομιλίες ακόμα",
"Start a new chat to ask questions about this book": "Ξεκινήστε μια νέα συνομιλία για να κάνετε ερωτήσεις σχετικά με αυτό το βιβλίο",
"Rename": "Μετονομασία",
"New Chat": "Νέα συνομιλία",
"Chat": "Συνομιλία",
"Please enter a model ID": "Εισάγετε ένα αναγνωριστικό μοντέλου",
"Model not available or invalid": "Το μοντέλο δεν είναι διαθέσιμο ή δεν είναι έγκυρο",
"Failed to validate model": "Αποτυχία επικύρωσης μοντέλου",
"Couldn't connect to Ollama. Is it running?": "Δεν ήταν δυνατή η σύνδεση με το Ollama. Εκτελείται;",
"Invalid API key or connection failed": "Μη έγκυρο κλειδί API ή αποτυχία σύνδεσης",
"Connection failed": "Αποτυχία σύνδεσης",
"AI Assistant": "Βοηθός AI",
"Enable AI Assistant": "Ενεργοποίηση βοηθού AI",
"Provider": "Πάροχος",
"Ollama (Local)": "Ollama (Τοπικό)",
"AI Gateway (Cloud)": "Πύλη AI (Cloud)",
"Ollama Configuration": "Διαμόρφωση Ollama",
"Refresh Models": "Ανανέωση μοντέλων",
"AI Model": "Μοντέλο AI",
"No models detected": "Δεν εντοπίστηκαν μοντέλα",
"AI Gateway Configuration": "Διαμόρφωση πύλης AI",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Επιλέξτε από μια συλλογή υψηλής ποιότητας, οικονομικών μοντέλων AI. Μπορείτε επίσης να χρησιμοποιήσετε το δικό σας μοντέλο επιλέγοντας \"Προσαρμοσμένο μοντέλο\" παρακάτω.",
"API Key": "Κλειδί API",
"Get Key": "Λήψη κλειδιού",
"Model": "Μοντέλο",
"Custom Model...": "Προσαρμοσμένο μοντέλο...",
"Custom Model ID": "Αναγνωριστικό προσαρμοσμένου μοντέλου",
"Validate": "Επικύρωση",
"Model available": "Το μοντέλο είναι διαθέσιμο",
"Connection": "Σύνδεση",
"Test Connection": "Δοκιμή σύνδεσης",
"Connected": "Συνδεδεμένο"
}
@@ -884,7 +884,6 @@
"Array of annotations": "Lista de anotaciones",
"Highlighted text": "Texto resaltado",
"Annotation note": "Nota de anotación",
"Update time": "Hora de actualización",
"Date Format Tokens:": "Tokens de formato de fecha:",
"Year (4 digits)": "Año (4 dígitos)",
"Month (01-12)": "Mes (01-12)",
@@ -902,5 +901,50 @@
"Show on Discord": "Mostrar en Discord",
"Instant {{action}}": "{{action}} instantáneo",
"Instant {{action}} Disabled": "{{action}} instantáneo desactivado",
"Annotation": "Anotación"
"Annotation": "Anotación",
"Reset Template": "Restablecer plantilla",
"Annotation style": "Estilo de anotación",
"Annotation color": "Color de anotación",
"Annotation time": "Hora de anotación",
"AI": "IA",
"Are you sure you want to re-index this book?": "¿Está seguro de que desea reindexar este libro?",
"Enable AI in Settings": "Habilitar IA en Configuración",
"Index This Book": "Indexar este libro",
"Enable AI search and chat for this book": "Habilitar búsqueda y chat con IA para este libro",
"Start Indexing": "Iniciar indexación",
"Indexing book...": "Indexando libro...",
"Preparing...": "Preparando...",
"Delete this conversation?": "¿Eliminar esta conversación?",
"No conversations yet": "Aún no hay conversaciones",
"Start a new chat to ask questions about this book": "Inicie un nuevo chat para hacer preguntas sobre este libro",
"Rename": "Renombrar",
"New Chat": "Nuevo chat",
"Chat": "Chat",
"Please enter a model ID": "Por favor, ingrese un ID de modelo",
"Model not available or invalid": "Modelo no disponible o inválido",
"Failed to validate model": "Error al validar el modelo",
"Couldn't connect to Ollama. Is it running?": "No se pudo conectar a Ollama. ¿Está ejecutándose?",
"Invalid API key or connection failed": "Clave API inválida o conexión fallida",
"Connection failed": "Conexión fallida",
"AI Assistant": "Asistente de IA",
"Enable AI Assistant": "Habilitar asistente de IA",
"Provider": "Proveedor",
"Ollama (Local)": "Ollama (Local)",
"AI Gateway (Cloud)": "Pasarela de IA (Nube)",
"Ollama Configuration": "Configuración de Ollama",
"Refresh Models": "Actualizar modelos",
"AI Model": "Modelo de IA",
"No models detected": "No se detectaron modelos",
"AI Gateway Configuration": "Configuración de pasarela de IA",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Elija entre una selección de modelos de IA de alta calidad y económicos. También puede usar su propio modelo seleccionando \"Modelo personalizado\" a continuación.",
"API Key": "Clave API",
"Get Key": "Obtener clave",
"Model": "Modelo",
"Custom Model...": "Modelo personalizado...",
"Custom Model ID": "ID de modelo personalizado",
"Validate": "Validar",
"Model available": "Modelo disponible",
"Connection": "Conexión",
"Test Connection": "Probar conexión",
"Connected": "Conectado"
}
@@ -874,7 +874,6 @@
"Array of annotations": "آرایه یادداشت‌ها",
"Highlighted text": "متن برجسته‌شده",
"Annotation note": "یادداشت حاشیه",
"Update time": "زمان به‌روزرسانی",
"Date Format Tokens:": "نشانه‌های قالب تاریخ:",
"Year (4 digits)": "سال (۴ رقم)",
"Month (01-12)": "ماه (۰۱-۱۲)",
@@ -892,5 +891,50 @@
"Show on Discord": "نمایش در Discord",
"Instant {{action}}": "{{action}} فوری",
"Instant {{action}} Disabled": "{{action}} فوری غیرفعال شد",
"Annotation": "یادداشت"
"Annotation": "یادداشت",
"Reset Template": "بازنشانی قالب",
"Annotation style": "سبک یادداشت",
"Annotation color": "رنگ یادداشت",
"Annotation time": "زمان یادداشت",
"AI": "هوش مصنوعی",
"Are you sure you want to re-index this book?": "آیا مطمئن هستید که می‌خواهید این کتاب را دوباره فهرست کنید؟",
"Enable AI in Settings": "فعال کردن هوش مصنوعی در تنظیمات",
"Index This Book": "فهرست کردن این کتاب",
"Enable AI search and chat for this book": "فعال کردن جستجو و گفتگوی هوش مصنوعی برای این کتاب",
"Start Indexing": "شروع فهرست‌بندی",
"Indexing book...": "در حال فهرست‌بندی کتاب...",
"Preparing...": "در حال آماده‌سازی...",
"Delete this conversation?": "این مکالمه حذف شود؟",
"No conversations yet": "هنوز مکالمه‌ای وجود ندارد",
"Start a new chat to ask questions about this book": "یک گفتگوی جدید برای پرسیدن سؤال درباره این کتاب شروع کنید",
"Rename": "تغییر نام",
"New Chat": "گفتگوی جدید",
"Chat": "گفتگو",
"Please enter a model ID": "لطفاً شناسه مدل را وارد کنید",
"Model not available or invalid": "مدل در دسترس نیست یا نامعتبر است",
"Failed to validate model": "اعتبارسنجی مدل ناموفق بود",
"Couldn't connect to Ollama. Is it running?": "اتصال به Ollama ممکن نشد. آیا در حال اجرا است؟",
"Invalid API key or connection failed": "کلید API نامعتبر است یا اتصال ناموفق بود",
"Connection failed": "اتصال ناموفق بود",
"AI Assistant": "دستیار هوش مصنوعی",
"Enable AI Assistant": "فعال کردن دستیار هوش مصنوعی",
"Provider": "ارائه‌دهنده",
"Ollama (Local)": "Ollama (محلی)",
"AI Gateway (Cloud)": "دروازه هوش مصنوعی (ابری)",
"Ollama Configuration": "پیکربندی Ollama",
"Refresh Models": "بازخوانی مدل‌ها",
"AI Model": "مدل هوش مصنوعی",
"No models detected": "هیچ مدلی شناسایی نشد",
"AI Gateway Configuration": "پیکربندی دروازه هوش مصنوعی",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "از میان مجموعه‌ای از مدل‌های هوش مصنوعی با کیفیت و اقتصادی انتخاب کنید. همچنین می‌توانید با انتخاب \"مدل سفارشی\" در زیر، مدل خود را استفاده کنید.",
"API Key": "کلید API",
"Get Key": "دریافت کلید",
"Model": "مدل",
"Custom Model...": "مدل سفارشی...",
"Custom Model ID": "شناسه مدل سفارشی",
"Validate": "اعتبارسنجی",
"Model available": "مدل در دسترس است",
"Connection": "اتصال",
"Test Connection": "تست اتصال",
"Connected": "متصل"
}
@@ -884,7 +884,6 @@
"Array of annotations": "Liste d'annotations",
"Highlighted text": "Texte surligné",
"Annotation note": "Note d'annotation",
"Update time": "Heure de mise à jour",
"Date Format Tokens:": "Jetons de format de date :",
"Year (4 digits)": "Année (4 chiffres)",
"Month (01-12)": "Mois (01-12)",
@@ -902,5 +901,50 @@
"Show on Discord": "Afficher sur Discord",
"Instant {{action}}": "{{action}} instantané",
"Instant {{action}} Disabled": "{{action}} instantané désactivé",
"Annotation": "Annotation"
"Annotation": "Annotation",
"Reset Template": "Réinitialiser le modèle",
"Annotation style": "Style d'annotation",
"Annotation color": "Couleur d'annotation",
"Annotation time": "Heure d'annotation",
"AI": "IA",
"Are you sure you want to re-index this book?": "Voulez-vous vraiment réindexer ce livre ?",
"Enable AI in Settings": "Activer l'IA dans les paramètres",
"Index This Book": "Indexer ce livre",
"Enable AI search and chat for this book": "Activer la recherche IA et le chat pour ce livre",
"Start Indexing": "Démarrer l'indexation",
"Indexing book...": "Indexation du livre...",
"Preparing...": "Préparation...",
"Delete this conversation?": "Supprimer cette conversation ?",
"No conversations yet": "Pas encore de conversations",
"Start a new chat to ask questions about this book": "Démarrez une nouvelle conversation pour poser des questions sur ce livre",
"Rename": "Renommer",
"New Chat": "Nouvelle conversation",
"Chat": "Chat",
"Please enter a model ID": "Veuillez entrer un ID de modèle",
"Model not available or invalid": "Modèle non disponible ou invalide",
"Failed to validate model": "Échec de la validation du modèle",
"Couldn't connect to Ollama. Is it running?": "Impossible de se connecter à Ollama. Est-il en cours d'exécution ?",
"Invalid API key or connection failed": "Clé API invalide ou échec de connexion",
"Connection failed": "Échec de connexion",
"AI Assistant": "Assistant IA",
"Enable AI Assistant": "Activer l'assistant IA",
"Provider": "Fournisseur",
"Ollama (Local)": "Ollama (Local)",
"AI Gateway (Cloud)": "Passerelle IA (Cloud)",
"Ollama Configuration": "Configuration Ollama",
"Refresh Models": "Actualiser les modèles",
"AI Model": "Modèle IA",
"No models detected": "Aucun modèle détecté",
"AI Gateway Configuration": "Configuration de la passerelle IA",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Choisissez parmi une sélection de modèles IA de haute qualité et économiques. Vous pouvez également utiliser votre propre modèle en sélectionnant \"Modèle personnalisé\" ci-dessous.",
"API Key": "Clé API",
"Get Key": "Obtenir la clé",
"Model": "Modèle",
"Custom Model...": "Modèle personnalisé...",
"Custom Model ID": "ID du modèle personnalisé",
"Validate": "Valider",
"Model available": "Modèle disponible",
"Connection": "Connexion",
"Test Connection": "Tester la connexion",
"Connected": "Connecté"
}
@@ -874,7 +874,6 @@
"Array of annotations": "एनोटेशन की सूची",
"Highlighted text": "हाइलाइट किया गया पाठ",
"Annotation note": "एनोटेशन नोट",
"Update time": "अपडेट समय",
"Date Format Tokens:": "तिथि प्रारूप टोकन:",
"Year (4 digits)": "वर्ष (4 अंक)",
"Month (01-12)": "माह (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Discord पर दिखाएं",
"Instant {{action}}": "तत्काल {{action}}",
"Instant {{action}} Disabled": "तत्काल {{action}} अक्षम",
"Annotation": "टिप्पणी"
"Annotation": "टिप्पणी",
"Reset Template": "टेम्पलेट रीसेट करें",
"Annotation style": "एनोटेशन शैली",
"Annotation color": "एनोटेशन रंग",
"Annotation time": "एनोटेशन समय",
"AI": "AI",
"Are you sure you want to re-index this book?": "क्या आप वाकई इस पुस्तक को फिर से इंडेक्स करना चाहते हैं?",
"Enable AI in Settings": "सेटिंग्स में AI सक्षम करें",
"Index This Book": "इस पुस्तक को इंडेक्स करें",
"Enable AI search and chat for this book": "इस पुस्तक के लिए AI खोज और चैट सक्षम करें",
"Start Indexing": "इंडेक्सिंग शुरू करें",
"Indexing book...": "पुस्तक इंडेक्स हो रही है...",
"Preparing...": "तैयारी हो रही है...",
"Delete this conversation?": "इस वार्तालाप को हटाएं?",
"No conversations yet": "अभी तक कोई वार्तालाप नहीं",
"Start a new chat to ask questions about this book": "इस पुस्तक के बारे में प्रश्न पूछने के लिए नई चैट शुरू करें",
"Rename": "नाम बदलें",
"New Chat": "नई चैट",
"Chat": "चैट",
"Please enter a model ID": "कृपया मॉडल ID दर्ज करें",
"Model not available or invalid": "मॉडल उपलब्ध नहीं या अमान्य है",
"Failed to validate model": "मॉडल सत्यापित करने में विफल",
"Couldn't connect to Ollama. Is it running?": "Ollama से कनेक्ट नहीं हो सका। क्या यह चल रहा है?",
"Invalid API key or connection failed": "अमान्य API कुंजी या कनेक्शन विफल",
"Connection failed": "कनेक्शन विफल",
"AI Assistant": "AI सहायक",
"Enable AI Assistant": "AI सहायक सक्षम करें",
"Provider": "प्रदाता",
"Ollama (Local)": "Ollama (स्थानीय)",
"AI Gateway (Cloud)": "AI गेटवे (क्लाउड)",
"Ollama Configuration": "Ollama कॉन्फ़िगरेशन",
"Refresh Models": "मॉडल रीफ्रेश करें",
"AI Model": "AI मॉडल",
"No models detected": "कोई मॉडल नहीं मिला",
"AI Gateway Configuration": "AI गेटवे कॉन्फ़िगरेशन",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "उच्च-गुणवत्ता, किफायती AI मॉडल के चयन में से चुनें। आप नीचे \"कस्टम मॉडल\" का चयन करके अपना मॉडल भी उपयोग कर सकते हैं।",
"API Key": "API कुंजी",
"Get Key": "कुंजी प्राप्त करें",
"Model": "मॉडल",
"Custom Model...": "कस्टम मॉडल...",
"Custom Model ID": "कस्टम मॉडल ID",
"Validate": "सत्यापित करें",
"Model available": "मॉडल उपलब्ध",
"Connection": "कनेक्शन",
"Test Connection": "कनेक्शन परीक्षण",
"Connected": "कनेक्टेड"
}
@@ -864,7 +864,6 @@
"Array of annotations": "Daftar anotasi",
"Highlighted text": "Teks yang disorot",
"Annotation note": "Catatan anotasi",
"Update time": "Waktu pembaruan",
"Date Format Tokens:": "Token Format Tanggal:",
"Year (4 digits)": "Tahun (4 digit)",
"Month (01-12)": "Bulan (01-12)",
@@ -882,5 +881,50 @@
"Show on Discord": "Tampilkan di Discord",
"Instant {{action}}": "{{action}} Instan",
"Instant {{action}} Disabled": "{{action}} Instan Dinonaktifkan",
"Annotation": "Anotasi"
"Annotation": "Anotasi",
"Reset Template": "Atur Ulang Template",
"Annotation style": "Gaya anotasi",
"Annotation color": "Warna anotasi",
"Annotation time": "Waktu anotasi",
"AI": "AI",
"Are you sure you want to re-index this book?": "Apakah Anda yakin ingin mengindeks ulang buku ini?",
"Enable AI in Settings": "Aktifkan AI di Pengaturan",
"Index This Book": "Indeks Buku Ini",
"Enable AI search and chat for this book": "Aktifkan pencarian AI dan obrolan untuk buku ini",
"Start Indexing": "Mulai Mengindeks",
"Indexing book...": "Mengindeks buku...",
"Preparing...": "Mempersiapkan...",
"Delete this conversation?": "Hapus percakapan ini?",
"No conversations yet": "Belum ada percakapan",
"Start a new chat to ask questions about this book": "Mulai obrolan baru untuk mengajukan pertanyaan tentang buku ini",
"Rename": "Ubah nama",
"New Chat": "Obrolan Baru",
"Chat": "Obrolan",
"Please enter a model ID": "Silakan masukkan ID model",
"Model not available or invalid": "Model tidak tersedia atau tidak valid",
"Failed to validate model": "Gagal memvalidasi model",
"Couldn't connect to Ollama. Is it running?": "Tidak dapat terhubung ke Ollama. Apakah sedang berjalan?",
"Invalid API key or connection failed": "Kunci API tidak valid atau koneksi gagal",
"Connection failed": "Koneksi gagal",
"AI Assistant": "Asisten AI",
"Enable AI Assistant": "Aktifkan Asisten AI",
"Provider": "Penyedia",
"Ollama (Local)": "Ollama (Lokal)",
"AI Gateway (Cloud)": "Gateway AI (Cloud)",
"Ollama Configuration": "Konfigurasi Ollama",
"Refresh Models": "Segarkan Model",
"AI Model": "Model AI",
"No models detected": "Tidak ada model terdeteksi",
"AI Gateway Configuration": "Konfigurasi Gateway AI",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Pilih dari pilihan model AI berkualitas tinggi dan ekonomis. Anda juga dapat menggunakan model Anda sendiri dengan memilih \"Model Kustom\" di bawah.",
"API Key": "Kunci API",
"Get Key": "Dapatkan Kunci",
"Model": "Model",
"Custom Model...": "Model Kustom...",
"Custom Model ID": "ID Model Kustom",
"Validate": "Validasi",
"Model available": "Model tersedia",
"Connection": "Koneksi",
"Test Connection": "Uji Koneksi",
"Connected": "Terhubung"
}
@@ -884,7 +884,6 @@
"Array of annotations": "Elenco annotazioni",
"Highlighted text": "Testo evidenziato",
"Annotation note": "Nota annotazione",
"Update time": "Ora aggiornamento",
"Date Format Tokens:": "Token formato data:",
"Year (4 digits)": "Anno (4 cifre)",
"Month (01-12)": "Mese (01-12)",
@@ -902,5 +901,50 @@
"Show on Discord": "Mostra su Discord",
"Instant {{action}}": "{{action}} istantaneo",
"Instant {{action}} Disabled": "{{action}} istantaneo disattivato",
"Annotation": "Annotazione"
"Annotation": "Annotazione",
"Reset Template": "Reimposta modello",
"Annotation style": "Stile annotazione",
"Annotation color": "Colore annotazione",
"Annotation time": "Ora annotazione",
"AI": "IA",
"Are you sure you want to re-index this book?": "Sei sicuro di voler reindicizzare questo libro?",
"Enable AI in Settings": "Abilita IA nelle impostazioni",
"Index This Book": "Indicizza questo libro",
"Enable AI search and chat for this book": "Abilita ricerca IA e chat per questo libro",
"Start Indexing": "Avvia indicizzazione",
"Indexing book...": "Indicizzazione libro...",
"Preparing...": "Preparazione...",
"Delete this conversation?": "Eliminare questa conversazione?",
"No conversations yet": "Ancora nessuna conversazione",
"Start a new chat to ask questions about this book": "Avvia una nuova chat per fare domande su questo libro",
"Rename": "Rinomina",
"New Chat": "Nuova chat",
"Chat": "Chat",
"Please enter a model ID": "Inserisci un ID modello",
"Model not available or invalid": "Modello non disponibile o non valido",
"Failed to validate model": "Validazione modello fallita",
"Couldn't connect to Ollama. Is it running?": "Impossibile connettersi a Ollama. È in esecuzione?",
"Invalid API key or connection failed": "Chiave API non valida o connessione fallita",
"Connection failed": "Connessione fallita",
"AI Assistant": "Assistente IA",
"Enable AI Assistant": "Abilita assistente IA",
"Provider": "Fornitore",
"Ollama (Local)": "Ollama (Locale)",
"AI Gateway (Cloud)": "Gateway IA (Cloud)",
"Ollama Configuration": "Configurazione Ollama",
"Refresh Models": "Aggiorna modelli",
"AI Model": "Modello IA",
"No models detected": "Nessun modello rilevato",
"AI Gateway Configuration": "Configurazione gateway IA",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Scegli tra una selezione di modelli IA di alta qualità ed economici. Puoi anche usare il tuo modello selezionando \"Modello personalizzato\" qui sotto.",
"API Key": "Chiave API",
"Get Key": "Ottieni chiave",
"Model": "Modello",
"Custom Model...": "Modello personalizzato...",
"Custom Model ID": "ID modello personalizzato",
"Validate": "Convalida",
"Model available": "Modello disponibile",
"Connection": "Connessione",
"Test Connection": "Testa connessione",
"Connected": "Connesso"
}
@@ -864,7 +864,6 @@
"Array of annotations": "注釈の配列",
"Highlighted text": "ハイライトテキスト",
"Annotation note": "注釈ノート",
"Update time": "更新時刻",
"Date Format Tokens:": "日付形式トークン:",
"Year (4 digits)": "年(4桁)",
"Month (01-12)": "月(01-12)",
@@ -882,5 +881,50 @@
"Show on Discord": "Discordに表示",
"Instant {{action}}": "インスタント{{action}}",
"Instant {{action}} Disabled": "インスタント{{action}}を無効化",
"Annotation": "注釈"
"Annotation": "注釈",
"Reset Template": "テンプレートをリセット",
"Annotation style": "注釈スタイル",
"Annotation color": "注釈の色",
"Annotation time": "注釈時間",
"AI": "AI",
"Are you sure you want to re-index this book?": "この本を再インデックスしてもよろしいですか?",
"Enable AI in Settings": "設定でAIを有効にする",
"Index This Book": "この本をインデックス",
"Enable AI search and chat for this book": "この本のAI検索とチャットを有効にする",
"Start Indexing": "インデックス開始",
"Indexing book...": "本をインデックス中...",
"Preparing...": "準備中...",
"Delete this conversation?": "この会話を削除しますか?",
"No conversations yet": "まだ会話がありません",
"Start a new chat to ask questions about this book": "この本について質問するには新しいチャットを開始してください",
"Rename": "名前を変更",
"New Chat": "新しいチャット",
"Chat": "チャット",
"Please enter a model ID": "モデルIDを入力してください",
"Model not available or invalid": "モデルが利用できないか無効です",
"Failed to validate model": "モデルの検証に失敗しました",
"Couldn't connect to Ollama. Is it running?": "Ollamaに接続できませんでした。実行中ですか?",
"Invalid API key or connection failed": "APIキーが無効か接続に失敗しました",
"Connection failed": "接続に失敗しました",
"AI Assistant": "AIアシスタント",
"Enable AI Assistant": "AIアシスタントを有効にする",
"Provider": "プロバイダー",
"Ollama (Local)": "Ollama(ローカル)",
"AI Gateway (Cloud)": "AIゲートウェイ(クラウド)",
"Ollama Configuration": "Ollama設定",
"Refresh Models": "モデルを更新",
"AI Model": "AIモデル",
"No models detected": "モデルが検出されませんでした",
"AI Gateway Configuration": "AIゲートウェイ設定",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "高品質で経済的なAIモデルからお選びください。下の「カスタムモデル」を選択して独自のモデルを使用することもできます。",
"API Key": "APIキー",
"Get Key": "キーを取得",
"Model": "モデル",
"Custom Model...": "カスタムモデル...",
"Custom Model ID": "カスタムモデルID",
"Validate": "検証",
"Model available": "モデルが利用可能",
"Connection": "接続",
"Test Connection": "接続をテスト",
"Connected": "接続済み"
}
@@ -864,7 +864,6 @@
"Array of annotations": "주석 배열",
"Highlighted text": "하이라이트된 텍스트",
"Annotation note": "주석 노트",
"Update time": "업데이트 시간",
"Date Format Tokens:": "날짜 형식 토큰:",
"Year (4 digits)": "연도 (4자리)",
"Month (01-12)": "월 (01-12)",
@@ -882,5 +881,50 @@
"Show on Discord": "Discord에 표시",
"Instant {{action}}": "즉시 {{action}}",
"Instant {{action}} Disabled": "즉시 {{action}} 비활성화",
"Annotation": "주석"
"Annotation": "주석",
"Reset Template": "템플릿 초기화",
"Annotation style": "주석 스타일",
"Annotation color": "주석 색상",
"Annotation time": "주석 시간",
"AI": "AI",
"Are you sure you want to re-index this book?": "이 책을 다시 인덱싱하시겠습니까?",
"Enable AI in Settings": "설정에서 AI 활성화",
"Index This Book": "이 책 인덱싱",
"Enable AI search and chat for this book": "이 책에 대한 AI 검색 및 채팅 활성화",
"Start Indexing": "인덱싱 시작",
"Indexing book...": "책 인덱싱 중...",
"Preparing...": "준비 중...",
"Delete this conversation?": "이 대화를 삭제하시겠습니까?",
"No conversations yet": "아직 대화가 없습니다",
"Start a new chat to ask questions about this book": "이 책에 대해 질문하려면 새 채팅을 시작하세요",
"Rename": "이름 변경",
"New Chat": "새 채팅",
"Chat": "채팅",
"Please enter a model ID": "모델 ID를 입력하세요",
"Model not available or invalid": "모델을 사용할 수 없거나 유효하지 않습니다",
"Failed to validate model": "모델 검증 실패",
"Couldn't connect to Ollama. Is it running?": "Ollama에 연결할 수 없습니다. 실행 중인가요?",
"Invalid API key or connection failed": "API 키가 유효하지 않거나 연결 실패",
"Connection failed": "연결 실패",
"AI Assistant": "AI 어시스턴트",
"Enable AI Assistant": "AI 어시스턴트 활성화",
"Provider": "제공자",
"Ollama (Local)": "Ollama (로컬)",
"AI Gateway (Cloud)": "AI 게이트웨이 (클라우드)",
"Ollama Configuration": "Ollama 설정",
"Refresh Models": "모델 새로고침",
"AI Model": "AI 모델",
"No models detected": "모델이 감지되지 않았습니다",
"AI Gateway Configuration": "AI 게이트웨이 설정",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "고품질의 경제적인 AI 모델 중에서 선택하세요. 아래의 \"사용자 정의 모델\"을 선택하여 자신의 모델을 사용할 수도 있습니다.",
"API Key": "API 키",
"Get Key": "키 받기",
"Model": "모델",
"Custom Model...": "사용자 정의 모델...",
"Custom Model ID": "사용자 정의 모델 ID",
"Validate": "검증",
"Model available": "모델 사용 가능",
"Connection": "연결",
"Test Connection": "연결 테스트",
"Connected": "연결됨"
}
@@ -864,7 +864,6 @@
"Array of annotations": "Senarai anotasi",
"Highlighted text": "Teks yang diserlahkan",
"Annotation note": "Nota anotasi",
"Update time": "Masa kemas kini",
"Date Format Tokens:": "Token Format Tarikh:",
"Year (4 digits)": "Tahun (4 digit)",
"Month (01-12)": "Bulan (01-12)",
@@ -882,5 +881,50 @@
"Show on Discord": "Papar di Discord",
"Instant {{action}}": "{{action}} Segera",
"Instant {{action}} Disabled": "{{action}} Segera Dilumpuhkan",
"Annotation": "Anotasi"
"Annotation": "Anotasi",
"Reset Template": "Tetapkan Semula Templat",
"Annotation style": "Gaya anotasi",
"Annotation color": "Warna anotasi",
"Annotation time": "Masa anotasi",
"AI": "AI",
"Are you sure you want to re-index this book?": "Adakah anda pasti mahu mengindeks semula buku ini?",
"Enable AI in Settings": "Aktifkan AI dalam Tetapan",
"Index This Book": "Indeks Buku Ini",
"Enable AI search and chat for this book": "Aktifkan carian AI dan sembang untuk buku ini",
"Start Indexing": "Mula Mengindeks",
"Indexing book...": "Mengindeks buku...",
"Preparing...": "Menyediakan...",
"Delete this conversation?": "Padam perbualan ini?",
"No conversations yet": "Tiada perbualan lagi",
"Start a new chat to ask questions about this book": "Mulakan sembang baharu untuk bertanya tentang buku ini",
"Rename": "Namakan Semula",
"New Chat": "Sembang Baharu",
"Chat": "Sembang",
"Please enter a model ID": "Sila masukkan ID model",
"Model not available or invalid": "Model tidak tersedia atau tidak sah",
"Failed to validate model": "Gagal mengesahkan model",
"Couldn't connect to Ollama. Is it running?": "Tidak dapat menyambung ke Ollama. Adakah ia sedang berjalan?",
"Invalid API key or connection failed": "Kunci API tidak sah atau sambungan gagal",
"Connection failed": "Sambungan gagal",
"AI Assistant": "Pembantu AI",
"Enable AI Assistant": "Aktifkan Pembantu AI",
"Provider": "Pembekal",
"Ollama (Local)": "Ollama (Tempatan)",
"AI Gateway (Cloud)": "Gateway AI (Awan)",
"Ollama Configuration": "Konfigurasi Ollama",
"Refresh Models": "Muat Semula Model",
"AI Model": "Model AI",
"No models detected": "Tiada model dikesan",
"AI Gateway Configuration": "Konfigurasi Gateway AI",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Pilih dari pelbagai model AI berkualiti tinggi dan ekonomi. Anda juga boleh menggunakan model anda sendiri dengan memilih \"Model Tersuai\" di bawah.",
"API Key": "Kunci API",
"Get Key": "Dapatkan Kunci",
"Model": "Model",
"Custom Model...": "Model Tersuai...",
"Custom Model ID": "ID Model Tersuai",
"Validate": "Sahkan",
"Model available": "Model tersedia",
"Connection": "Sambungan",
"Test Connection": "Uji Sambungan",
"Connected": "Disambung"
}
@@ -874,7 +874,6 @@
"Array of annotations": "Reeks annotaties",
"Highlighted text": "Gemarkeerde tekst",
"Annotation note": "Annotatienotitie",
"Update time": "Updatetijd",
"Date Format Tokens:": "Datumformaattokens:",
"Year (4 digits)": "Jaar (4 cijfers)",
"Month (01-12)": "Maand (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Toon op Discord",
"Instant {{action}}": "Directe {{action}}",
"Instant {{action}} Disabled": "Directe {{action}} uitgeschakeld",
"Annotation": "Annotatie"
"Annotation": "Annotatie",
"Reset Template": "Sjabloon resetten",
"Annotation style": "Annotatiestijl",
"Annotation color": "Annotatiekleur",
"Annotation time": "Annotatietijd",
"AI": "AI",
"Are you sure you want to re-index this book?": "Weet u zeker dat u dit boek opnieuw wilt indexeren?",
"Enable AI in Settings": "AI inschakelen in Instellingen",
"Index This Book": "Dit boek indexeren",
"Enable AI search and chat for this book": "AI-zoeken en chat voor dit boek inschakelen",
"Start Indexing": "Indexeren starten",
"Indexing book...": "Boek wordt geïndexeerd...",
"Preparing...": "Voorbereiden...",
"Delete this conversation?": "Dit gesprek verwijderen?",
"No conversations yet": "Nog geen gesprekken",
"Start a new chat to ask questions about this book": "Start een nieuwe chat om vragen te stellen over dit boek",
"Rename": "Hernoemen",
"New Chat": "Nieuwe chat",
"Chat": "Chat",
"Please enter a model ID": "Voer een model-ID in",
"Model not available or invalid": "Model niet beschikbaar of ongeldig",
"Failed to validate model": "Model valideren mislukt",
"Couldn't connect to Ollama. Is it running?": "Kon geen verbinding maken met Ollama. Is het actief?",
"Invalid API key or connection failed": "Ongeldige API-sleutel of verbinding mislukt",
"Connection failed": "Verbinding mislukt",
"AI Assistant": "AI-assistent",
"Enable AI Assistant": "AI-assistent inschakelen",
"Provider": "Provider",
"Ollama (Local)": "Ollama (Lokaal)",
"AI Gateway (Cloud)": "AI-gateway (Cloud)",
"Ollama Configuration": "Ollama-configuratie",
"Refresh Models": "Modellen vernieuwen",
"AI Model": "AI-model",
"No models detected": "Geen modellen gedetecteerd",
"AI Gateway Configuration": "AI-gateway configuratie",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Kies uit een selectie van hoogwaardige, voordelige AI-modellen. U kunt ook uw eigen model gebruiken door hieronder \"Aangepast model\" te selecteren.",
"API Key": "API-sleutel",
"Get Key": "Sleutel ophalen",
"Model": "Model",
"Custom Model...": "Aangepast model...",
"Custom Model ID": "Aangepast model-ID",
"Validate": "Valideren",
"Model available": "Model beschikbaar",
"Connection": "Verbinding",
"Test Connection": "Verbinding testen",
"Connected": "Verbonden"
}
@@ -894,7 +894,6 @@
"Array of annotations": "Tablica adnotacji",
"Highlighted text": "Zaznaczony tekst",
"Annotation note": "Notatka adnotacji",
"Update time": "Czas aktualizacji",
"Date Format Tokens:": "Tokeny formatu daty:",
"Year (4 digits)": "Rok (4 cyfry)",
"Month (01-12)": "Miesiąc (01-12)",
@@ -912,5 +911,50 @@
"Show on Discord": "Pokaż na Discord",
"Instant {{action}}": "Natychmiastowe {{action}}",
"Instant {{action}} Disabled": "Natychmiastowe {{action}} wyłączone",
"Annotation": "Adnotacja"
"Annotation": "Adnotacja",
"Reset Template": "Resetuj szablon",
"Annotation style": "Styl adnotacji",
"Annotation color": "Kolor adnotacji",
"Annotation time": "Czas adnotacji",
"AI": "AI",
"Are you sure you want to re-index this book?": "Czy na pewno chcesz ponownie zaindeksować tę książkę?",
"Enable AI in Settings": "Włącz AI w ustawieniach",
"Index This Book": "Zaindeksuj tę książkę",
"Enable AI search and chat for this book": "Włącz wyszukiwanie AI i czat dla tej książki",
"Start Indexing": "Rozpocznij indeksowanie",
"Indexing book...": "Indeksowanie książki...",
"Preparing...": "Przygotowywanie...",
"Delete this conversation?": "Usunąć tę rozmowę?",
"No conversations yet": "Brak rozmów",
"Start a new chat to ask questions about this book": "Rozpocznij nowy czat, aby zadać pytania o tę książkę",
"Rename": "Zmień nazwę",
"New Chat": "Nowy czat",
"Chat": "Czat",
"Please enter a model ID": "Wprowadź ID modelu",
"Model not available or invalid": "Model niedostępny lub nieprawidłowy",
"Failed to validate model": "Nie udało się zwalidować modelu",
"Couldn't connect to Ollama. Is it running?": "Nie można połączyć się z Ollama. Czy jest uruchomiona?",
"Invalid API key or connection failed": "Nieprawidłowy klucz API lub błąd połączenia",
"Connection failed": "Połączenie nieudane",
"AI Assistant": "Asystent AI",
"Enable AI Assistant": "Włącz asystenta AI",
"Provider": "Dostawca",
"Ollama (Local)": "Ollama (Lokalny)",
"AI Gateway (Cloud)": "Brama AI (Chmura)",
"Ollama Configuration": "Konfiguracja Ollama",
"Refresh Models": "Odśwież modele",
"AI Model": "Model AI",
"No models detected": "Nie wykryto modeli",
"AI Gateway Configuration": "Konfiguracja bramy AI",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Wybierz spośród wysokiej jakości, ekonomicznych modeli AI. Możesz również użyć własnego modelu, wybierając \"Model niestandardowy\" poniżej.",
"API Key": "Klucz API",
"Get Key": "Pobierz klucz",
"Model": "Model",
"Custom Model...": "Model niestandardowy...",
"Custom Model ID": "ID modelu niestandardowego",
"Validate": "Waliduj",
"Model available": "Model dostępny",
"Connection": "Połączenie",
"Test Connection": "Testuj połączenie",
"Connected": "Połączono"
}
@@ -884,7 +884,6 @@
"Array of annotations": "Lista de anotações",
"Highlighted text": "Texto destacado",
"Annotation note": "Nota de anotação",
"Update time": "Hora de atualização",
"Date Format Tokens:": "Tokens de formato de data:",
"Year (4 digits)": "Ano (4 dígitos)",
"Month (01-12)": "Mês (01-12)",
@@ -902,5 +901,50 @@
"Show on Discord": "Mostrar no Discord",
"Instant {{action}}": "{{action}} instantâneo",
"Instant {{action}} Disabled": "{{action}} instantâneo desativado",
"Annotation": "Anotação"
"Annotation": "Anotação",
"Reset Template": "Redefinir modelo",
"Annotation style": "Estilo de anotação",
"Annotation color": "Cor da anotação",
"Annotation time": "Hora da anotação",
"AI": "IA",
"Are you sure you want to re-index this book?": "Tem certeza de que deseja reindexar este livro?",
"Enable AI in Settings": "Ativar IA nas configurações",
"Index This Book": "Indexar este livro",
"Enable AI search and chat for this book": "Ativar pesquisa e chat com IA para este livro",
"Start Indexing": "Iniciar indexação",
"Indexing book...": "Indexando livro...",
"Preparing...": "Preparando...",
"Delete this conversation?": "Excluir esta conversa?",
"No conversations yet": "Ainda não há conversas",
"Start a new chat to ask questions about this book": "Inicie um novo chat para fazer perguntas sobre este livro",
"Rename": "Renomear",
"New Chat": "Novo chat",
"Chat": "Chat",
"Please enter a model ID": "Por favor, insira um ID de modelo",
"Model not available or invalid": "Modelo não disponível ou inválido",
"Failed to validate model": "Falha ao validar modelo",
"Couldn't connect to Ollama. Is it running?": "Não foi possível conectar ao Ollama. Está em execução?",
"Invalid API key or connection failed": "Chave API inválida ou falha na conexão",
"Connection failed": "Falha na conexão",
"AI Assistant": "Assistente de IA",
"Enable AI Assistant": "Ativar assistente de IA",
"Provider": "Provedor",
"Ollama (Local)": "Ollama (Local)",
"AI Gateway (Cloud)": "Gateway de IA (Nuvem)",
"Ollama Configuration": "Configuração do Ollama",
"Refresh Models": "Atualizar modelos",
"AI Model": "Modelo de IA",
"No models detected": "Nenhum modelo detectado",
"AI Gateway Configuration": "Configuração do gateway de IA",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Escolha entre uma seleção de modelos de IA de alta qualidade e econômicos. Você também pode usar seu próprio modelo selecionando \"Modelo personalizado\" abaixo.",
"API Key": "Chave API",
"Get Key": "Obter chave",
"Model": "Modelo",
"Custom Model...": "Modelo personalizado...",
"Custom Model ID": "ID do modelo personalizado",
"Validate": "Validar",
"Model available": "Modelo disponível",
"Connection": "Conexão",
"Test Connection": "Testar conexão",
"Connected": "Conectado"
}
@@ -894,7 +894,6 @@
"Array of annotations": "Массив примечаний",
"Highlighted text": "Выделенный текст",
"Annotation note": "Примечание аннотации",
"Update time": "Время обновления",
"Date Format Tokens:": "Токены формата даты:",
"Year (4 digits)": "Год (4 цифры)",
"Month (01-12)": "Месяц (01-12)",
@@ -912,5 +911,50 @@
"Show on Discord": "Показать в Discord",
"Instant {{action}}": "Мгновенное {{action}}",
"Instant {{action}} Disabled": "Мгновенное {{action}} отключено",
"Annotation": "Аннотация"
"Annotation": "Аннотация",
"Reset Template": "Сбросить шаблон",
"Annotation style": "Стиль аннотации",
"Annotation color": "Цвет аннотации",
"Annotation time": "Время аннотации",
"AI": "ИИ",
"Are you sure you want to re-index this book?": "Вы уверены, что хотите переиндексировать эту книгу?",
"Enable AI in Settings": "Включить ИИ в настройках",
"Index This Book": "Индексировать эту книгу",
"Enable AI search and chat for this book": "Включить ИИ-поиск и чат для этой книги",
"Start Indexing": "Начать индексацию",
"Indexing book...": "Индексация книги...",
"Preparing...": "Подготовка...",
"Delete this conversation?": "Удалить этот разговор?",
"No conversations yet": "Пока нет разговоров",
"Start a new chat to ask questions about this book": "Начните новый чат, чтобы задать вопросы об этой книге",
"Rename": "Переименовать",
"New Chat": "Новый чат",
"Chat": "Чат",
"Please enter a model ID": "Пожалуйста, введите ID модели",
"Model not available or invalid": "Модель недоступна или недействительна",
"Failed to validate model": "Не удалось проверить модель",
"Couldn't connect to Ollama. Is it running?": "Не удалось подключиться к Ollama. Он запущен?",
"Invalid API key or connection failed": "Недействительный API-ключ или ошибка подключения",
"Connection failed": "Ошибка подключения",
"AI Assistant": "ИИ-ассистент",
"Enable AI Assistant": "Включить ИИ-ассистента",
"Provider": "Провайдер",
"Ollama (Local)": "Ollama (Локальный)",
"AI Gateway (Cloud)": "ИИ-шлюз (Облако)",
"Ollama Configuration": "Настройка Ollama",
"Refresh Models": "Обновить модели",
"AI Model": "ИИ-модель",
"No models detected": "Модели не обнаружены",
"AI Gateway Configuration": "Настройка ИИ-шлюза",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Выберите из высококачественных, экономичных ИИ-моделей. Вы также можете использовать свою модель, выбрав \"Пользовательская модель\" ниже.",
"API Key": "API-ключ",
"Get Key": "Получить ключ",
"Model": "Модель",
"Custom Model...": "Пользовательская модель...",
"Custom Model ID": "ID пользовательской модели",
"Validate": "Проверить",
"Model available": "Модель доступна",
"Connection": "Соединение",
"Test Connection": "Проверить соединение",
"Connected": "Подключено"
}
@@ -874,7 +874,6 @@
"Array of annotations": "අනුසටහන් අරය",
"Highlighted text": "ඉස්මතු කළ පෙළ",
"Annotation note": "අනුසටහන් සටහන",
"Update time": "යාවත්කාලීන කාලය",
"Date Format Tokens:": "දිනය ආකෘති ටෝකන:",
"Year (4 digits)": "වසර (අංක 4)",
"Month (01-12)": "මාසය (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Discord හි පෙන්වන්න",
"Instant {{action}}": "ක්ෂණික {{action}}",
"Instant {{action}} Disabled": "ක්ෂණික {{action}} අබල කර ඇත",
"Annotation": "විවරණය"
"Annotation": "විවරණය",
"Reset Template": "සැකිල්ල නැවත සකස් කරන්න",
"Annotation style": "විවරණ විලාසය",
"Annotation color": "විවරණ වර්ණය",
"Annotation time": "විවරණ වේලාව",
"AI": "කෘතිම බුද්ධිය (AI)",
"Are you sure you want to re-index this book?": "ඔබට මෙම පොත නැවත දර්ශකය කිරීමට අවශ්‍ය බව සහතිකද?",
"Enable AI in Settings": "සැකසුම් තුළ AI සක්‍රීය කරන්න",
"Index This Book": "මෙම පොත දර්ශකය කරන්න",
"Enable AI search and chat for this book": "මෙම පොත සඳහා AI සෙවීම සහ කතාබස් සක්‍රීය කරන්න",
"Start Indexing": "දර්ශකය කිරීම ආරම්භ කරන්න",
"Indexing book...": "පොත දර්ශකය කරමින්...",
"Preparing...": "සූදානම් වෙමින්...",
"Delete this conversation?": "මෙම සංවාදය මකා දමන්නද?",
"No conversations yet": "තවම සංවාද නැත",
"Start a new chat to ask questions about this book": "මෙම පොත ගැන ප්‍රශ්න ඇසීමට නව කතාබහක් ආරම්භ කරන්න",
"Rename": "නම වෙනස් කරන්න",
"New Chat": "නව කතාබහ",
"Chat": "කතාබහ",
"Please enter a model ID": "කරුණාකර මාදිලි හැඳුනුම්පතක් ඇතුළත් කරන්න",
"Model not available or invalid": "මාදිලිය ලබා ගත නොහැක හෝ අවලංගුයි",
"Failed to validate model": "මාදිලිය තහවුරු කිරීමට අසමත් විය",
"Couldn't connect to Ollama. Is it running?": "Ollama වෙත සම්බන්ධ වීමට නොහැකි විය. එය ක්‍රියාත්මක වේද?",
"Invalid API key or connection failed": "අවලංගු API යතුරක් හෝ සම්බන්ධතාවය අසාර්ථක විය",
"Connection failed": "සම්බන්ධතාවය අසාර්ථක විය",
"AI Assistant": "AI සහායකයා",
"Enable AI Assistant": "AI සහායකයා සක්‍රීය කරන්න",
"Provider": "සපයන්නා",
"Ollama (Local)": "Ollama (දේශීය)",
"AI Gateway (Cloud)": "AI Gateway (Cloud)",
"Ollama Configuration": "Ollama වින්‍යාසය",
"Refresh Models": "මාදිලි යාවත්කාලීන කරන්න",
"AI Model": "AI මාදිලිය",
"No models detected": "කිසිදු මාදිලියක් හඳුනාගෙන නැත",
"AI Gateway Configuration": "AI Gateway වින්‍යාසය",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "උසස් තත්ත්වයේ, ලාභදායී AI මාදිලි එකතුවකින් තෝරන්න. පහතින් \"අභිරුචි මාදිලිය\" තේරීමෙන් ඔබට ඔබේම මාදිලියක් ද ගෙන ඒමට හැකිය.",
"API Key": "API යතුර",
"Get Key": "යතුර ලබා ගන්න",
"Model": "මාදිලිය",
"Custom Model...": "අභිරුචි මාදිලිය...",
"Custom Model ID": "අභිරුචි මාදිලි හැඳුනුම්පත",
"Validate": "තහවුරු කරන්න",
"Model available": "මාදිලිය ලබා ගත හැකිය",
"Connection": "සම්බන්ධතාවය",
"Test Connection": "සම්බන්ධතාවය පරීක්ෂා කරන්න",
"Connected": "සම්බන්ධ විය"
}
@@ -874,7 +874,6 @@
"Array of annotations": "Lista med anteckningar",
"Highlighted text": "Markerad text",
"Annotation note": "Anteckningsnotering",
"Update time": "Uppdateringstid",
"Date Format Tokens:": "Datumformattecken:",
"Year (4 digits)": "År (4 siffror)",
"Month (01-12)": "Månad (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Visa på Discord",
"Instant {{action}}": "Omedelbar {{action}}",
"Instant {{action}} Disabled": "Omedelbar {{action}} inaktiverad",
"Annotation": "Anteckning"
"Annotation": "Anteckning",
"Reset Template": "Återställ mall",
"Annotation style": "Anteckningsstil",
"Annotation color": "Anteckningsfärg",
"Annotation time": "Anteckningstid",
"AI": "AI",
"Are you sure you want to re-index this book?": "Är du säker på att du vill indexera om denna bok?",
"Enable AI in Settings": "Aktivera AI i inställningar",
"Index This Book": "Indexera denna bok",
"Enable AI search and chat for this book": "Aktivera AI-sökning och chatt för denna bok",
"Start Indexing": "Starta indexering",
"Indexing book...": "Indexerar bok...",
"Preparing...": "Förbereder...",
"Delete this conversation?": "Ta bort denna konversation?",
"No conversations yet": "Inga konversationer ännu",
"Start a new chat to ask questions about this book": "Starta en ny chatt för att ställa frågor om denna bok",
"Rename": "Byt namn",
"New Chat": "Ny chatt",
"Chat": "Chatt",
"Please enter a model ID": "Ange ett modell-ID",
"Model not available or invalid": "Modell ej tillgänglig eller ogiltig",
"Failed to validate model": "Misslyckades att validera modell",
"Couldn't connect to Ollama. Is it running?": "Kunde inte ansluta till Ollama. Körs den?",
"Invalid API key or connection failed": "Ogiltig API-nyckel eller anslutningen misslyckades",
"Connection failed": "Anslutningen misslyckades",
"AI Assistant": "AI-assistent",
"Enable AI Assistant": "Aktivera AI-assistent",
"Provider": "Leverantör",
"Ollama (Local)": "Ollama (Lokal)",
"AI Gateway (Cloud)": "AI-gateway (Moln)",
"Ollama Configuration": "Ollama-konfiguration",
"Refresh Models": "Uppdatera modeller",
"AI Model": "AI-modell",
"No models detected": "Inga modeller hittades",
"AI Gateway Configuration": "AI-gateway-konfiguration",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Välj bland ett urval av högkvalitativa, ekonomiska AI-modeller. Du kan också använda din egen modell genom att välja \"Anpassad modell\" nedan.",
"API Key": "API-nyckel",
"Get Key": "Hämta nyckel",
"Model": "Modell",
"Custom Model...": "Anpassad modell...",
"Custom Model ID": "Anpassat modell-ID",
"Validate": "Validera",
"Model available": "Modell tillgänglig",
"Connection": "Anslutning",
"Test Connection": "Testa anslutning",
"Connected": "Ansluten"
}
@@ -874,7 +874,6 @@
"Array of annotations": "குறிப்புகளின் பட்டியல்",
"Highlighted text": "முன்னிலைப்படுத்தப்பட்ட உரை",
"Annotation note": "குறிப்பு குறிப்பு",
"Update time": "புதுப்பிப்பு நேரம்",
"Date Format Tokens:": "தேதி வடிவமைப்பு டோக்கன்கள்:",
"Year (4 digits)": "ஆண்டு (4 இலக்கங்கள்)",
"Month (01-12)": "மாதம் (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Discord இல் காட்டு",
"Instant {{action}}": "உடனடி {{action}}",
"Instant {{action}} Disabled": "உடனடி {{action}} முடக்கப்பட்டது",
"Annotation": "குறிப்பு"
"Annotation": "குறிப்பு",
"Reset Template": "வார்ப்புருவை மீட்டமைக்கவும்",
"Annotation style": "சிறுகுறிப்பு பாணி",
"Annotation color": "சிறுகுறிப்பு நிறம்",
"Annotation time": "சிறுகுறிப்பு நேரம்",
"AI": "செயற்கை நுண்ணறிவு (AI)",
"Are you sure you want to re-index this book?": "இந்த புத்தகத்தை மீண்டும் அட்டவணைப்படுத்த உறுதியாக இருக்கிறீர்களா?",
"Enable AI in Settings": "அமைப்புகளில் AI ஐ இயக்கவும்",
"Index This Book": "இந்த புத்தகத்தை அட்டவணைப்படுத்தவும்",
"Enable AI search and chat for this book": "இந்த புத்தகத்திற்கு AI தேடல் மற்றும் அரட்டையை இயக்கவும்",
"Start Indexing": "அட்டவணைப்படுத்தலைத் தொடங்கவும்",
"Indexing book...": "புத்தகம் அட்டவணைப்படுத்தப்படுகிறது...",
"Preparing...": "தயாராகிறது...",
"Delete this conversation?": "இந்த உரையாடலை நீக்கவா?",
"No conversations yet": "இதுவரை உரையாடல்கள் எதுவும் இல்லை",
"Start a new chat to ask questions about this book": "இந்த புத்தகத்தைப் பற்றி கேள்விகள் கேட்க புதிய அரட்டையைத் தொடங்கவும்",
"Rename": "மறுபெயரிடு",
"New Chat": "புதிய அரட்டை",
"Chat": "அரட்டை",
"Please enter a model ID": "மாதிரி ஐடியை உள்ளிடவும்",
"Model not available or invalid": "மாதிரி கிடைக்கவில்லை அல்லது தவறானது",
"Failed to validate model": "மாதிரியைச் சரிபார்க்கத் தவறிவிட்டது",
"Couldn't connect to Ollama. Is it running?": "Ollama வுடன் இணைக்க முடியவில்லை. அது இயங்குகிறதா?",
"Invalid API key or connection failed": "தவறான ஏபிஐ (API) விசை அல்லது இணைப்பு தோல்வியடைந்தது",
"Connection failed": "இணைப்பு தோல்வியடைந்தது",
"AI Assistant": "AI உதவியாளர்",
"Enable AI Assistant": "AI உதவியாளரை இயக்கவும்",
"Provider": "வழங்குநர்",
"Ollama (Local)": "Ollama (உள்ளூர்)",
"AI Gateway (Cloud)": "AI கேட்வே (கிளவுட்)",
"Ollama Configuration": "Ollama உள்ளமைவு",
"Refresh Models": "மாதிரிகளைப் புதுப்பிக்கவும்",
"AI Model": "AI மாதிரி",
"No models detected": "மாதிரிகள் எதுவும் கண்டறியப்படவில்லை",
"AI Gateway Configuration": "AI கேட்வே உள்ளமைவு",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "உயர்தர, சிக்கனமான AI மாதிரிகளிலிருந்து தேர்ந்தெடுக்கவும். கீழே உள்ள \"தனிப்பயன் மாதிரி\" என்பதைத் தேர்ந்தெடுப்பதன் மூலம் உங்கள் சொந்த மாதிரியையும் கொண்டு வரலாம்.",
"API Key": "API விசை",
"Get Key": "விசையைப் பெறவும்",
"Model": "மாதிரி",
"Custom Model...": "தனிப்பயன் மாதிரி...",
"Custom Model ID": "தனிப்பயன் மாதிரி ஐடி",
"Validate": "சரிபார்க்கவும்",
"Model available": "மாதிரி கிடைக்கிறது",
"Connection": "இணைப்பு",
"Test Connection": "இணைப்பைச் சோதிக்கவும்",
"Connected": "இணைக்கப்பட்டது"
}
@@ -864,7 +864,6 @@
"Array of annotations": "อาร์เรย์ของคำอธิบาย",
"Highlighted text": "ข้อความที่ไฮไลต์",
"Annotation note": "หมายเหตุคำอธิบาย",
"Update time": "เวลาอัปเดต",
"Date Format Tokens:": "โทเค็นรูปแบบวันที่:",
"Year (4 digits)": "ปี (4 หลัก)",
"Month (01-12)": "เดือน (01-12)",
@@ -882,5 +881,50 @@
"Show on Discord": "แสดงบน Discord",
"Instant {{action}}": "{{action}}ทันที",
"Instant {{action}} Disabled": "{{action}}ทันทีถูกปิดใช้งาน",
"Annotation": "คำอธิบาย"
"Annotation": "คำอธิบาย",
"Reset Template": "รีเซ็ตเทมเพลต",
"Annotation style": "สไตล์คำอธิบาย",
"Annotation color": "สีคำอธิบาย",
"Annotation time": "เวลาคำอธิบาย",
"AI": "AI",
"Are you sure you want to re-index this book?": "คุณแน่ใจหรือไม่ว่าต้องการสร้างดัชนีหนังสือเล่มนี้ใหม่?",
"Enable AI in Settings": "เปิดใช้งาน AI ในการตั้งค่า",
"Index This Book": "สร้างดัชนีหนังสือเล่มนี้",
"Enable AI search and chat for this book": "เปิดใช้งานการค้นหา AI และแชทสำหรับหนังสือเล่มนี้",
"Start Indexing": "เริ่มสร้างดัชนี",
"Indexing book...": "กำลังสร้างดัชนีหนังสือ...",
"Preparing...": "กำลังเตรียม...",
"Delete this conversation?": "ลบการสนทนานี้?",
"No conversations yet": "ยังไม่มีการสนทนา",
"Start a new chat to ask questions about this book": "เริ่มแชทใหม่เพื่อถามคำถามเกี่ยวกับหนังสือเล่มนี้",
"Rename": "เปลี่ยนชื่อ",
"New Chat": "แชทใหม่",
"Chat": "แชท",
"Please enter a model ID": "กรุณาป้อน ID โมเดล",
"Model not available or invalid": "โมเดลไม่พร้อมใช้งานหรือไม่ถูกต้อง",
"Failed to validate model": "ไม่สามารถตรวจสอบโมเดลได้",
"Couldn't connect to Ollama. Is it running?": "ไม่สามารถเชื่อมต่อกับ Ollama ได้ กำลังทำงานอยู่หรือไม่?",
"Invalid API key or connection failed": "คีย์ API ไม่ถูกต้องหรือการเชื่อมต่อล้มเหลว",
"Connection failed": "การเชื่อมต่อล้มเหลว",
"AI Assistant": "ผู้ช่วย AI",
"Enable AI Assistant": "เปิดใช้งานผู้ช่วย AI",
"Provider": "ผู้ให้บริการ",
"Ollama (Local)": "Ollama (ในเครื่อง)",
"AI Gateway (Cloud)": "เกตเวย์ AI (คลาวด์)",
"Ollama Configuration": "การกำหนดค่า Ollama",
"Refresh Models": "รีเฟรชโมเดล",
"AI Model": "โมเดล AI",
"No models detected": "ไม่พบโมเดล",
"AI Gateway Configuration": "การกำหนดค่าเกตเวย์ AI",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "เลือกจากโมเดล AI คุณภาพสูงและประหยัด คุณยังสามารถใช้โมเดลของคุณเองโดยเลือก \"โมเดลกำหนดเอง\" ด้านล่าง",
"API Key": "คีย์ API",
"Get Key": "รับคีย์",
"Model": "โมเดล",
"Custom Model...": "โมเดลกำหนดเอง...",
"Custom Model ID": "ID โมเดลกำหนดเอง",
"Validate": "ตรวจสอบ",
"Model available": "โมเดลพร้อมใช้งาน",
"Connection": "การเชื่อมต่อ",
"Test Connection": "ทดสอบการเชื่อมต่อ",
"Connected": "เชื่อมต่อแล้ว"
}
@@ -874,7 +874,6 @@
"Array of annotations": "Notlar dizisi",
"Highlighted text": "Vurgulanan metin",
"Annotation note": "Not açıklaması",
"Update time": "Güncelleme zamanı",
"Date Format Tokens:": "Tarih Biçimi Belirteçleri:",
"Year (4 digits)": "Yıl (4 basamak)",
"Month (01-12)": "Ay (01-12)",
@@ -892,5 +891,50 @@
"Show on Discord": "Discord'da göster",
"Instant {{action}}": "Anında {{action}}",
"Instant {{action}} Disabled": "Anında {{action}} Devre Dışı",
"Annotation": "Açıklama"
"Annotation": "Açıklama",
"Reset Template": "Şablonu Sıfırla",
"Annotation style": "Açıklama stili",
"Annotation color": "Açıklama rengi",
"Annotation time": "Açıklama zamanı",
"AI": "YZ",
"Are you sure you want to re-index this book?": "Bu kitabı yeniden dizinlemek istediğinizden emin misiniz?",
"Enable AI in Settings": "Ayarlarda YZ'yi Etkinleştir",
"Index This Book": "Bu Kitabı Dizinle",
"Enable AI search and chat for this book": "Bu kitap için YZ arama ve sohbeti etkinleştir",
"Start Indexing": "Dizinlemeyi Başlat",
"Indexing book...": "Kitap dizinleniyor...",
"Preparing...": "Hazırlanıyor...",
"Delete this conversation?": "Bu konuşmayı sil?",
"No conversations yet": "Henüz konuşma yok",
"Start a new chat to ask questions about this book": "Bu kitap hakkında soru sormak için yeni bir sohbet başlatın",
"Rename": "Yeniden Adlandır",
"New Chat": "Yeni Sohbet",
"Chat": "Sohbet",
"Please enter a model ID": "Lütfen bir model kimliği girin",
"Model not available or invalid": "Model kullanılamıyor veya geçersiz",
"Failed to validate model": "Model doğrulanamadı",
"Couldn't connect to Ollama. Is it running?": "Ollama'ya bağlanılamadı. Çalışıyor mu?",
"Invalid API key or connection failed": "Geçersiz API anahtarı veya bağlantı başarısız",
"Connection failed": "Bağlantı başarısız",
"AI Assistant": "YZ Asistanı",
"Enable AI Assistant": "YZ Asistanını Etkinleştir",
"Provider": "Sağlayıcı",
"Ollama (Local)": "Ollama (Yerel)",
"AI Gateway (Cloud)": "YZ Geçidi (Bulut)",
"Ollama Configuration": "Ollama Yapılandırması",
"Refresh Models": "Modelleri Yenile",
"AI Model": "YZ Modeli",
"No models detected": "Model algılanmadı",
"AI Gateway Configuration": "YZ Geçidi Yapılandırması",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Yüksek kaliteli, ekonomik YZ modelleri arasından seçim yapın. Aşağıdaki \"Özel Model\"i seçerek kendi modelinizi de kullanabilirsiniz.",
"API Key": "API Anahtarı",
"Get Key": "Anahtar Al",
"Model": "Model",
"Custom Model...": "Özel Model...",
"Custom Model ID": "Özel Model Kimliği",
"Validate": "Doğrula",
"Model available": "Model mevcut",
"Connection": "Bağlantı",
"Test Connection": "Bağlantıyı Test Et",
"Connected": "Bağlandı"
}
@@ -894,7 +894,6 @@
"Array of annotations": "Масив приміток",
"Highlighted text": "Виділений текст",
"Annotation note": "Примітка анотації",
"Update time": "Час оновлення",
"Date Format Tokens:": "Токени формату дати:",
"Year (4 digits)": "Рік (4 цифри)",
"Month (01-12)": "Місяць (01-12)",
@@ -912,5 +911,50 @@
"Show on Discord": "Показати в Discord",
"Instant {{action}}": "Миттєва {{action}}",
"Instant {{action}} Disabled": "Миттєву {{action}} вимкнено",
"Annotation": "Анотація"
"Annotation": "Анотація",
"Reset Template": "Скинути шаблон",
"Annotation style": "Стиль анотації",
"Annotation color": "Колір анотації",
"Annotation time": "Час анотації",
"AI": "ШІ",
"Are you sure you want to re-index this book?": "Ви впевнені, що хочете переіндексувати цю книгу?",
"Enable AI in Settings": "Увімкнути ШІ в налаштуваннях",
"Index This Book": "Індексувати цю книгу",
"Enable AI search and chat for this book": "Увімкнути ШІ-пошук та чат для цієї книги",
"Start Indexing": "Почати індексацію",
"Indexing book...": "Індексація книги...",
"Preparing...": "Підготовка...",
"Delete this conversation?": "Видалити цю розмову?",
"No conversations yet": "Поки немає розмов",
"Start a new chat to ask questions about this book": "Почніть новий чат, щоб задати питання про цю книгу",
"Rename": "Перейменувати",
"New Chat": "Новий чат",
"Chat": "Чат",
"Please enter a model ID": "Будь ласка, введіть ID моделі",
"Model not available or invalid": "Модель недоступна або недійсна",
"Failed to validate model": "Не вдалося перевірити модель",
"Couldn't connect to Ollama. Is it running?": "Не вдалося підключитися до Ollama. Чи вона запущена?",
"Invalid API key or connection failed": "Недійсний API-ключ або помилка підключення",
"Connection failed": "Помилка підключення",
"AI Assistant": "ШІ-асистент",
"Enable AI Assistant": "Увімкнути ШІ-асистента",
"Provider": "Провайдер",
"Ollama (Local)": "Ollama (Локальний)",
"AI Gateway (Cloud)": "ШІ-шлюз (Хмара)",
"Ollama Configuration": "Налаштування Ollama",
"Refresh Models": "Оновити моделі",
"AI Model": "ШІ-модель",
"No models detected": "Моделі не знайдено",
"AI Gateway Configuration": "Налаштування ШІ-шлюзу",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Оберіть з високоякісних, економічних ШІ-моделей. Ви також можете використати власну модель, обравши \"Користувацька модель\" нижче.",
"API Key": "API-ключ",
"Get Key": "Отримати ключ",
"Model": "Модель",
"Custom Model...": "Користувацька модель...",
"Custom Model ID": "ID користувацької моделі",
"Validate": "Перевірити",
"Model available": "Модель доступна",
"Connection": "З'єднання",
"Test Connection": "Перевірити з'єднання",
"Connected": "Підключено"
}
@@ -864,7 +864,6 @@
"Array of annotations": "Danh sách chú thích",
"Highlighted text": "Văn bản được đánh dấu",
"Annotation note": "Ghi chú chú thích",
"Update time": "Thời gian cập nhật",
"Date Format Tokens:": "Token định dạng ngày:",
"Year (4 digits)": "Năm (4 chữ số)",
"Month (01-12)": "Tháng (01-12)",
@@ -882,5 +881,50 @@
"Show on Discord": "Hiện trên Discord",
"Instant {{action}}": "{{action}} tức thì",
"Instant {{action}} Disabled": "{{action}} tức thì đã tắt",
"Annotation": "Chú thích"
"Annotation": "Chú thích",
"Reset Template": "Đặt lại mẫu",
"Annotation style": "Kiểu chú thích",
"Annotation color": "Màu chú thích",
"Annotation time": "Thời gian chú thích",
"AI": "AI",
"Are you sure you want to re-index this book?": "Bạn có chắc muốn lập chỉ mục lại cuốn sách này?",
"Enable AI in Settings": "Bật AI trong Cài đặt",
"Index This Book": "Lập chỉ mục sách này",
"Enable AI search and chat for this book": "Bật tìm kiếm AI và trò chuyện cho cuốn sách này",
"Start Indexing": "Bắt đầu lập chỉ mục",
"Indexing book...": "Đang lập chỉ mục sách...",
"Preparing...": "Đang chuẩn bị...",
"Delete this conversation?": "Xóa cuộc trò chuyện này?",
"No conversations yet": "Chưa có cuộc trò chuyện nào",
"Start a new chat to ask questions about this book": "Bắt đầu cuộc trò chuyện mới để hỏi về cuốn sách này",
"Rename": "Đổi tên",
"New Chat": "Cuộc trò chuyện mới",
"Chat": "Trò chuyện",
"Please enter a model ID": "Vui lòng nhập ID mô hình",
"Model not available or invalid": "Mô hình không khả dụng hoặc không hợp lệ",
"Failed to validate model": "Xác thực mô hình thất bại",
"Couldn't connect to Ollama. Is it running?": "Không thể kết nối với Ollama. Nó có đang chạy không?",
"Invalid API key or connection failed": "Khóa API không hợp lệ hoặc kết nối thất bại",
"Connection failed": "Kết nối thất bại",
"AI Assistant": "Trợ lý AI",
"Enable AI Assistant": "Bật trợ lý AI",
"Provider": "Nhà cung cấp",
"Ollama (Local)": "Ollama (Cục bộ)",
"AI Gateway (Cloud)": "Cổng AI (Đám mây)",
"Ollama Configuration": "Cấu hình Ollama",
"Refresh Models": "Làm mới mô hình",
"AI Model": "Mô hình AI",
"No models detected": "Không phát hiện mô hình nào",
"AI Gateway Configuration": "Cấu hình cổng AI",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "Chọn từ các mô hình AI chất lượng cao, tiết kiệm. Bạn cũng có thể sử dụng mô hình riêng bằng cách chọn \"Mô hình tùy chỉnh\" bên dưới.",
"API Key": "Khóa API",
"Get Key": "Lấy khóa",
"Model": "Mô hình",
"Custom Model...": "Mô hình tùy chỉnh...",
"Custom Model ID": "ID mô hình tùy chỉnh",
"Validate": "Xác thực",
"Model available": "Mô hình khả dụng",
"Connection": "Kết nối",
"Test Connection": "Kiểm tra kết nối",
"Connected": "Đã kết nối"
}
@@ -864,7 +864,6 @@
"Array of annotations": "标注数组",
"Highlighted text": "高亮文本",
"Annotation note": "标注笔记",
"Update time": "更新时间",
"Date Format Tokens:": "日期格式标记:",
"Year (4 digits)": "年(4位)",
"Month (01-12)": "月(01-12",
@@ -882,5 +881,50 @@
"Show on Discord": "在Discord显示",
"Instant {{action}}": "即时{{action}}",
"Instant {{action}} Disabled": "即时{{action}}已禁用",
"Annotation": "标注"
"Annotation": "标注",
"Reset Template": "重置模板",
"Annotation style": "标注样式",
"Annotation color": "标注颜色",
"Annotation time": "标注时间",
"AI": "AI",
"Are you sure you want to re-index this book?": "确定要重新索引这本书吗?",
"Enable AI in Settings": "在设置中启用 AI",
"Index This Book": "索引这本书",
"Enable AI search and chat for this book": "为这本书启用 AI 搜索和聊天",
"Start Indexing": "开始索引",
"Indexing book...": "正在索引书籍...",
"Preparing...": "准备中...",
"Delete this conversation?": "删除此对话?",
"No conversations yet": "暂无对话",
"Start a new chat to ask questions about this book": "开始新对话来询问关于这本书的问题",
"Rename": "重命名",
"New Chat": "新对话",
"Chat": "聊天",
"Please enter a model ID": "请输入模型 ID",
"Model not available or invalid": "模型不可用或无效",
"Failed to validate model": "模型验证失败",
"Couldn't connect to Ollama. Is it running?": "无法连接到 Ollama。它是否正在运行?",
"Invalid API key or connection failed": "API 密钥无效或连接失败",
"Connection failed": "连接失败",
"AI Assistant": "AI 助手",
"Enable AI Assistant": "启用 AI 助手",
"Provider": "提供商",
"Ollama (Local)": "Ollama(本地)",
"AI Gateway (Cloud)": "AI 网关(云端)",
"Ollama Configuration": "Ollama 配置",
"Refresh Models": "刷新模型",
"AI Model": "AI 模型",
"No models detected": "未检测到模型",
"AI Gateway Configuration": "AI 网关配置",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "从一系列高质量、经济实惠的 AI 模型中选择。您也可以通过下方的\"自定义模型\"使用自己的模型。",
"API Key": "API 密钥",
"Get Key": "获取密钥",
"Model": "模型",
"Custom Model...": "自定义模型...",
"Custom Model ID": "自定义模型 ID",
"Validate": "验证",
"Model available": "模型可用",
"Connection": "连接",
"Test Connection": "测试连接",
"Connected": "已连接"
}
@@ -864,7 +864,6 @@
"Array of annotations": "標註陣列",
"Highlighted text": "標記文字",
"Annotation note": "標註筆記",
"Update time": "更新時間",
"Date Format Tokens:": "日期格式標記:",
"Year (4 digits)": "年(4位)",
"Month (01-12)": "月(01-12",
@@ -882,5 +881,50 @@
"Show on Discord": "在Discord顯示",
"Instant {{action}}": "即時{{action}}",
"Instant {{action}} Disabled": "即時{{action}}已停用",
"Annotation": "註解"
"Annotation": "註解",
"Reset Template": "重置範本",
"Annotation style": "標註樣式",
"Annotation color": "標註顏色",
"Annotation time": "標註時間",
"AI": "AI",
"Are you sure you want to re-index this book?": "確定要重新索引這本書嗎?",
"Enable AI in Settings": "在設定中啟用 AI",
"Index This Book": "索引這本書",
"Enable AI search and chat for this book": "為這本書啟用 AI 搜尋和聊天",
"Start Indexing": "開始索引",
"Indexing book...": "正在索引書籍...",
"Preparing...": "準備中...",
"Delete this conversation?": "刪除此對話?",
"No conversations yet": "尚無對話",
"Start a new chat to ask questions about this book": "開始新對話來詢問關於這本書的問題",
"Rename": "重新命名",
"New Chat": "新對話",
"Chat": "聊天",
"Please enter a model ID": "請輸入模型 ID",
"Model not available or invalid": "模型不可用或無效",
"Failed to validate model": "模型驗證失敗",
"Couldn't connect to Ollama. Is it running?": "無法連線到 Ollama。它是否正在執行?",
"Invalid API key or connection failed": "API 金鑰無效或連線失敗",
"Connection failed": "連線失敗",
"AI Assistant": "AI 助手",
"Enable AI Assistant": "啟用 AI 助手",
"Provider": "提供者",
"Ollama (Local)": "Ollama(本機)",
"AI Gateway (Cloud)": "AI 閘道(雲端)",
"Ollama Configuration": "Ollama 設定",
"Refresh Models": "重新整理模型",
"AI Model": "AI 模型",
"No models detected": "未偵測到模型",
"AI Gateway Configuration": "AI 閘道設定",
"Choose from a selection of high-quality, economical AI models. You can also bring your own model by selecting \"Custom Model\" below.": "從一系列高品質、經濟實惠的 AI 模型中選擇。您也可以透過下方的「自訂模型」使用自己的模型。",
"API Key": "API 金鑰",
"Get Key": "取得金鑰",
"Model": "模型",
"Custom Model...": "自訂模型...",
"Custom Model ID": "自訂模型 ID",
"Validate": "驗證",
"Model available": "模型可用",
"Connection": "連線",
"Test Connection": "測試連線",
"Connected": "已連線"
}
@@ -0,0 +1,572 @@
import { describe, it, expect } from 'vitest';
import { renderNoteTemplate, validateNoteTemplate, NoteTemplateData } from '../../utils/note';
describe('renderNoteTemplate', () => {
const sampleData: NoteTemplateData = {
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald',
exportDate: '2024-01-15',
chapters: [
{
title: 'Chapter 1',
annotations: [
{
text: 'In my younger and more vulnerable years',
note: 'Opening line',
style: 'highlight',
color: 'yellow',
timestamp: 1705312800000, // 2024-01-15 10:00:00 UTC
},
{
text: 'So we beat on, boats against the current',
note: '',
style: 'underline',
color: 'blue',
timestamp: 1705316400000, // 2024-01-15 11:00:00 UTC
},
],
},
{
title: 'Chapter 2',
annotations: [
{
text: 'The eyes of Doctor T. J. Eckleburg',
note: 'Symbolism',
style: 'highlight',
color: 'green',
timestamp: 1705320000000, // 2024-01-15 12:00:00 UTC
},
],
},
],
};
describe('Variable substitution', () => {
it('should substitute simple variables', () => {
const template = 'Book: {{ title }} by {{ author }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Book: The Great Gatsby by F. Scott Fitzgerald');
});
it('should handle undefined variables gracefully', () => {
const template = '{{ nonexistent }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('');
});
it('should handle nested property access', () => {
const template = '{{ chapters[0].title }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Chapter 1');
});
it('should handle deeply nested property access', () => {
const template = '{{ chapters[0].annotations[0].text }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('In my younger and more vulnerable years');
});
});
describe('For loops', () => {
it('should iterate over arrays', () => {
const template = '{% for chapter in chapters %}{{ chapter.title }}\n{% endfor %}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('Chapter 1');
expect(result).toContain('Chapter 2');
});
it('should handle nested for loops', () => {
const template = `{% for chapter in chapters %}
## {{ chapter.title }}
{% for annotation in chapter.annotations %}
- {{ annotation.text }}
{% endfor %}
{% endfor %}`;
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('## Chapter 1');
expect(result).toContain('- In my younger and more vulnerable years');
expect(result).toContain('## Chapter 2');
expect(result).toContain('- The eyes of Doctor T. J. Eckleburg');
});
it('should handle empty arrays', () => {
const emptyData: NoteTemplateData = {
title: 'Empty Book',
author: 'Nobody',
exportDate: '2024-01-15',
chapters: [],
};
const template = '{% for chapter in chapters %}{{ chapter.title }}{% endfor %}';
const result = renderNoteTemplate(template, emptyData);
expect(result).toBe('');
});
it('should provide loop variables (loop.index, loop.first, loop.last)', () => {
const template = `{% for chapter in chapters %}{{ loop.index }}: {{ chapter.title }}{% if not loop.last %}, {% endif %}{% endfor %}`;
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('1: Chapter 1, 2: Chapter 2');
});
it('should provide loop.index0 (0-based index)', () => {
const template = `{% for chapter in chapters %}{{ loop.index0 }}: {{ chapter.title }}\n{% endfor %}`;
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('0: Chapter 1');
expect(result).toContain('1: Chapter 2');
});
});
describe('Conditionals', () => {
it('should handle if statements', () => {
const template = '{% if author %}Author: {{ author }}{% endif %}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Author: F. Scott Fitzgerald');
});
it('should handle if-else statements', () => {
const template = '{% if nonexistent %}Has value{% else %}No value{% endif %}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('No value');
});
it('should handle if-elif-else statements', () => {
const template =
'{% if chapters.length > 5 %}Many{% elif chapters.length > 1 %}Some{% else %}Few{% endif %}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Some');
});
it('should handle if statements with annotation note', () => {
const template = `{% for chapter in chapters %}{% for annotation in chapter.annotations %}{% if annotation.note %}Note: {{ annotation.note }}
{% endif %}{% endfor %}{% endfor %}`;
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('Note: Opening line');
expect(result).toContain('Note: Symbolism');
expect(result.match(/Note:/g)?.length).toBe(2);
});
});
describe('Date filter', () => {
it('should format timestamp with default locale format', () => {
const template = '{{ chapters[0].annotations[0].timestamp | date }}';
const result = renderNoteTemplate(template, sampleData);
// Result will be locale-dependent, just check it's not empty
expect(result).not.toBe('');
expect(result.length).toBeGreaterThan(0);
});
it('should format timestamp with custom format %Y-%m-%d', () => {
const template = "{{ chapters[0].annotations[0].timestamp | date('%Y-%m-%d') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('2024-01-15');
});
it('should format timestamp with time format %H:%M:%S', () => {
const template = "{{ chapters[0].annotations[0].timestamp | date('%H:%M:%S') }}";
const result = renderNoteTemplate(template, sampleData);
// This depends on timezone, so just check the format
expect(result).toMatch(/^\d{2}:\d{2}:\d{2}$/);
});
it('should format timestamp with full datetime', () => {
const template = "{{ chapters[0].annotations[0].timestamp | date('%Y-%m-%d %H:%M') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toMatch(/^2024-01-15 \d{2}:\d{2}$/);
});
it('should handle 12-hour format with %I and %p', () => {
const template = "{{ chapters[0].annotations[0].timestamp | date('%I:%M %p') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toMatch(/^\d{2}:\d{2} (AM|PM)$/);
});
it('should handle undefined timestamp', () => {
const dataWithoutTimestamp: NoteTemplateData = {
...sampleData,
chapters: [
{
title: 'Chapter',
annotations: [{ text: 'Text' }],
},
],
};
const template = "{{ chapters[0].annotations[0].timestamp | date('%Y-%m-%d') }}";
const result = renderNoteTemplate(template, dataWithoutTimestamp);
expect(result).toBe('');
});
it('should handle string date input', () => {
const template = "{{ exportDate | date('%Y-%m-%d') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('2024-01-15');
});
it('should handle escaped percent signs', () => {
const template =
"{{ chapters[0].annotations[0].timestamp | date('100%% complete on %Y-%m-%d') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('100% complete on 2024-01-15');
});
});
describe('Default filter', () => {
it('should return value when present', () => {
const template = "{{ title | default('Unknown') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('The Great Gatsby');
});
it('should return default when value is undefined', () => {
const template = "{{ nonexistent | default('Unknown') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Unknown');
});
it('should return default when value is empty string', () => {
const template = "{{ chapters[0].annotations[1].note | default('No note') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('No note');
});
});
describe('String filters', () => {
it('should convert to uppercase with upper filter', () => {
const template = '{{ title | upper }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('THE GREAT GATSBY');
});
it('should convert to lowercase with lower filter', () => {
const template = '{{ title | lower }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('the great gatsby');
});
it('should capitalize with capitalize filter', () => {
const template = '{{ "hello world" | capitalize }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Hello world');
});
it('should title case with title filter', () => {
const template = '{{ "hello world" | title }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Hello World');
});
it('should trim whitespace with trim filter', () => {
const template = '{{ " spaced " | trim }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('spaced');
});
it('should replace substrings with replace filter', () => {
const template = "{{ title | replace('Great', 'Amazing') }}";
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('The Amazing Gatsby');
});
});
describe('Truncate filter', () => {
it('should truncate long strings', () => {
const template = '{{ chapters[0].annotations[0].text | truncate(20) }}';
const result = renderNoteTemplate(template, sampleData);
expect(result.length).toBeLessThanOrEqual(23); // 20 + '...'
expect(result).toContain('...');
});
it('should not truncate short strings', () => {
const template = '{{ title | truncate(50) }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('The Great Gatsby');
});
it('should truncate at word boundaries by default', () => {
const template = '{{ chapters[0].annotations[0].text | truncate(25) }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).not.toMatch(/\s\.\.\.$/); // Should not end with space before ...
});
});
describe('Length filter', () => {
it('should return string length', () => {
const template = '{{ title | length }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('16');
});
it('should return array length', () => {
const template = '{{ chapters | length }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('2');
});
});
describe('First and last filters', () => {
it('should get first element of array', () => {
const template = '{% set first_chapter = chapters | first %}{{ first_chapter.title }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Chapter 1');
});
it('should get last element of array', () => {
const template = '{% set last_chapter = chapters | last %}{{ last_chapter.title }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Chapter 2');
});
});
describe('Join filter', () => {
it('should join array elements', () => {
// Using a loop with conditional comma separator
const template =
'{% for chapter in chapters %}{{ chapter.title }}{% if not loop.last %}, {% endif %}{% endfor %}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('Chapter 1, Chapter 2');
});
});
describe('Newline to BR filter', () => {
it('should convert newlines to br tags', () => {
const dataWithNewlines: NoteTemplateData = {
...sampleData,
chapters: [
{
title: 'Chapter 1',
annotations: [
{
text: 'Line 1\nLine 2\nLine 3',
note: 'Multiple lines',
},
],
},
],
};
const template = '{{ chapters[0].annotations[0].text | nl2br }}';
const result = renderNoteTemplate(template, dataWithNewlines);
expect(result).toBe('Line 1<br>\nLine 2<br>\nLine 3');
});
});
describe('Chained filters', () => {
it('should support chaining multiple filters', () => {
const template = '{{ title | upper | truncate(10) }}';
const result = renderNoteTemplate(template, sampleData);
// truncate(10) with word boundaries results in "THE GREAT..." (breaks at word boundary)
expect(result).toBe('THE GREAT...');
});
it('should support chaining with killwords option', () => {
const template = '{{ title | upper | truncate(5, true) }}';
const result = renderNoteTemplate(template, sampleData);
// truncate(5, true) with killwords results in "THE G..."
expect(result).toBe('THE G...');
});
it('should chain date and string filters', () => {
const template = '{{ exportDate | upper }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('2024-01-15');
});
});
describe('Complete template rendering', () => {
it('should render a complete export template', () => {
const template = `## {{ title }}
**Author**: {{ author }}
**Exported**: {{ exportDate }}
---
### Highlights & Annotations
{% for chapter in chapters %}
#### {{ chapter.title }}
{% for annotation in chapter.annotations %}
> {{ annotation.text }}
{% if annotation.note %}
**Note:** {{ annotation.note }}
{% endif %}
*Time: {{ annotation.timestamp | date('%Y-%m-%d %H:%M') }}*
{% endfor %}
---
{% endfor %}`;
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('## The Great Gatsby');
expect(result).toContain('**Author**: F. Scott Fitzgerald');
expect(result).toContain('#### Chapter 1');
expect(result).toContain('> In my younger and more vulnerable years');
expect(result).toContain('**Note:** Opening line');
expect(result).toContain('#### Chapter 2');
expect(result).toContain('> The eyes of Doctor T. J. Eckleburg');
});
it('should handle template with no chapters', () => {
const emptyData: NoteTemplateData = {
title: 'Empty Book',
author: 'Unknown',
exportDate: '2024-01-15',
chapters: [],
};
const template = `## {{ title }}
{% for chapter in chapters %}
{{ chapter.title }}
{% endfor %}`;
const result = renderNoteTemplate(template, emptyData);
expect(result).toContain('## Empty Book');
expect(result).not.toContain('Chapter');
});
});
describe('Error handling', () => {
it('should handle invalid template syntax gracefully', () => {
const template = '{{ unclosed';
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('[Template Error:');
});
it('should handle invalid filter', () => {
// Nunjucks throws on unknown filters
const template = '{{ title | nonexistentfilter }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toContain('[Template Error:');
});
});
describe('Special characters and escaping', () => {
it('should not auto-escape HTML characters', () => {
const dataWithHtml: NoteTemplateData = {
...sampleData,
title: '<b>Bold</b> Title',
};
const template = '{{ title }}';
const result = renderNoteTemplate(template, dataWithHtml);
expect(result).toBe('<b>Bold</b> Title');
});
it('should handle markdown special characters', () => {
const dataWithMarkdown: NoteTemplateData = {
...sampleData,
chapters: [
{
title: '# Heading with **bold** and _italic_',
annotations: [{ text: '> Quoted text' }],
},
],
};
const template = '{{ chapters[0].title }}\n{{ chapters[0].annotations[0].text }}';
const result = renderNoteTemplate(template, dataWithMarkdown);
expect(result).toContain('# Heading with **bold** and _italic_');
expect(result).toContain('> Quoted text');
});
it('should handle unicode characters', () => {
const dataWithUnicode: NoteTemplateData = {
...sampleData,
title: '红楼梦',
author: '曹雪芹',
chapters: [
{
title: '第一回',
annotations: [{ text: '满纸荒唐言,一把辛酸泪。' }],
},
],
};
const template = '{{ title }} - {{ author }}: {{ chapters[0].annotations[0].text }}';
const result = renderNoteTemplate(template, dataWithUnicode);
expect(result).toBe('红楼梦 - 曹雪芹: 满纸荒唐言,一把辛酸泪。');
});
it('should handle emoji', () => {
const dataWithEmoji: NoteTemplateData = {
...sampleData,
title: '📚 My Book',
chapters: [
{
title: 'Chapter 😀',
annotations: [{ text: 'Text with emoji 🎉' }],
},
],
};
const template = '{{ title }} - {{ chapters[0].annotations[0].text }}';
const result = renderNoteTemplate(template, dataWithEmoji);
expect(result).toBe('📚 My Book - Text with emoji 🎉');
});
});
describe('Whitespace handling', () => {
it('should trim blocks correctly', () => {
const template = `Start
{% if true %}
Content
{% endif %}
End`;
const result = renderNoteTemplate(template, sampleData);
// With trimBlocks and lstripBlocks enabled, whitespace should be managed
expect(result).toContain('Start');
expect(result).toContain('Content');
expect(result).toContain('End');
});
it('should preserve intentional whitespace in content', () => {
const template = '{{ title }} {{ author }}';
const result = renderNoteTemplate(template, sampleData);
expect(result).toBe('The Great Gatsby F. Scott Fitzgerald');
});
});
});
describe('validateNoteTemplate', () => {
it('should return valid for correct template', () => {
const template = '{{ title }} by {{ author }}';
const result = validateNoteTemplate(template);
expect(result.isValid).toBe(true);
expect(result.error).toBeUndefined();
});
it('should return valid for complex template', () => {
const template = `{% for chapter in chapters %}
{{ chapter.title }}
{% for annotation in chapter.annotations %}
{{ annotation.text }}
{% endfor %}
{% endfor %}`;
const result = validateNoteTemplate(template);
expect(result.isValid).toBe(true);
});
it('should return invalid for unclosed variable', () => {
const template = '{{ unclosed';
const result = validateNoteTemplate(template);
expect(result.isValid).toBe(false);
expect(result.error).toBeDefined();
});
it('should return invalid for unclosed block', () => {
const template = '{% if true %}content';
const result = validateNoteTemplate(template);
expect(result.isValid).toBe(false);
expect(result.error).toBeDefined();
});
it('should return invalid for unclosed for loop', () => {
const template = '{% for item in items %}content';
const result = validateNoteTemplate(template);
expect(result.isValid).toBe(false);
expect(result.error).toBeDefined();
});
it('should return valid for empty template', () => {
const template = '';
const result = validateNoteTemplate(template);
expect(result.isValid).toBe(true);
});
it('should return valid for plain text without template syntax', () => {
const template = 'Just plain text without any template syntax';
const result = validateNoteTemplate(template);
expect(result.isValid).toBe(true);
});
});
@@ -1,13 +1,14 @@
import clsx from 'clsx';
import React, { useState, useMemo, useEffect } from 'react';
import { marked } from 'marked';
import { BookNote, BooknoteGroup, NoteExportConfig } from '@/types/book';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
import Dialog from '@/components/Dialog';
import { useReaderStore } from '@/store/readerStore';
import { BookNote, BooknoteGroup, NoteExportConfig } from '@/types/book';
import { DEFAULT_NOTE_EXPORT_CONFIG } from '@/services/constants';
import { saveViewSettings } from '@/helpers/settings';
import { useEnv } from '@/context/EnvContext';
import { renderNoteTemplate } from '@/utils/note';
import Dialog from '@/components/Dialog';
interface ExportMarkdownDialogProps {
bookKey: string;
@@ -20,135 +21,6 @@ interface ExportMarkdownDialogProps {
onExport: (markdown: string) => void;
}
type TemplateData = {
title: string;
author: string;
exportDate: string;
chapters: {
title: string;
annotations: {
text: string;
note?: string;
timestamp?: number;
}[];
}[];
};
type TemplateVariable = { [key: string]: number | string | TemplateVariable | TemplateVariable[] };
const renderTemplate = (template: string, data: TemplateData): string => {
const applyFilter = (
value: TemplateVariable,
filter: string,
filterArg?: string,
): TemplateVariable | string => {
if (filter === 'date') {
if (typeof value === 'number') {
const date = new Date(value);
if (filterArg) {
const format = filterArg;
return format
.replace('%Y', date.getFullYear().toString())
.replace('%m', String(date.getMonth() + 1).padStart(2, '0'))
.replace('%d', String(date.getDate()).padStart(2, '0'))
.replace('%H', String(date.getHours()).padStart(2, '0'))
.replace('%M', String(date.getMinutes()).padStart(2, '0'))
.replace('%S', String(date.getSeconds()).padStart(2, '0'));
}
return date.toLocaleString();
}
return value;
}
return value;
};
const getValue = (path: string, context: TemplateData) => {
const keys = path.trim().split('.');
let value = context as TemplateVariable;
for (const key of keys) {
value = value?.[key] as TemplateVariable;
}
return value;
};
let result = template;
// Process {% for %} loops - find first occurrence and recurse
const forMatch = result.match(/\{%\s*for\s+(\w+)\s+in\s+(\w+(?:\.\w+)*)\s*%\}/);
if (forMatch) {
const itemName = forMatch[1]!;
const collectionPath = forMatch[2]!;
// Find matching {% endfor %} by counting nesting
let depth = 0;
let endPos = -1;
const startPos = forMatch.index! + forMatch[0].length;
for (let i = startPos; i < result.length; i++) {
const remaining = result.slice(i);
if (remaining.startsWith('{% for ')) {
depth++;
i += 6; // skip past "{% for"
} else if (remaining.startsWith('{% endfor %}')) {
if (depth === 0) {
endPos = i;
break;
}
depth--;
i += 11; // skip past "{% endfor %}"
}
}
if (endPos !== -1) {
const beforeLoop = result.slice(0, forMatch.index);
const loopContent = result.slice(startPos, endPos);
const afterLoop = result.slice(endPos + 13); // 13 = length of "{% endfor %}"
const collection = getValue(collectionPath, data);
if (Array.isArray(collection)) {
const renderedItems = collection
.map((item) => {
const loopData = { ...data, [itemName]: item };
return renderTemplate(loopContent, loopData);
})
.join('');
result = beforeLoop + renderedItems + afterLoop;
return renderTemplate(result, data);
} else {
// Collection not found or not an array, remove the loop
result = beforeLoop + afterLoop;
return renderTemplate(result, data);
}
}
}
// Process {% if condition %}...{% endif %}
result = result.replace(
/\{%\s*if\s+([^%]+?)\s*%\}([\s\S]*?)\{%\s*endif\s*%\}/g,
(_match, condition, content) => {
const value = getValue(condition, data);
return value ? renderTemplate(content, data) : '';
},
);
// Replace {{ variable }}, {{ variable | filter }}, and {{ variable | filter('arg') }}
result = result.replace(
/\{\{\s*([^}|]+?)(?:\s*\|\s*(\w+)(?:\s*\((['"]?)([^)'"]*)\3\))?)?\s*\}\}/g,
(_match, path, filter, _quote, filterArg) => {
const value = getValue(path, data);
if (value === undefined || value === null) return '';
if (filter) {
return applyFilter(value, filter, filterArg) as string;
}
return String(value);
},
);
return result;
};
const ExportMarkdownDialog: React.FC<ExportMarkdownDialogProps> = ({
bookKey,
isOpen,
@@ -167,7 +39,7 @@ const ExportMarkdownDialog: React.FC<ExportMarkdownDialogProps> = ({
const defaultTemplate = `## {{ title }}
**${_('Author')}**: {{ author }}
**${_('Exported from Readest')}**: {{ exportDate }}
**${_('Exported from Readest')}**: {{ exportDate | date('%Y-%m-%d') }}
---
@@ -176,7 +48,19 @@ const ExportMarkdownDialog: React.FC<ExportMarkdownDialogProps> = ({
{% for chapter in chapters %}
#### {{ chapter.title }}
{% for annotation in chapter.annotations %}
> {{ annotation.text }}
{% if annotation.color == 'yellow' %}
- {{ annotation.text }}
{% elif annotation.color == 'red' %}
- ❗ {{ annotation.text }}
{% elif annotation.color == 'green' %}
- ✅ {{ annotation.text }}
{% elif annotation.color == 'blue' %}
- 💡 {{ annotation.text }}
{% elif annotation.color == 'violet' %}
- ✨ {{ annotation.text }}
{% else %}
- {{ annotation.text }}
{% endif %}
{% if annotation.note %}
**${_('Note:')}** {{ annotation.note }}
{% endif %}
@@ -219,7 +103,7 @@ const ExportMarkdownDialog: React.FC<ExportMarkdownDialogProps> = ({
const templateData = {
title: bookTitle,
author: bookAuthor,
exportDate: new Date().toISOString().slice(0, 10),
exportDate: new Date().getTime(),
chapters: sortedGroups.map((group) => ({
title: group.label || _('Untitled'),
annotations: group.booknotes.map((note) => ({
@@ -232,7 +116,7 @@ const ExportMarkdownDialog: React.FC<ExportMarkdownDialogProps> = ({
})),
};
return renderTemplate(exportConfig.customTemplate, templateData);
return renderNoteTemplate(exportConfig.customTemplate, templateData);
}
// Default formatting (non-template mode)
@@ -456,7 +340,17 @@ const ExportMarkdownDialog: React.FC<ExportMarkdownDialogProps> = ({
{exportConfig.useCustomTemplate && (
<>
<div className='space-y-2'>
<label className='text-sm font-medium'>{_('Export Template')}</label>
<div className='flex items-center justify-between'>
<label className='text-sm font-medium'>{_('Export Template')}</label>
<button
onClick={() =>
setExportConfig({ ...exportConfig, customTemplate: defaultTemplate })
}
className='text-sm text-blue-500 hover:underline'
>
{_('Reset Template')}
</button>
</div>
<textarea
value={exportConfig.customTemplate}
onChange={(e) =>
@@ -537,9 +431,17 @@ const ExportMarkdownDialog: React.FC<ExportMarkdownDialogProps> = ({
<code className='bg-base-300 rounded px-1'>annotation.note</code> -{' '}
{_('Annotation note')}
</li>
<li className='ml-8'>
<code className='bg-base-300 rounded px-1'>annotation.style</code> -{' '}
{_('Annotation style')}: underline | highlight | squiggly
</li>
<li className='ml-8'>
<code className='bg-base-300 rounded px-1'>annotation.color</code> -{' '}
{_('Annotation color')}: yellow | red | green | blue | violet
</li>
<li className='ml-8'>
<code className='bg-base-300 rounded px-1'>annotation.timestamp</code> -{' '}
{_('Update time')}
{_('Annotation time')}
</li>
</ul>
</div>
+218
View File
@@ -0,0 +1,218 @@
import nunjucks from 'nunjucks';
export type NoteTemplateData = {
title: string;
author: string;
exportDate: number;
chapters: {
title: string;
annotations: {
text: string;
note?: string;
style?: string;
color?: string;
timestamp?: number;
}[];
}[];
};
// Configure nunjucks environment
const env = new nunjucks.Environment(null, {
autoescape: false, // Don't auto-escape since we're generating markdown
throwOnUndefined: false, // Return empty string for undefined variables
trimBlocks: false, // Trim newlines after block tags
lstripBlocks: true, // Strip leading whitespace before block tags
});
// Add custom 'date' filter for Jinja2 compatibility
// Supports Python strftime-style format strings
env.addFilter('date', (value: number | string | undefined, format?: string) => {
if (value === undefined || value === null) return '';
let date: Date;
if (typeof value === 'number') {
date = new Date(value);
} else if (typeof value === 'string') {
date = new Date(value);
} else {
return value;
}
if (isNaN(date.getTime())) return '';
if (!format) {
return date.toLocaleString();
}
// Convert Python strftime format to actual values
return format
.replace(/%Y/g, date.getFullYear().toString())
.replace(/%m/g, String(date.getMonth() + 1).padStart(2, '0'))
.replace(/%d/g, String(date.getDate()).padStart(2, '0'))
.replace(/%H/g, String(date.getHours()).padStart(2, '0'))
.replace(/%M/g, String(date.getMinutes()).padStart(2, '0'))
.replace(/%S/g, String(date.getSeconds()).padStart(2, '0'))
.replace(/%I/g, String(date.getHours() % 12 || 12).padStart(2, '0'))
.replace(/%p/g, date.getHours() >= 12 ? 'PM' : 'AM')
.replace(/%w/g, String(date.getDay()))
.replace(/%j/g, getDayOfYear(date).toString().padStart(3, '0'))
.replace(/%U/g, getWeekNumber(date).toString().padStart(2, '0'))
.replace(/%W/g, getWeekNumber(date, true).toString().padStart(2, '0'))
.replace(/%a/g, date.toLocaleDateString('en-US', { weekday: 'short' }))
.replace(/%A/g, date.toLocaleDateString('en-US', { weekday: 'long' }))
.replace(/%b/g, date.toLocaleDateString('en-US', { month: 'short' }))
.replace(/%B/g, date.toLocaleDateString('en-US', { month: 'long' }))
.replace(/%%/g, '%');
});
// Helper to get day of year
function getDayOfYear(date: Date): number {
const start = new Date(date.getFullYear(), 0, 0);
const diff = date.getTime() - start.getTime();
const oneDay = 1000 * 60 * 60 * 24;
return Math.floor(diff / oneDay);
}
// Helper to get week number
function getWeekNumber(date: Date, mondayFirst = false): number {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = mondayFirst ? d.getUTCDay() || 7 : d.getUTCDay();
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
}
// Add 'default' filter for providing fallback values
env.addFilter('default', (value: unknown, defaultValue: unknown = '') => {
if (value === undefined || value === null || value === '') {
return defaultValue;
}
return value;
});
// Add 'truncate' filter
env.addFilter('truncate', (value: string, length = 255, killwords = false, end = '...') => {
if (!value || typeof value !== 'string') return '';
if (value.length <= length) return value;
if (killwords) {
return value.slice(0, length) + end;
}
// Find the last word boundary
let truncated = value.slice(0, length);
const lastSpace = truncated.lastIndexOf(' ');
if (lastSpace > 0) {
truncated = truncated.slice(0, lastSpace);
}
return truncated + end;
});
// Add 'trim' filter
env.addFilter('trim', (value: string) => {
if (!value || typeof value !== 'string') return '';
return value.trim();
});
// Add 'replace' filter
env.addFilter('replace', (value: string, search: string, replacement: string) => {
if (!value || typeof value !== 'string') return '';
return value.split(search).join(replacement);
});
// Add 'upper' and 'lower' filters
env.addFilter('upper', (value: string) => {
if (!value || typeof value !== 'string') return '';
return value.toUpperCase();
});
env.addFilter('lower', (value: string) => {
if (!value || typeof value !== 'string') return '';
return value.toLowerCase();
});
// Add 'capitalize' filter
env.addFilter('capitalize', (value: string) => {
if (!value || typeof value !== 'string') return '';
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
});
// Add 'title' filter (capitalize each word)
env.addFilter('title', (value: string) => {
if (!value || typeof value !== 'string') return '';
return value.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase());
});
// Add 'length' filter
env.addFilter('length', (value: string | unknown[]) => {
if (!value) return 0;
return value.length;
});
// Add 'first' and 'last' filters
env.addFilter('first', (value: unknown[]) => {
if (!Array.isArray(value) || value.length === 0) return undefined;
return value[0];
});
env.addFilter('last', (value: unknown[]) => {
if (!Array.isArray(value) || value.length === 0) return undefined;
return value[value.length - 1];
});
// Add 'join' filter
env.addFilter('join', (value: unknown[], separator = '') => {
if (!Array.isArray(value)) return '';
return value.join(separator);
});
// Add 'nl2br' filter (convert newlines to <br>)
env.addFilter('nl2br', (value: string) => {
if (!value || typeof value !== 'string') return '';
return value.replace(/\n/g, '<br>\n');
});
/**
* Renders a Jinja2/Nunjucks template with the given data
* @param template The template string in Jinja2/Nunjucks syntax
* @param data The data to render the template with
* @returns The rendered template string
*/
export function renderNoteTemplate(template: string, data: NoteTemplateData): string {
try {
return env.renderString(template, data);
} catch (error) {
// Return error message in the output for debugging
const errorMessage = error instanceof Error ? error.message : 'Unknown template error';
return `[Template Error: ${errorMessage}]\n\n${template}`;
}
}
/**
* Validates a template string by attempting to compile it
* @param template The template string to validate
* @returns An object with isValid flag and optional error message
*/
export function validateNoteTemplate(template: string): { isValid: boolean; error?: string } {
try {
env.renderString(template, {
title: '',
author: '',
exportDate: '',
chapters: [],
});
return { isValid: true };
} catch (error) {
return {
isValid: false,
error: error instanceof Error ? error.message : 'Unknown template error',
};
}
}
/**
* Get the nunjucks environment instance for advanced use cases
*/
export function getNunjucksEnv(): nunjucks.Environment {
return env;
}
+45
View File
@@ -266,6 +266,9 @@ importers:
next:
specifier: 16.0.10
version: 16.0.10(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
nunjucks:
specifier: ^3.2.4
version: 3.2.4(chokidar@3.6.0)
overlayscrollbars:
specifier: ^2.11.4
version: 2.13.0
@@ -360,6 +363,9 @@ importers:
'@types/node':
specifier: ^22.15.31
version: 22.19.7
'@types/nunjucks':
specifier: ^3.2.6
version: 3.2.6
'@types/react':
specifier: ^19.0.0
version: 19.2.9
@@ -3661,6 +3667,9 @@ packages:
'@types/node@22.19.7':
resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==}
'@types/nunjucks@3.2.6':
resolution: {integrity: sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==}
'@types/phoenix@1.6.7':
resolution: {integrity: sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==}
@@ -3974,6 +3983,9 @@ packages:
resolution: {integrity: sha512-HZKJLFe4eGVgCe9J87PnijY7T1Zn638bEHS+Fm/ygHZozRpefzWcOYfPaP52S8pqk9g4xN3+LzMDl3Lv9dLglA==}
engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'}
a-sync-waterfall@1.0.1:
resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==}
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
@@ -4168,6 +4180,9 @@ packages:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -4429,6 +4444,10 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
commander@5.1.0:
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
engines: {node: '>= 6'}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
@@ -6360,6 +6379,16 @@ packages:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
nunjucks@3.2.4:
resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==}
engines: {node: '>= 6.9.0'}
hasBin: true
peerDependencies:
chokidar: ^3.3.0
peerDependenciesMeta:
chokidar:
optional: true
nwsapi@2.2.23:
resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
@@ -11967,6 +11996,8 @@ snapshots:
dependencies:
undici-types: 6.21.0
'@types/nunjucks@3.2.6': {}
'@types/phoenix@1.6.7': {}
'@types/react-color@3.0.13(@types/react@19.2.9)':
@@ -12301,6 +12332,8 @@ snapshots:
'@zip.js/zip.js@2.8.15': {}
a-sync-waterfall@1.0.1: {}
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
@@ -12516,6 +12549,8 @@ snapshots:
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
asap@2.0.6: {}
assertion-error@2.0.1: {}
assistant-cloud@0.1.13:
@@ -12779,6 +12814,8 @@ snapshots:
commander@4.1.1: {}
commander@5.1.0: {}
commander@7.2.0: {}
commander@8.3.0: {}
@@ -15281,6 +15318,14 @@ snapshots:
dependencies:
path-key: 3.1.1
nunjucks@3.2.4(chokidar@3.6.0):
dependencies:
a-sync-waterfall: 1.0.1
asap: 2.0.6
commander: 5.1.0
optionalDependencies:
chokidar: 3.6.0
nwsapi@2.2.23: {}
object-assign@4.1.1: {}