i18n: translate CampaignDetailPage
Extract user-facing strings on the campaign detail page into the campaignsDetail.* namespace. Covers the hero (back/edit/delete chips, author attribution, deadline pill, comment action label), the engagement counter row above the comments (repost/quote/like counts with pluralized labels and a bold count wrapper), the comments + donations section header and empty state, the delete-confirm AlertDialog, the donate sidebar (raised/of-goal labels, donation count, recent-donations list, share button, ended state), the story component, and pin/unpin and deletion toasts. Chevron + arrow icons flip with rtl:rotate-180. Uses i18next plural suffixes for the four count-driven labels (reposts/quotes/likes, comments, donations, days-left). This completes Priority 1 of the i18n rollout (Pledges + Communities/Groups + Campaigns verticals — list pages, create forms, detail pages). Native-speaker review still pending for: km, sn, ps, fa.
This commit is contained in:
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "نقطة محفظة مطلوبة.",
|
||||
"errorPublishedInvalid": "فشل التحقق من الحدث المنشور. الرجاء التحديث والمحاولة مرة أخرى."
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | حملات {{appName}}",
|
||||
"seoDescriptionFallback": "ادعم {{title}} على {{appName}}.",
|
||||
"deadlineEndedOn": "انتهى في {{date}}",
|
||||
"deadlineEndsToday": "ينتهي اليوم",
|
||||
"deadlineDaysLeft_one": "بقي {{count}} يوم",
|
||||
"deadlineDaysLeft_other": "بقي {{count}} يومًا",
|
||||
"deadlineEndsOn": "ينتهي في {{date}}",
|
||||
"back": "رجوع",
|
||||
"edit": "تعديل",
|
||||
"delete": "حذف",
|
||||
"deleting": "جارٍ الحذف…",
|
||||
"byAuthor": "بواسطة <0>{{name}}</0>",
|
||||
"commentLabel": "تعليق",
|
||||
"linkCopied": "تم نسخ الرابط إلى الحافظة",
|
||||
"pinnedToast": "تم التثبيت على الحملة",
|
||||
"unpinnedToast": "تم إلغاء التثبيت",
|
||||
"pinFailed": "تعذّر تحديث المثبتات",
|
||||
"deletedToast": "تم حذف الحملة",
|
||||
"deletedToastDesc": "تم نشر طلب حذف. ستزيل المرحّلات المتعاونة الحملة من الخلاصات.",
|
||||
"deleteErrorTitle": "تعذّر حذف الحملة",
|
||||
"campaigner": "صاحب الحملة",
|
||||
"repost_one": "<0>{{count}}</0> إعادة نشر",
|
||||
"repost_other": "<0>{{count}}</0> إعادات نشر",
|
||||
"quote_one": "<0>{{count}}</0> اقتباس",
|
||||
"quote_other": "<0>{{count}}</0> اقتباسات",
|
||||
"like_one": "<0>{{count}}</0> إعجاب",
|
||||
"like_other": "<0>{{count}}</0> إعجابات",
|
||||
"commentsAndDonations": "التعليقات والتبرعات",
|
||||
"commentCount_one": "{{count}} تعليق",
|
||||
"commentCount_other": "{{count}} تعليقات",
|
||||
"noCommentsTitle": "لا توجد تعليقات بعد",
|
||||
"noCommentsHint": "كن أول من يترك رسالة دعم.",
|
||||
"deleteDialogTitle": "حذف هذه الحملة؟",
|
||||
"deleteDialogBody": "هذا ينشر طلب حذف NIP-09. ستزيل المرحّلات المتعاونة الحملة من الخلاصات والروابط المباشرة. تبقى إيصالات التبرعات السابقة على السلسلة بغض النظر. لا يمكن التراجع عن هذا الإجراء — لمواصلة قبول التبرعات، عدّل الحملة بدلًا من ذلك.",
|
||||
"storyHeading": "القصة",
|
||||
"storyEmpty": "لم يكتب المنظم قصة هذه الحملة بعد.",
|
||||
"campaignEnded": "انتهت الحملة",
|
||||
"donate": "تبرّع",
|
||||
"share": "مشاركة",
|
||||
"target": "الهدف: {{amount}}",
|
||||
"raised": "مجموع",
|
||||
"ofGoal": "من هدف {{amount}}",
|
||||
"donationCount_one": "{{count}} تبرع",
|
||||
"donationCount_other": "{{count}} تبرعات",
|
||||
"recentDonations": "التبرعات الأخيرة",
|
||||
"seeAllDonations_one": "عرض {{count}} تبرع",
|
||||
"seeAllDonations_other": "عرض جميع {{count}} تبرع"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "حملات تمويل",
|
||||
|
||||
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "Wallet endpoint is required.",
|
||||
"errorPublishedInvalid": "Published event failed validation. Please refresh and try again."
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | {{appName}} Fundraisers",
|
||||
"seoDescriptionFallback": "Support {{title}} on {{appName}}.",
|
||||
"deadlineEndedOn": "Ended {{date}}",
|
||||
"deadlineEndsToday": "Ends today",
|
||||
"deadlineDaysLeft_one": "{{count}} day left",
|
||||
"deadlineDaysLeft_other": "{{count}} days left",
|
||||
"deadlineEndsOn": "Ends {{date}}",
|
||||
"back": "Back",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"deleting": "Deleting…",
|
||||
"byAuthor": "by <0>{{name}}</0>",
|
||||
"commentLabel": "Comment",
|
||||
"linkCopied": "Link copied to clipboard",
|
||||
"pinnedToast": "Pinned to campaign",
|
||||
"unpinnedToast": "Unpinned from campaign",
|
||||
"pinFailed": "Failed to update campaign pins",
|
||||
"deletedToast": "Campaign deleted",
|
||||
"deletedToastDesc": "A deletion request was published. Well-behaved relays will drop the campaign from feeds.",
|
||||
"deleteErrorTitle": "Could not delete campaign",
|
||||
"campaigner": "Campaigner",
|
||||
"repost_one": "<0>{{count}}</0> Repost",
|
||||
"repost_other": "<0>{{count}}</0> Reposts",
|
||||
"quote_one": "<0>{{count}}</0> Quote",
|
||||
"quote_other": "<0>{{count}}</0> Quotes",
|
||||
"like_one": "<0>{{count}}</0> Like",
|
||||
"like_other": "<0>{{count}}</0> Likes",
|
||||
"commentsAndDonations": "Comments & donations",
|
||||
"commentCount_one": "{{count}} comment",
|
||||
"commentCount_other": "{{count}} comments",
|
||||
"noCommentsTitle": "No comments yet",
|
||||
"noCommentsHint": "Be the first to leave a message of support.",
|
||||
"deleteDialogTitle": "Delete this campaign?",
|
||||
"deleteDialogBody": "This publishes a NIP-09 deletion request. Well-behaved relays will drop the campaign from feeds and direct links. Past donation receipts stay on-chain regardless. This action cannot be undone — to keep accepting donations, edit the campaign instead.",
|
||||
"storyHeading": "The story",
|
||||
"storyEmpty": "The organizer hasn't written a story for this campaign yet.",
|
||||
"campaignEnded": "Campaign ended",
|
||||
"donate": "Donate",
|
||||
"share": "Share",
|
||||
"target": "Target: {{amount}}",
|
||||
"raised": "raised",
|
||||
"ofGoal": "of {{amount}} goal",
|
||||
"donationCount_one": "{{count}} donation",
|
||||
"donationCount_other": "{{count}} donations",
|
||||
"recentDonations": "Recent donations",
|
||||
"seeAllDonations_one": "See all {{count}} donation",
|
||||
"seeAllDonations_other": "See all {{count}} donations"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "Fundraisers",
|
||||
|
||||
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "Se requiere un punto de cartera.",
|
||||
"errorPublishedInvalid": "El evento publicado falló la validación. Recarga e inténtalo de nuevo."
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | Recaudaciones de {{appName}}",
|
||||
"seoDescriptionFallback": "Apoya {{title}} en {{appName}}.",
|
||||
"deadlineEndedOn": "Finalizó el {{date}}",
|
||||
"deadlineEndsToday": "Finaliza hoy",
|
||||
"deadlineDaysLeft_one": "Queda {{count}} día",
|
||||
"deadlineDaysLeft_other": "Quedan {{count}} días",
|
||||
"deadlineEndsOn": "Finaliza el {{date}}",
|
||||
"back": "Atrás",
|
||||
"edit": "Editar",
|
||||
"delete": "Eliminar",
|
||||
"deleting": "Eliminando…",
|
||||
"byAuthor": "por <0>{{name}}</0>",
|
||||
"commentLabel": "Comentar",
|
||||
"linkCopied": "Enlace copiado al portapapeles",
|
||||
"pinnedToast": "Fijado a la campaña",
|
||||
"unpinnedToast": "Quitado de la campaña",
|
||||
"pinFailed": "No se pudieron actualizar los fijados",
|
||||
"deletedToast": "Campaña eliminada",
|
||||
"deletedToastDesc": "Se publicó una solicitud de eliminación. Los relés que se comportan bien quitarán la campaña de los feeds.",
|
||||
"deleteErrorTitle": "No se pudo eliminar la campaña",
|
||||
"campaigner": "Organizador",
|
||||
"repost_one": "<0>{{count}}</0> Reposteo",
|
||||
"repost_other": "<0>{{count}}</0> Reposteos",
|
||||
"quote_one": "<0>{{count}}</0> Cita",
|
||||
"quote_other": "<0>{{count}}</0> Citas",
|
||||
"like_one": "<0>{{count}}</0> Me gusta",
|
||||
"like_other": "<0>{{count}}</0> Me gusta",
|
||||
"commentsAndDonations": "Comentarios y donaciones",
|
||||
"commentCount_one": "{{count}} comentario",
|
||||
"commentCount_other": "{{count}} comentarios",
|
||||
"noCommentsTitle": "Aún no hay comentarios",
|
||||
"noCommentsHint": "Sé la primera persona en dejar un mensaje de apoyo.",
|
||||
"deleteDialogTitle": "¿Eliminar esta campaña?",
|
||||
"deleteDialogBody": "Esto publica una solicitud de eliminación NIP-09. Los relés que se comportan bien quitarán la campaña de los feeds y los enlaces directos. Los recibos de donaciones pasadas quedan en cadena de todos modos. Esta acción no se puede deshacer — para seguir aceptando donaciones, edita la campaña en su lugar.",
|
||||
"storyHeading": "La historia",
|
||||
"storyEmpty": "El organizador todavía no ha escrito la historia para esta campaña.",
|
||||
"campaignEnded": "Campaña finalizada",
|
||||
"donate": "Donar",
|
||||
"share": "Compartir",
|
||||
"target": "Meta: {{amount}}",
|
||||
"raised": "recaudado",
|
||||
"ofGoal": "de la meta de {{amount}}",
|
||||
"donationCount_one": "{{count}} donación",
|
||||
"donationCount_other": "{{count}} donaciones",
|
||||
"recentDonations": "Donaciones recientes",
|
||||
"seeAllDonations_one": "Ver la {{count}} donación",
|
||||
"seeAllDonations_other": "Ver las {{count}} donaciones"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "Recaudaciones",
|
||||
|
||||
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "نقطهٔ کیف پول الزامی است.",
|
||||
"errorPublishedInvalid": "رویداد منتشرشده اعتبارسنجی نشد. لطفاً صفحه را بهروز کن و دوباره تلاش کن."
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | کمپینهای {{appName}}",
|
||||
"seoDescriptionFallback": "از {{title}} در {{appName}} حمایت کن.",
|
||||
"deadlineEndedOn": "در {{date}} پایان یافت",
|
||||
"deadlineEndsToday": "امروز پایان مییابد",
|
||||
"deadlineDaysLeft_one": "{{count}} روز باقی",
|
||||
"deadlineDaysLeft_other": "{{count}} روز باقی",
|
||||
"deadlineEndsOn": "در {{date}} پایان مییابد",
|
||||
"back": "بازگشت",
|
||||
"edit": "ویرایش",
|
||||
"delete": "حذف",
|
||||
"deleting": "در حال حذف…",
|
||||
"byAuthor": "توسط <0>{{name}}</0>",
|
||||
"commentLabel": "نظر",
|
||||
"linkCopied": "پیوند در کلیپبورد کپی شد",
|
||||
"pinnedToast": "به کمپین سنجاق شد",
|
||||
"unpinnedToast": "از کمپین جدا شد",
|
||||
"pinFailed": "بهروزرسانی سنجاقها ناموفق بود",
|
||||
"deletedToast": "کمپین حذف شد",
|
||||
"deletedToastDesc": "درخواست حذف منتشر شد. رلههای همکار کمپین را از فیدها حذف خواهند کرد.",
|
||||
"deleteErrorTitle": "حذف کمپین ممکن نشد",
|
||||
"campaigner": "میزبان کمپین",
|
||||
"repost_one": "<0>{{count}}</0> بازنشر",
|
||||
"repost_other": "<0>{{count}}</0> بازنشر",
|
||||
"quote_one": "<0>{{count}}</0> نقلقول",
|
||||
"quote_other": "<0>{{count}}</0> نقلقول",
|
||||
"like_one": "<0>{{count}}</0> پسند",
|
||||
"like_other": "<0>{{count}}</0> پسند",
|
||||
"commentsAndDonations": "نظرها و کمکها",
|
||||
"commentCount_one": "{{count}} نظر",
|
||||
"commentCount_other": "{{count}} نظر",
|
||||
"noCommentsTitle": "هنوز نظری نیست",
|
||||
"noCommentsHint": "اولین کسی باش که پیام حمایت میگذارد.",
|
||||
"deleteDialogTitle": "این کمپین حذف شود؟",
|
||||
"deleteDialogBody": "این یک درخواست حذف NIP-09 منتشر میکند. رلههای همکار کمپین را از فیدها و پیوندهای مستقیم حذف خواهند کرد. رسیدهای کمکهای گذشته در زنجیره باقی میمانند. این کار قابل بازگشت نیست — برای ادامهٔ دریافت کمک، بهجای حذف کمپین را ویرایش کن.",
|
||||
"storyHeading": "داستان",
|
||||
"storyEmpty": "سازماندهنده هنوز داستانی برای این کمپین ننوشته است.",
|
||||
"campaignEnded": "کمپین پایان یافت",
|
||||
"donate": "کمک کنید",
|
||||
"share": "همرسانی",
|
||||
"target": "هدف: {{amount}}",
|
||||
"raised": "جمعآوریشده",
|
||||
"ofGoal": "از هدف {{amount}}",
|
||||
"donationCount_one": "{{count}} کمک",
|
||||
"donationCount_other": "{{count}} کمک",
|
||||
"recentDonations": "کمکهای اخیر",
|
||||
"seeAllDonations_one": "نمایش همهٔ {{count}} کمک",
|
||||
"seeAllDonations_other": "نمایش همهٔ {{count}} کمک"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "کمپینهای جذب کمک مالی",
|
||||
|
||||
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "ត្រូវការចំណុចកាបូប។",
|
||||
"errorPublishedInvalid": "ព្រឹត្តិការណ៍ដែលបានផ្សព្វផ្សាយបរាជ័យក្នុងការវាយតម្លៃ។ សូមផ្ទុកឡើងវិញ ហើយព្យាយាមម្តងទៀត។"
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | យុទ្ធនាការរបស់ {{appName}}",
|
||||
"seoDescriptionFallback": "គាំទ្រ {{title}} នៅលើ {{appName}}។",
|
||||
"deadlineEndedOn": "បានបញ្ចប់នៅ {{date}}",
|
||||
"deadlineEndsToday": "បញ្ចប់នៅថ្ងៃនេះ",
|
||||
"deadlineDaysLeft_one": "នៅសល់ {{count}} ថ្ងៃ",
|
||||
"deadlineDaysLeft_other": "នៅសល់ {{count}} ថ្ងៃ",
|
||||
"deadlineEndsOn": "បញ្ចប់នៅ {{date}}",
|
||||
"back": "ត្រឡប់",
|
||||
"edit": "កែសម្រួល",
|
||||
"delete": "លុប",
|
||||
"deleting": "កំពុងលុប…",
|
||||
"byAuthor": "ដោយ <0>{{name}}</0>",
|
||||
"commentLabel": "មតិយោបល់",
|
||||
"linkCopied": "បានចម្លងតំណទៅក្តារតម្បៀតខ្ទាស់",
|
||||
"pinnedToast": "បានបោះខ្ទាស់ទៅយុទ្ធនាការ",
|
||||
"unpinnedToast": "បានដកខ្ទាស់ចេញពីយុទ្ធនាការ",
|
||||
"pinFailed": "មិនអាចធ្វើបច្ចុប្បន្នភាពខ្ទាស់យុទ្ធនាការ",
|
||||
"deletedToast": "បានលុបយុទ្ធនាការ",
|
||||
"deletedToastDesc": "សំណើលុបត្រូវបានផ្សព្វផ្សាយ។ Relay ដែលអនុលោមនឹងលុបយុទ្ធនាការចេញពី feed។",
|
||||
"deleteErrorTitle": "មិនអាចលុបយុទ្ធនាការ",
|
||||
"campaigner": "អ្នករៀបចំ",
|
||||
"repost_one": "<0>{{count}}</0> Repost",
|
||||
"repost_other": "<0>{{count}}</0> Reposts",
|
||||
"quote_one": "<0>{{count}}</0> ការដកស្រង់",
|
||||
"quote_other": "<0>{{count}}</0> ការដកស្រង់",
|
||||
"like_one": "<0>{{count}}</0> ចូលចិត្ត",
|
||||
"like_other": "<0>{{count}}</0> ចូលចិត្ត",
|
||||
"commentsAndDonations": "មតិយោបល់ និងការបរិច្ចាគ",
|
||||
"commentCount_one": "{{count}} មតិយោបល់",
|
||||
"commentCount_other": "{{count}} មតិយោបល់",
|
||||
"noCommentsTitle": "មិនទាន់មានមតិយោបល់",
|
||||
"noCommentsHint": "ក្លាយជាមនុស្សដំបូងផ្ញើសារគាំទ្រ។",
|
||||
"deleteDialogTitle": "លុបយុទ្ធនាការនេះមែនទេ?",
|
||||
"deleteDialogBody": "នេះផ្សព្វផ្សាយសំណើលុប NIP-09។ Relay ដែលអនុលោមនឹងលុបយុទ្ធនាការចេញពី feed និងតំណផ្ទាល់។ បង្កាន់ដៃនៃការបរិច្ចាគកន្លងមកនៅតែស្ថិតលើខ្សែសង្វាក់។ សកម្មភាពនេះមិនអាចត្រឡប់វិញបានទេ — ដើម្បីបន្តទទួលការបរិច្ចាគ កែសម្រួលយុទ្ធនាការជំនួស។",
|
||||
"storyHeading": "រឿង",
|
||||
"storyEmpty": "អ្នករៀបចំមិនទាន់សរសេររឿងសម្រាប់យុទ្ធនាការនេះទេ។",
|
||||
"campaignEnded": "យុទ្ធនាការបានបញ្ចប់",
|
||||
"donate": "បរិច្ចាគ",
|
||||
"share": "ចែករំលែក",
|
||||
"target": "គោលដៅ៖ {{amount}}",
|
||||
"raised": "បានរៃអង្គាស",
|
||||
"ofGoal": "នៃគោលដៅ {{amount}}",
|
||||
"donationCount_one": "{{count}} ការបរិច្ចាគ",
|
||||
"donationCount_other": "{{count}} ការបរិច្ចាគ",
|
||||
"recentDonations": "ការបរិច្ចាគថ្មីៗ",
|
||||
"seeAllDonations_one": "មើលការបរិច្ចាគ {{count}} ទាំងអស់",
|
||||
"seeAllDonations_other": "មើលការបរិច្ចាគ {{count}} ទាំងអស់"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "យុទ្ធនាការប្រមូលមូលនិធិ",
|
||||
|
||||
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "د پاکټ نقطه اړینه ده.",
|
||||
"errorPublishedInvalid": "خپره شوې پیښه اعتبار نه ولاره. مهرباني وکړئ پاڼه تازه کړئ او بیا هڅه وکړئ."
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | د {{appName}} کمپاینونه",
|
||||
"seoDescriptionFallback": "په {{appName}} کې د {{title}} ملاتړ وکړئ.",
|
||||
"deadlineEndedOn": "په {{date}} پای ته ورسیده",
|
||||
"deadlineEndsToday": "نن پای ته رسي",
|
||||
"deadlineDaysLeft_one": "{{count}} ورځ پاتې",
|
||||
"deadlineDaysLeft_other": "{{count}} ورځې پاتې",
|
||||
"deadlineEndsOn": "په {{date}} پای ته رسي",
|
||||
"back": "شاته",
|
||||
"edit": "سمول",
|
||||
"delete": "ړنګول",
|
||||
"deleting": "ړنګېږي…",
|
||||
"byAuthor": "د <0>{{name}}</0> له لوري",
|
||||
"commentLabel": "تبصره",
|
||||
"linkCopied": "لینک کلپبورډ ته کاپي شو",
|
||||
"pinnedToast": "کمپاین ته ونښلول شو",
|
||||
"unpinnedToast": "له کمپاین جلا شو",
|
||||
"pinFailed": "د نښلونو تازه کول پاتې راغلل",
|
||||
"deletedToast": "کمپاین ړنګ شو",
|
||||
"deletedToastDesc": "د ړنګولو غوښتنه خپره شوه. ښه چلند کوونکي ریلې به کمپاین له فیدونو څخه لرې کړي.",
|
||||
"deleteErrorTitle": "کمپاین نه ړنګېدلای شي",
|
||||
"campaigner": "کمپاینوال",
|
||||
"repost_one": "<0>{{count}}</0> بیا-خپرول",
|
||||
"repost_other": "<0>{{count}}</0> بیا-خپرونې",
|
||||
"quote_one": "<0>{{count}}</0> نقل",
|
||||
"quote_other": "<0>{{count}}</0> نقلونه",
|
||||
"like_one": "<0>{{count}}</0> خوښ",
|
||||
"like_other": "<0>{{count}}</0> خوښونه",
|
||||
"commentsAndDonations": "تبصرې او مرستې",
|
||||
"commentCount_one": "{{count}} تبصره",
|
||||
"commentCount_other": "{{count}} تبصرې",
|
||||
"noCommentsTitle": "تر اوسه تبصرې نشته",
|
||||
"noCommentsHint": "د ملاتړ د پیغام لومړنی شه.",
|
||||
"deleteDialogTitle": "دا کمپاین ړنګ کړئ؟",
|
||||
"deleteDialogBody": "دا د NIP-09 د ړنګولو غوښتنه خپروي. ښه چلند کوونکي ریلې به کمپاین له فیدونو او مستقیمو لینکونو څخه لرې کړي. د تېرو مرستو رسیدونه پر چین پاتې کیږي. دا کار بیرته نه راګرځول کیږي — د مرستو د منلو لپاره، کمپاین سم کړئ.",
|
||||
"storyHeading": "کیسه",
|
||||
"storyEmpty": "تنظیم کوونکی تر اوسه د دې کمپاین لپاره کیسه نه ده لیکلې.",
|
||||
"campaignEnded": "کمپاین پای ته ورسید",
|
||||
"donate": "مرسته",
|
||||
"share": "شریکول",
|
||||
"target": "هدف: {{amount}}",
|
||||
"raised": "راټولشوي",
|
||||
"ofGoal": "د {{amount}} هدف څخه",
|
||||
"donationCount_one": "{{count}} مرسته",
|
||||
"donationCount_other": "{{count}} مرستې",
|
||||
"recentDonations": "وروستۍ مرستې",
|
||||
"seeAllDonations_one": "ټولې {{count}} مرستې وګورئ",
|
||||
"seeAllDonations_other": "ټولې {{count}} مرستې وګورئ"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "د مرستو راټولولو کمپاینونه",
|
||||
|
||||
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "Chinangwa chechikwama chinodikanwa.",
|
||||
"errorPublishedInvalid": "Chiitiko chakaburitswa chatadza chenjedzo. Tapota refresh moedza zvakare."
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | Macampaign e{{appName}}",
|
||||
"seoDescriptionFallback": "Tsigira {{title}} pa{{appName}}.",
|
||||
"deadlineEndedOn": "Zvakapera pa{{date}}",
|
||||
"deadlineEndsToday": "Zvinopera nhasi",
|
||||
"deadlineDaysLeft_one": "{{count}} zuva rasara",
|
||||
"deadlineDaysLeft_other": "Mazuva {{count}} asara",
|
||||
"deadlineEndsOn": "Zvinopera pa{{date}}",
|
||||
"back": "Dzokera",
|
||||
"edit": "Gadzirisa",
|
||||
"delete": "Bvisa",
|
||||
"deleting": "Kubvisa…",
|
||||
"byAuthor": "na <0>{{name}}</0>",
|
||||
"commentLabel": "Pindura",
|
||||
"linkCopied": "Rink yakopirwa kuclipboard",
|
||||
"pinnedToast": "Yakanamatidzwa kucampaign",
|
||||
"unpinnedToast": "Yabviswa pacampaign",
|
||||
"pinFailed": "Yatadza kuchinja zvakanamatidzwa",
|
||||
"deletedToast": "Campaign yabviswa",
|
||||
"deletedToastDesc": "Chikumbiro chekubvisa chakaburitswa. Marelay anozvibata zvakanaka achabvisa campaign mufeeds.",
|
||||
"deleteErrorTitle": "Hatina kukwanisa kubvisa campaign",
|
||||
"campaigner": "Murongi",
|
||||
"repost_one": "<0>{{count}}</0> Repost",
|
||||
"repost_other": "<0>{{count}}</0> maRepost",
|
||||
"quote_one": "<0>{{count}}</0> Quote",
|
||||
"quote_other": "<0>{{count}}</0> maQuote",
|
||||
"like_one": "<0>{{count}}</0> Like",
|
||||
"like_other": "<0>{{count}}</0> maLike",
|
||||
"commentsAndDonations": "Mhinduro nezvipo",
|
||||
"commentCount_one": "{{count}} mhinduro",
|
||||
"commentCount_other": "{{count}} mhinduro",
|
||||
"noCommentsTitle": "Hapana mhinduro parizvino",
|
||||
"noCommentsHint": "Iva wekutanga kusiya shoko rerutsigiro.",
|
||||
"deleteDialogTitle": "Bvisa campaign iyi?",
|
||||
"deleteDialogBody": "Izvi zvinoburitsa chikumbiro chekubvisa cheNIP-09. Marelay anozvibata zvakanaka achabvisa campaign mufeeds nezvirink zvakananga. Rezvi dzezvipo dzapfuura dzinoramba dzakachengetwa pachain. Izvi hazvigoni kudzosererwa — kuti urambe uchigamuchira zvipo, gadzirisa campaign panzvimbo yokuti uibvise.",
|
||||
"storyHeading": "Nyaya",
|
||||
"storyEmpty": "Murongi haasati anyora nyaya yecampaign iyi.",
|
||||
"campaignEnded": "Campaign yapera",
|
||||
"donate": "Ipa",
|
||||
"share": "Govera",
|
||||
"target": "Chinangwa: {{amount}}",
|
||||
"raised": "yakaunganidzwa",
|
||||
"ofGoal": "ye{{amount}} chinangwa",
|
||||
"donationCount_one": "{{count}} chipo",
|
||||
"donationCount_other": "{{count}} zvipo",
|
||||
"recentDonations": "Zvipo zvichangoitika",
|
||||
"seeAllDonations_one": "Ona zvipo zvese {{count}}",
|
||||
"seeAllDonations_other": "Ona zvipo zvese {{count}}"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "Mishandirapamwe yekuunganidza mari",
|
||||
|
||||
@@ -354,6 +354,55 @@
|
||||
"errorWalletRequiredFallback": "需要钱包端点。",
|
||||
"errorPublishedInvalid": "已发布的事件未通过验证。请刷新并重试。"
|
||||
},
|
||||
"campaignsDetail": {
|
||||
"seoTitle": "{{title}} | {{appName}} 募款",
|
||||
"seoDescriptionFallback": "在 {{appName}} 上支持 {{title}}。",
|
||||
"deadlineEndedOn": "{{date}} 已结束",
|
||||
"deadlineEndsToday": "今天截止",
|
||||
"deadlineDaysLeft_one": "还剩 {{count}} 天",
|
||||
"deadlineDaysLeft_other": "还剩 {{count}} 天",
|
||||
"deadlineEndsOn": "{{date}} 截止",
|
||||
"back": "返回",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"deleting": "删除中……",
|
||||
"byAuthor": "由 <0>{{name}}</0>",
|
||||
"commentLabel": "评论",
|
||||
"linkCopied": "已复制链接到剪贴板",
|
||||
"pinnedToast": "已置顶到活动",
|
||||
"unpinnedToast": "已取消置顶",
|
||||
"pinFailed": "更新置顶失败",
|
||||
"deletedToast": "活动已删除",
|
||||
"deletedToastDesc": "删除请求已发布。行为良好的中继会将活动从信息流中移除。",
|
||||
"deleteErrorTitle": "无法删除活动",
|
||||
"campaigner": "发起人",
|
||||
"repost_one": "<0>{{count}}</0> 转发",
|
||||
"repost_other": "<0>{{count}}</0> 转发",
|
||||
"quote_one": "<0>{{count}}</0> 引用",
|
||||
"quote_other": "<0>{{count}}</0> 引用",
|
||||
"like_one": "<0>{{count}}</0> 点赞",
|
||||
"like_other": "<0>{{count}}</0> 点赞",
|
||||
"commentsAndDonations": "评论与捐赠",
|
||||
"commentCount_one": "{{count}} 条评论",
|
||||
"commentCount_other": "{{count}} 条评论",
|
||||
"noCommentsTitle": "暂无评论",
|
||||
"noCommentsHint": "成为第一个留下支持留言的人。",
|
||||
"deleteDialogTitle": "删除此活动?",
|
||||
"deleteDialogBody": "这将发布一个 NIP-09 删除请求。行为良好的中继会将活动从信息流和直接链接中移除。过往的捐赠收据无论如何都会保留在链上。此操作无法撤销 — 若要继续接受捐赠,请改为编辑活动。",
|
||||
"storyHeading": "故事",
|
||||
"storyEmpty": "组织者尚未为此活动撰写故事。",
|
||||
"campaignEnded": "活动已结束",
|
||||
"donate": "捐赠",
|
||||
"share": "分享",
|
||||
"target": "目标:{{amount}}",
|
||||
"raised": "已募",
|
||||
"ofGoal": "目标 {{amount}}",
|
||||
"donationCount_one": "{{count}} 笔捐赠",
|
||||
"donationCount_other": "{{count}} 笔捐赠",
|
||||
"recentDonations": "最近捐赠",
|
||||
"seeAllDonations_one": "查看全部 {{count}} 笔捐赠",
|
||||
"seeAllDonations_other": "查看全部 {{count}} 笔捐赠"
|
||||
},
|
||||
"campaigns": {
|
||||
"home": {
|
||||
"seoTitle": "众筹活动",
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useMemo, useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
import {
|
||||
CalendarClock,
|
||||
@@ -41,6 +43,7 @@ import { NoteMoreMenu } from '@/components/NoteMoreMenu';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ThreadedReplyList, type ReplyNode } from '@/components/ThreadedReplyList';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
import { useBtcPrice } from '@/hooks/useBtcPrice';
|
||||
import { useCampaign } from '@/hooks/useCampaign';
|
||||
import { useCampaignDonations } from '@/hooks/useCampaignDonations';
|
||||
@@ -80,16 +83,16 @@ function formatSatsFull(sats: number, btcPrice: number | undefined): string {
|
||||
return `${sats.toLocaleString()} sats`;
|
||||
}
|
||||
|
||||
function formatDeadline(unixSeconds: number): { label: string; isPast: boolean } {
|
||||
function formatDeadline(unixSeconds: number, t: TFunction): { label: string; isPast: boolean } {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const diff = unixSeconds - now;
|
||||
if (diff <= 0) {
|
||||
return { label: `Ended ${new Date(unixSeconds * 1000).toLocaleDateString()}`, isPast: true };
|
||||
return { label: t('campaignsDetail.deadlineEndedOn', { date: new Date(unixSeconds * 1000).toLocaleDateString() }), isPast: true };
|
||||
}
|
||||
const days = Math.ceil(diff / 86_400);
|
||||
if (days <= 1) return { label: 'Ends today', isPast: false };
|
||||
if (days < 60) return { label: `${days} days left`, isPast: false };
|
||||
return { label: `Ends ${new Date(unixSeconds * 1000).toLocaleDateString()}`, isPast: false };
|
||||
if (days <= 1) return { label: t('campaignsDetail.deadlineEndsToday'), isPast: false };
|
||||
if (days < 60) return { label: t('campaignsDetail.deadlineDaysLeft', { count: days }), isPast: false };
|
||||
return { label: t('campaignsDetail.deadlineEndsOn', { date: new Date(unixSeconds * 1000).toLocaleDateString() }), isPast: false };
|
||||
}
|
||||
|
||||
function collectReplyEvents(nodes: ReplyNode[], out = new Map<string, NostrEvent>()): Map<string, NostrEvent> {
|
||||
@@ -119,6 +122,8 @@ export function CampaignDetailPage({ pubkey, identifier, relays }: CampaignDetai
|
||||
}
|
||||
|
||||
function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
const { t } = useTranslation();
|
||||
const { config } = useAppContext();
|
||||
const { user } = useCurrentUser();
|
||||
const { data: btcPrice } = useBtcPrice();
|
||||
const author = useAuthor(campaign.pubkey);
|
||||
@@ -241,7 +246,7 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
creatorMetadata?.display_name || creatorMetadata?.name || genUserName(campaign.pubkey);
|
||||
const creatorProfileUrl = useProfileUrl(campaign.pubkey, creatorMetadata);
|
||||
|
||||
const deadline = campaign.deadline ? formatDeadline(campaign.deadline) : null;
|
||||
const deadline = campaign.deadline ? formatDeadline(campaign.deadline, t) : null;
|
||||
const countryLabel = getCampaignCountryLabel(campaign);
|
||||
const raisedSats = stats?.totalSats ?? 0;
|
||||
|
||||
@@ -256,8 +261,8 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
);
|
||||
|
||||
useSeoMeta({
|
||||
title: `${campaign.title} | Agora Fundraisers`,
|
||||
description: campaign.summary || `Support ${campaign.title} on Agora.`,
|
||||
title: t('campaignsDetail.seoTitle', { title: campaign.title, appName: config.appName }),
|
||||
description: campaign.summary || t('campaignsDetail.seoDescriptionFallback', { title: campaign.title, appName: config.appName }),
|
||||
ogImage: cover,
|
||||
});
|
||||
|
||||
@@ -269,7 +274,7 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
await nav.share({ title: campaign.title, text: campaign.summary, url });
|
||||
} else if (nav?.clipboard) {
|
||||
await nav.clipboard.writeText(url);
|
||||
toast({ title: 'Link copied to clipboard' });
|
||||
toast({ title: t('campaignsDetail.linkCopied') });
|
||||
}
|
||||
} catch {
|
||||
// User likely cancelled the share sheet; nothing to do.
|
||||
@@ -287,9 +292,8 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Campaign deleted',
|
||||
description:
|
||||
'A deletion request was published. Well-behaved relays will drop the campaign from feeds.',
|
||||
title: t('campaignsDetail.deletedToast'),
|
||||
description: t('campaignsDetail.deletedToastDesc'),
|
||||
});
|
||||
setDeleteConfirmOpen(false);
|
||||
void queryClient.invalidateQueries({ queryKey: ['campaign', campaign.pubkey, campaign.identifier] });
|
||||
@@ -312,7 +316,7 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
onError: (error: unknown) => {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
toast({
|
||||
title: 'Could not delete campaign',
|
||||
title: t('campaignsDetail.deleteErrorTitle'),
|
||||
description: msg,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -416,10 +420,12 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
onClick={() => openInteractions('reposts')}
|
||||
className="hover:underline transition-colors"
|
||||
>
|
||||
<span className="font-bold text-foreground">
|
||||
{formatNumber(engagementStats.reposts)}
|
||||
</span>{' '}
|
||||
Repost{engagementStats.reposts !== 1 ? 's' : ''}
|
||||
<Trans
|
||||
i18nKey="campaignsDetail.repost"
|
||||
count={engagementStats.reposts}
|
||||
values={{ count: formatNumber(engagementStats.reposts) }}
|
||||
components={{ 0: <span className="font-bold text-foreground" /> }}
|
||||
/>
|
||||
</button>
|
||||
) : null}
|
||||
{engagementStats?.quotes ? (
|
||||
@@ -427,10 +433,12 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
onClick={() => openInteractions('quotes')}
|
||||
className="hover:underline transition-colors"
|
||||
>
|
||||
<span className="font-bold text-foreground">
|
||||
{formatNumber(engagementStats.quotes)}
|
||||
</span>{' '}
|
||||
Quote{engagementStats.quotes !== 1 ? 's' : ''}
|
||||
<Trans
|
||||
i18nKey="campaignsDetail.quote"
|
||||
count={engagementStats.quotes}
|
||||
values={{ count: formatNumber(engagementStats.quotes) }}
|
||||
components={{ 0: <span className="font-bold text-foreground" /> }}
|
||||
/>
|
||||
</button>
|
||||
) : null}
|
||||
{engagementStats?.reactions ? (
|
||||
@@ -438,10 +446,12 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
onClick={() => openInteractions('reactions')}
|
||||
className="hover:underline transition-colors"
|
||||
>
|
||||
<span className="font-bold text-foreground">
|
||||
{formatNumber(engagementStats.reactions)}
|
||||
</span>{' '}
|
||||
Like{engagementStats.reactions !== 1 ? 's' : ''}
|
||||
<Trans
|
||||
i18nKey="campaignsDetail.like"
|
||||
count={engagementStats.reactions}
|
||||
values={{ count: formatNumber(engagementStats.reactions) }}
|
||||
components={{ 0: <span className="font-bold text-foreground" /> }}
|
||||
/>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -450,12 +460,11 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
<div className="mt-6">
|
||||
<div className="flex items-baseline justify-between gap-3 mb-3 px-1">
|
||||
<h2 className="text-lg font-semibold tracking-tight">
|
||||
Comments & donations
|
||||
{t('campaignsDetail.commentsAndDonations')}
|
||||
</h2>
|
||||
{engagementStats?.replies ? (
|
||||
<span className="text-sm text-muted-foreground tabular-nums">
|
||||
{formatNumber(engagementStats.replies)}{' '}
|
||||
{engagementStats.replies === 1 ? 'comment' : 'comments'}
|
||||
{t('campaignsDetail.commentCount', { count: engagementStats.replies })}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -492,10 +501,10 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
className="block w-full rounded-2xl border border-dashed border-border/80 bg-card/50 px-6 py-10 text-center hover:bg-card hover:border-primary/40 transition-colors"
|
||||
>
|
||||
<p className="text-base font-medium text-foreground">
|
||||
No comments yet
|
||||
{t('campaignsDetail.noCommentsTitle')}
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Be the first to leave a message of support.
|
||||
{t('campaignsDetail.noCommentsHint')}
|
||||
</p>
|
||||
</button>
|
||||
)}
|
||||
@@ -539,17 +548,13 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete this campaign?</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t('campaignsDetail.deleteDialogTitle')}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This publishes a NIP-09 deletion request. Well-behaved relays
|
||||
will drop the campaign from feeds and direct links. Past
|
||||
donation receipts stay on-chain regardless. This action cannot
|
||||
be undone — to keep accepting donations, edit the campaign
|
||||
instead.
|
||||
{t('campaignsDetail.deleteDialogBody')}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={deleteMutation.isPending}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogCancel disabled={deleteMutation.isPending}>{t('common.cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -558,7 +563,7 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
disabled={deleteMutation.isPending}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{deleteMutation.isPending ? 'Deleting…' : 'Delete'}
|
||||
{deleteMutation.isPending ? t('campaignsDetail.deleting') : t('campaignsDetail.delete')}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@@ -570,10 +575,10 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
const wasPinned = isPinned(event.id);
|
||||
togglePin.mutate(event.id, {
|
||||
onSuccess: () => {
|
||||
toast({ title: wasPinned ? 'Unpinned from campaign' : 'Pinned to campaign' });
|
||||
toast({ title: wasPinned ? t('campaignsDetail.unpinnedToast') : t('campaignsDetail.pinnedToast') });
|
||||
},
|
||||
onError: () => {
|
||||
toast({ title: 'Failed to update campaign pins', variant: 'destructive' });
|
||||
toast({ title: t('campaignsDetail.pinFailed'), variant: 'destructive' });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -592,6 +597,7 @@ function CampaignPinHeader({
|
||||
pinPending: boolean;
|
||||
onTogglePin: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<PinnedCommentHeader
|
||||
isPinned={isPinned}
|
||||
@@ -602,7 +608,7 @@ function CampaignPinHeader({
|
||||
{isCampaignAuthor && (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-primary/10 px-2 py-0.5 font-medium text-primary">
|
||||
<ShieldCheck className="size-3" />
|
||||
Campaigner
|
||||
{t('campaignsDetail.campaigner')}
|
||||
</span>
|
||||
)}
|
||||
</PinnedCommentHeader>
|
||||
@@ -650,6 +656,7 @@ function CampaignHero({
|
||||
onReply,
|
||||
onMore,
|
||||
}: CampaignHeroProps) {
|
||||
const { t } = useTranslation();
|
||||
const initials = creatorName.slice(0, 2).toUpperCase();
|
||||
|
||||
return (
|
||||
@@ -696,10 +703,10 @@ function CampaignHero({
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="inline-flex items-center gap-1.5 h-10 pl-2 pr-3.5 rounded-full bg-black/30 text-white backdrop-blur-md hover:bg-black/45 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 motion-safe:transition-colors"
|
||||
aria-label="Go back"
|
||||
aria-label={t('common.goBack')}
|
||||
>
|
||||
<ChevronLeft className="size-5" />
|
||||
<span className="text-sm font-medium hidden sm:inline">Back</span>
|
||||
<ChevronLeft className="size-5 rtl:rotate-180" />
|
||||
<span className="text-sm font-medium hidden sm:inline">{t('campaignsDetail.back')}</span>
|
||||
</button>
|
||||
|
||||
{isCreator && (
|
||||
@@ -711,7 +718,7 @@ function CampaignHero({
|
||||
>
|
||||
<Link to={`/campaigns/new?edit=${encodeURIComponent(naddr)}`}>
|
||||
<Pencil className="size-4 sm:mr-2" />
|
||||
<span className="hidden sm:inline">Edit</span>
|
||||
<span className="hidden sm:inline">{t('campaignsDetail.edit')}</span>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
@@ -722,7 +729,7 @@ function CampaignHero({
|
||||
className="h-10 rounded-full bg-black/30 text-white backdrop-blur-md shadow-none hover:bg-destructive/70 focus-visible:ring-white/80"
|
||||
>
|
||||
<Trash2 className="size-4 sm:mr-2" />
|
||||
<span className="hidden sm:inline">Delete</span>
|
||||
<span className="hidden sm:inline">{t('campaignsDetail.delete')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -759,10 +766,11 @@ function CampaignHero({
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="[text-shadow:0_1px_3px_rgba(0,0,0,0.7)]">
|
||||
by{' '}
|
||||
<span className="font-semibold underline-offset-4 group-hover:underline">
|
||||
{creatorName}
|
||||
</span>
|
||||
<Trans
|
||||
i18nKey="campaignsDetail.byAuthor"
|
||||
values={{ name: creatorName }}
|
||||
components={{ 0: <span className="font-semibold underline-offset-4 group-hover:underline" /> }}
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
@@ -791,7 +799,7 @@ function CampaignHero({
|
||||
<div className="mt-4 pt-3 border-t border-white/15 [&_button]:!text-white/90 [&_button:hover]:!text-white [&_button:hover]:!bg-white/15 [&_button]:transition-colors [text-shadow:none]">
|
||||
<PostActionBar
|
||||
event={campaign.event}
|
||||
replyLabel="Comment"
|
||||
replyLabel={t('campaignsDetail.commentLabel')}
|
||||
hideZap
|
||||
showShareInSidebar
|
||||
onReply={onReply}
|
||||
@@ -815,13 +823,14 @@ function CampaignStory({
|
||||
storyEvent: NostrEvent;
|
||||
hasContent: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<DetailStory
|
||||
event={storyEvent}
|
||||
hasContent={hasContent}
|
||||
heading="The story"
|
||||
heading={t('campaignsDetail.storyHeading')}
|
||||
headingId="campaign-story-heading"
|
||||
emptyText="The organizer hasn't written a story for this campaign yet."
|
||||
emptyText={t('campaignsDetail.storyEmpty')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -853,8 +862,9 @@ function DonateColumn({
|
||||
onShare,
|
||||
onSeeAll,
|
||||
}: DonateColumnProps) {
|
||||
const { t } = useTranslation();
|
||||
const ended = !!deadline?.isPast;
|
||||
const endedLabel = ended ? 'Campaign ended' : null;
|
||||
const endedLabel = ended ? t('campaignsDetail.campaignEnded') : null;
|
||||
const isSilentPayment = !campaign.wallets.onchain;
|
||||
|
||||
return (
|
||||
@@ -871,7 +881,7 @@ function DonateColumn({
|
||||
{isSilentPayment ? (
|
||||
campaign.goalUsd && campaign.goalUsd > 0 ? (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Target: {formatUsdGoal(campaign.goalUsd)}
|
||||
{t('campaignsDetail.target', { amount: formatUsdGoal(campaign.goalUsd) })}
|
||||
</div>
|
||||
) : null
|
||||
) : statsLoading ? (
|
||||
@@ -882,24 +892,22 @@ function DonateColumn({
|
||||
<div className="text-2xl font-bold tracking-tight">
|
||||
{formatSatsFull(raisedSats, btcPrice)}
|
||||
<span className="ml-1.5 text-sm font-normal text-muted-foreground">
|
||||
raised
|
||||
{t('campaignsDetail.raised')}
|
||||
</span>
|
||||
</div>
|
||||
{campaign.goalUsd ? (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
of {formatUsdGoal(campaign.goalUsd)} goal
|
||||
{t('campaignsDetail.ofGoal', { amount: formatUsdGoal(campaign.goalUsd) })}
|
||||
{donations.length > 0 && (
|
||||
<>
|
||||
{' · '}
|
||||
{formatNumber(donations.length)}{' '}
|
||||
{donations.length === 1 ? 'donation' : 'donations'}
|
||||
{t('campaignsDetail.donationCount', { count: donations.length })}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : donations.length > 0 ? (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatNumber(donations.length)}{' '}
|
||||
{donations.length === 1 ? 'donation' : 'donations'}
|
||||
{t('campaignsDetail.donationCount', { count: donations.length })}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -920,11 +928,11 @@ function DonateColumn({
|
||||
<div className="space-y-2">
|
||||
<Button size="lg" className="w-full" disabled>
|
||||
<HandHeart className="size-5 mr-2" />
|
||||
{endedLabel ?? 'Donate'}
|
||||
{endedLabel ?? t('campaignsDetail.donate')}
|
||||
</Button>
|
||||
<Button variant="outline" size="lg" className="w-full" onClick={onShare}>
|
||||
<Share2 className="size-4 mr-2" />
|
||||
Share
|
||||
{t('campaignsDetail.share')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -935,7 +943,7 @@ function DonateColumn({
|
||||
<CampaignWalletDonatePanel wallets={campaign.wallets} />
|
||||
<Button variant="outline" size="lg" className="w-full" onClick={onShare}>
|
||||
<Share2 className="size-4 mr-2" />
|
||||
Share
|
||||
{t('campaignsDetail.share')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -945,7 +953,7 @@ function DonateColumn({
|
||||
{!isSilentPayment && donations.length > 0 && (
|
||||
<div className="space-y-2 border-t border-border/60 pt-4">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Recent donations
|
||||
{t('campaignsDetail.recentDonations')}
|
||||
</div>
|
||||
<DonorPreviewList donations={donations} btcPrice={btcPrice} />
|
||||
<button
|
||||
@@ -953,8 +961,7 @@ function DonateColumn({
|
||||
onClick={onSeeAll}
|
||||
className="w-full text-sm font-medium text-primary hover:underline motion-safe:transition-colors text-center pt-1"
|
||||
>
|
||||
See all {donations.length}{' '}
|
||||
{donations.length === 1 ? 'donation' : 'donations'}
|
||||
{t('campaignsDetail.seeAllDonations', { count: donations.length })}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user