371 lines
20 KiB
TypeScript
371 lines
20 KiB
TypeScript
/* ============================================================================
|
||
ROUTE COPY - Events Calendar / Applications and Opportunities
|
||
----------------------------------------------------------------------------
|
||
The message catalogue in ~/i18n holds UI chrome that every page shares
|
||
(actions, filters, form labels, statuses). Page prose belongs to the page,
|
||
and lives here.
|
||
|
||
Every string below is taken from the approved draft,
|
||
docs/SHARPER_Website_Draft_Section_EN_AR.docx - section 4 "Events Calendar"
|
||
and section 8 "Applications and Opportunities" - in both languages. Where
|
||
the draft marks a value "TBD" (network eligibility criteria, the response
|
||
time in weeks, the project email, activity figures) NOTHING is invented: the
|
||
sentence either omits it or the value comes from the CMS.
|
||
|
||
Parity is enforced by the type: `RouteCopy` is derived from the English
|
||
object, so a key added on one side and forgotten on the other is a compile
|
||
error rather than a blank paragraph in production.
|
||
========================================================================= */
|
||
|
||
import type { Locale } from "~/i18n";
|
||
|
||
const EN = {
|
||
events: {
|
||
/* The kicker above an event title. "other" is intentionally empty: an
|
||
event the CMS did not classify simply gets no kicker. */
|
||
eventTypes: {
|
||
"suq-dialogue": "SUQ Dialogue",
|
||
training: "Training",
|
||
"law-session": "Law Session",
|
||
"network-meeting": "Network Meeting",
|
||
launch: "Launch",
|
||
other: "",
|
||
},
|
||
/** Draft 4, EN headline. */
|
||
heading: "Upcoming Events",
|
||
/** Draft 4, EN standfirst. */
|
||
intro:
|
||
"Stay informed about upcoming dialogue sessions, training opportunities, and networking events across Libya. Use the calendar to find registration details, thematic focus, and event materials.",
|
||
metaDescription:
|
||
"Dialogues, trainings and Civil Society Law Sessions across Libya. Register for a SUQ dialogue, join a training, or track what’s coming next.",
|
||
listLabel: "Events",
|
||
statusFilterLabel: "Status",
|
||
emptyHint:
|
||
"Upcoming events will be announced soon. Dialogues, trainings, and sessions will be published here as soon as they are scheduled.",
|
||
unavailable:
|
||
"The events calendar cannot be loaded right now. Please try again in a moment.",
|
||
formatHybrid: "In person and online",
|
||
},
|
||
eventDetail: {
|
||
backToList: "Back to the events calendar",
|
||
aboutHeading: "About this event",
|
||
detailsHeading: "Event details",
|
||
registrationDeadlineLabel: "Registration deadline",
|
||
registerHeading: "Register for this event",
|
||
waitlistHeading: "Join the waitlist",
|
||
formIntro:
|
||
"We use these details only to manage your place and to send joining information.",
|
||
registrationClosed: "Registration for this event is closed.",
|
||
/** `event.registrationStatus === "not-required"`. */
|
||
registrationNotRequired: "No registration required",
|
||
registrationNotRequiredNote:
|
||
"This event is open to all - no registration is needed.",
|
||
registrationFull:
|
||
"This event is fully booked. Join the waitlist and we will contact you if a place becomes available.",
|
||
eventCompleted: "This event has already taken place.",
|
||
joinOnlineLabel: "Joining link",
|
||
addToCalendarHint:
|
||
"The .ics file is compatible with Calendar on Apple devices and " +
|
||
"other calendar apps.",
|
||
timeZoneNote: "All times are shown in Libya time (UTC+2).",
|
||
/**
|
||
* `event-registration.consentGdpr` is REQUIRED in the CMS, so the reader
|
||
* has to be asked in plain words rather than opted in silently.
|
||
*/
|
||
consentLabel:
|
||
"I agree that SHARP-ER may store the details above to manage my registration for this event.",
|
||
consentHint:
|
||
"We keep them only for as long as the event needs, and never share them with anyone outside the project. You can ask us to delete them at any time.",
|
||
/** Draft 4, EN confirmation screen, reused for the waitlist variant. */
|
||
waitlistConfirmationTitle: "You’re on the waitlist.",
|
||
waitlistConfirmationBody:
|
||
"Thank you — {event} on {date} is fully booked, so we have added you to the waitlist. We will email you if a place becomes available.",
|
||
},
|
||
opportunities: {
|
||
/** Draft 8, EN headline. */
|
||
heading: "Get Involved",
|
||
/** Draft 8, EN standfirst. */
|
||
intro:
|
||
"SHARP-ER connects people and organisations with opportunities to learn, participate, collaborate, and lead. Whether you’re an individual or a civil society organisation, explore current calls for applications, training programmes, and partnership opportunities below.",
|
||
metaDescription:
|
||
"Calls for expression of interest, SHARP network membership and upcoming sessions — how civil society organisations and individuals take part in SHARP-ER.",
|
||
pathwaysHeading: "Ways to take part",
|
||
/* The draft has no call list; this single line keeps the open calls
|
||
reachable in-site now that the listing band is gone. */
|
||
currentCallsLabel: "Open calls:",
|
||
pathwayCallsTitle: "Apply for a Call for Expression of Interest",
|
||
pathwayCallsBody:
|
||
"Open calls for CSO sub-projects and Policy-to-Rights (P2R) initiatives.",
|
||
pathwayNetworkTitle: "Join the SHARP Network",
|
||
pathwayNetworkBody:
|
||
"Become part of the civil society network behind SHARP-ER: regional meetings, peer learning, and joint events.",
|
||
pathwaySessionsTitle: "Register for a SUQ Dialogue or Training",
|
||
pathwaySessionsBody:
|
||
"Take part in an upcoming Search, Understand, Question dialogue or a dialogue-facilitation training.",
|
||
},
|
||
/* -------------------------------------------------------------------------
|
||
THE APPLICATION FORM - the redesign draft's #apply band
|
||
----------------------------------------------------------------------------
|
||
THREE OF THE DRAFT'S SENTENCES ARE NOT REPRODUCED, on the client's own
|
||
instruction and under CLAUDE.md rule 5:
|
||
|
||
* "PDF or DOCX, up to 10 MB" - not the rule this project runs. The CMS
|
||
ceiling is UPLOAD_MAX_FILE_SIZE (50 MB by default) over the media types
|
||
listed in apps/cms/config/plugins.ts, and in any case nothing is
|
||
uploaded here (see `documentsNote`), so no limit is quoted at all.
|
||
* "an automatic acknowledgment with your reference number" - there is no
|
||
reference-number field anywhere in the CMS, so no number is promised.
|
||
The acknowledgement itself IS sent: the CMS mails it on submission
|
||
and stamps `application.acknowledgementSentAt` when it went out.
|
||
* "follows up within four weeks" - that response time appears in no
|
||
approved document; §8 of the draft marks it "[X weeks] - TBD". The
|
||
follow-up is stated without a period attached, exactly as the per-call
|
||
acknowledgement already does.
|
||
---------------------------------------------------------------------- */
|
||
apply: {
|
||
/** The draft's "Step {n} of 3" kicker over the form. */
|
||
stepOf: "Step {current} of {total}",
|
||
/** Accessible name of the progress list. */
|
||
stepsLabel: "Application progress",
|
||
stepEligibility: "Eligibility",
|
||
stepApplication: "Application",
|
||
stepReview: "Review",
|
||
/** Announced on the step the reader is on. */
|
||
stepCurrent: "current step",
|
||
stepDone: "done",
|
||
/** The standfirst beside the form. */
|
||
standfirst:
|
||
"Submissions are acknowledged automatically by email. The consortium reviews complete applications and follows up by email.",
|
||
formLabel: "Application form",
|
||
fieldsetLegend: "Your details",
|
||
routeLabel: "What are you applying for?",
|
||
/* The short names the draft gives the three routes - the long ones head
|
||
the pathway cards above and are too long for a select. */
|
||
routeCalls: "Call for Expression of Interest",
|
||
routeNetwork: "SHARP Network membership",
|
||
routeSessions: "SUQ dialogue or training",
|
||
organisationLabel: "Organisation name",
|
||
organisationPlaceholder: "Registered legal name",
|
||
contactLabel: "Contact person",
|
||
contactPlaceholder: "Full name",
|
||
emailLabel: "Email",
|
||
emailPlaceholder: "name@organisation.ly",
|
||
regionLabel: "Region of operation",
|
||
regionPlaceholder: "City or municipality",
|
||
yearsLabel: "Years of operating history",
|
||
yearsPlaceholder: "e.g. 4",
|
||
summaryLabel: "Summary of your proposal",
|
||
summaryHint: "Briefly describe your organisation and why you are applying.",
|
||
documentsTitle: "Supporting documents",
|
||
/**
|
||
* The draft draws a file input here. There is nowhere for the file to go:
|
||
* `attachments` is deliberately off the public whitelist in
|
||
* apps/cms/src/api/application/middlewares/sanitize-submission.ts, and the
|
||
* Public role cannot upload. A control that accepted a document and then
|
||
* dropped it would be worse than none, so the panel says plainly what the
|
||
* form does with documents - and quotes no size limit, because it accepts
|
||
* no files at all.
|
||
*/
|
||
documentsNote:
|
||
"Documents are not uploaded through this form. Describe any supporting material in the summary above.",
|
||
/** `application.acknowledgementSentAt` - sent, not just modelled. */
|
||
acknowledgement: "Your submission is acknowledged automatically by email.",
|
||
submit: "Submit application",
|
||
errorSummaryTitle: "Please check the form",
|
||
receivedTitle: "Application received",
|
||
receivedBody:
|
||
"Thank you for applying to {route}. An acknowledgement is on its way to the email address you gave us, and our team will review your application and follow up by email.",
|
||
submitAnother: "Submit another",
|
||
},
|
||
/** `opportunity.opportunityType`, verbatim, with the draft's own wording. */
|
||
opportunityTypes: {
|
||
"call-eoi": "Call for Expression of Interest",
|
||
"network-membership": "Network membership",
|
||
training: "Training",
|
||
grant: "Grant",
|
||
other: "Opportunity",
|
||
},
|
||
opportunityDetail: {
|
||
backToList: "Back to applications and opportunities",
|
||
aboutHeading: "About this opportunity",
|
||
detailsHeading: "Call details",
|
||
howToApplyHeading: "How to apply",
|
||
attachmentsHeading: "Documents",
|
||
applyHeading: "Apply",
|
||
statusLabel: "Status",
|
||
typeLabel: "Type",
|
||
opensLabel: "Opens",
|
||
deadlineLabel: "Deadline",
|
||
formIntro:
|
||
"Tell us briefly why you are applying. Our team reviews every application.",
|
||
messageHint:
|
||
"Briefly describe your organisation and why you are applying.",
|
||
closedNote: "This call is closed. It stays published for reference.",
|
||
upcomingNote: "This call opens on {date}.",
|
||
externalApplication:
|
||
"Applications for this call are handled on an external site.",
|
||
/**
|
||
* Draft 8 marks the response time "[X weeks] - TBD" and the CMS has no
|
||
* field for it, so no number is ever named.
|
||
*/
|
||
applicationReceivedBody:
|
||
"Thank you for your application. Our team will review it and follow up by email.",
|
||
},
|
||
form: {
|
||
/** Honeypot label - never seen by a human, read by a bot. */
|
||
honeypotLabel: "Leave this field empty",
|
||
submissionFailed:
|
||
"Your details could not be submitted. Please try again in a moment.",
|
||
},
|
||
};
|
||
|
||
export type RouteCopy = typeof EN;
|
||
|
||
const AR: RouteCopy = {
|
||
events: {
|
||
eventTypes: {
|
||
"suq-dialogue": "حوار سوق",
|
||
training: "تدريب",
|
||
"law-session": "جلسة قانونية",
|
||
"network-meeting": "اجتماع الشبكة",
|
||
launch: "إطلاق",
|
||
other: "",
|
||
},
|
||
heading: "الفعاليات القادمة",
|
||
intro:
|
||
"ابقَ على اطلاع بجلسات الحوار القادمة، وفرص التدريب، وفعاليات التشبيك في جميع أنحاء ليبيا. استخدم التقويم لمعرفة تفاصيل التسجيل، ومحاور الفعاليات، والمواد المرفقة بها.",
|
||
metaDescription:
|
||
"حوارات وتدريبات وجلسات قانون المجتمع المدني في مختلف أنحاء ليبيا. سجّل في حوار SUQ، أو انضم إلى تدريب، أو تابع ما هو قادم.",
|
||
listLabel: "الفعاليات",
|
||
statusFilterLabel: "الحالة",
|
||
emptyHint: "ستُنشر هنا الحوارات والتدريبات والجلسات فور تحديد مواعيدها.",
|
||
unavailable:
|
||
"تعذّر تحميل تقويم الفعاليات في الوقت الحالي. يرجى المحاولة مرة أخرى بعد قليل.",
|
||
formatHybrid: "حضوري وعبر الإنترنت",
|
||
},
|
||
eventDetail: {
|
||
backToList: "العودة إلى تقويم الفعاليات",
|
||
aboutHeading: "عن الفعالية",
|
||
detailsHeading: "تفاصيل الفعالية",
|
||
registrationDeadlineLabel: "الموعد النهائي للتسجيل",
|
||
registerHeading: "سجّل في هذه الفعالية",
|
||
waitlistHeading: "انضم إلى قائمة الانتظار",
|
||
formIntro:
|
||
"نستخدم هذه البيانات فقط لإدارة مكانك وإرسال تفاصيل الانضمام.",
|
||
registrationClosed: "أُغلق التسجيل في هذه الفعالية.",
|
||
registrationNotRequired: "لا يلزم التسجيل",
|
||
registrationNotRequiredNote:
|
||
"هذه الفعالية مفتوحة للجميع، ولا حاجة إلى التسجيل.",
|
||
registrationFull:
|
||
"اكتمل العدد في هذه الفعالية. انضم إلى قائمة الانتظار وسنتواصل معك في حال توفّر مكان.",
|
||
eventCompleted: "انتهت هذه الفعالية.",
|
||
joinOnlineLabel: "رابط الانضمام",
|
||
addToCalendarHint:
|
||
"ملف .ics متوافق مع تطبيق التقويم على أجهزة Apple ومع تطبيقات " +
|
||
"التقويم الأخرى.",
|
||
timeZoneNote: "جميع الأوقات معروضة بتوقيت ليبيا (UTC+2).",
|
||
consentLabel:
|
||
"أوافق على أن يحتفظ مشروع SHARP-ER بالبيانات أعلاه لإدارة تسجيلي في هذه الفعالية.",
|
||
consentHint:
|
||
"نحتفظ بها فقط للمدة التي تتطلبها الفعالية، ولا نشاركها مع أي جهة خارج المشروع. ويمكنك أن تطلب حذفها في أي وقت.",
|
||
waitlistConfirmationTitle: "تمت إضافتك إلى قائمة الانتظار.",
|
||
waitlistConfirmationBody:
|
||
"شكرًا لك — اكتمل العدد في {event} بتاريخ {date}، لذلك أضفناك إلى قائمة الانتظار. سنراسلك عبر البريد الإلكتروني في حال توفّر مكان.",
|
||
},
|
||
opportunities: {
|
||
heading: "شارك معنا",
|
||
intro:
|
||
"يربط برنامج شارب-إي آر (SHARP-ER) الأفراد والمنظمات بفرص التعلم والمشاركة والتعاون والقيادة. سواء كنتم أفراداً أو منظمات مجتمع مدني، استكشفوا دعوات التقديم الحالية، والبرامج التدريبية، وفرص الشراكة أدناه.",
|
||
metaDescription:
|
||
"دعوات للتعبير عن الاهتمام، وعضوية شبكة SHARP، وجلسات قادمة — كيف تشارك منظمات المجتمع المدني والأفراد في SHARP-ER.",
|
||
pathwaysHeading: "طرق المشاركة",
|
||
currentCallsLabel: "الدعوات المفتوحة:",
|
||
pathwayCallsTitle: "التقديم على دعوة للتعبير عن الاهتمام",
|
||
pathwayCallsBody:
|
||
"دعوات مفتوحة للمشاريع الفرعية لمنظمات المجتمع المدني ومبادرات «من السياسة إلى الحقوق» (P2R).",
|
||
pathwayNetworkTitle: "انضم إلى شبكة SHARP",
|
||
pathwayNetworkBody:
|
||
"كن جزءًا من شبكة المجتمع المدني وراء SHARP-ER: اجتماعات إقليمية، وتعلّم بين الأقران، وفعاليات مشتركة.",
|
||
pathwaySessionsTitle: "سجّل في حوار SUQ أو تدريب",
|
||
pathwaySessionsBody:
|
||
"شارك في حوار قادم من نوع «ابحث، افهم، اسأل» أو في تدريب على تيسير الحوار.",
|
||
},
|
||
apply: {
|
||
stepOf: "الخطوة {current} من {total}",
|
||
stepsLabel: "مراحل تقديم الطلب",
|
||
stepEligibility: "الأهلية",
|
||
stepApplication: "الطلب",
|
||
stepReview: "المراجعة",
|
||
stepCurrent: "الخطوة الحالية",
|
||
stepDone: "مكتملة",
|
||
standfirst:
|
||
"يصلك إشعار تلقائي باستلام طلبك عبر البريد الإلكتروني. ويراجع الائتلاف الطلبات المكتملة ويتواصل معك عبر البريد الإلكتروني.",
|
||
formLabel: "نموذج التقديم",
|
||
fieldsetLegend: "بياناتك",
|
||
routeLabel: "ما الذي تقدّم عليه؟",
|
||
routeCalls: "دعوة للتعبير عن الاهتمام",
|
||
routeNetwork: "عضوية شبكة SHARP",
|
||
routeSessions: "حوار SUQ أو تدريب",
|
||
organisationLabel: "اسم المنظمة",
|
||
organisationPlaceholder: "الاسم القانوني المسجَّل",
|
||
contactLabel: "الشخص المسؤول",
|
||
contactPlaceholder: "الاسم الكامل",
|
||
emailLabel: "البريد الإلكتروني",
|
||
emailPlaceholder: "name@organisation.ly",
|
||
regionLabel: "نطاق العمل",
|
||
regionPlaceholder: "المدينة أو البلدية",
|
||
yearsLabel: "عدد سنوات العمل",
|
||
yearsPlaceholder: "مثال: 4",
|
||
summaryLabel: "ملخّص المقترح",
|
||
summaryHint: "صف باختصار منظمتك وسبب تقديمك.",
|
||
documentsTitle: "المستندات الداعمة",
|
||
documentsNote:
|
||
"لا تُرفَق المستندات عبر هذا النموذج. صف أي مواد داعمة في الملخّص أعلاه.",
|
||
acknowledgement: "يصلك إشعار تلقائي باستلام طلبك عبر البريد الإلكتروني.",
|
||
submit: "إرسال الطلب",
|
||
errorSummaryTitle: "يرجى مراجعة النموذج",
|
||
receivedTitle: "تم استلام الطلب",
|
||
receivedBody:
|
||
"شكرًا لتقديمك على {route}. سيصلك إشعار بالاستلام على البريد الإلكتروني الذي زوّدتنا به، وسيراجع فريقنا طلبك ويتواصل معك عبر البريد الإلكتروني.",
|
||
submitAnother: "تقديم طلب آخر",
|
||
},
|
||
opportunityTypes: {
|
||
"call-eoi": "دعوة للتعبير عن الاهتمام",
|
||
"network-membership": "عضوية الشبكة",
|
||
training: "تدريب",
|
||
grant: "منحة",
|
||
other: "فرصة",
|
||
},
|
||
opportunityDetail: {
|
||
backToList: "العودة إلى طلبات وفرص المشاركة",
|
||
aboutHeading: "عن هذه الفرصة",
|
||
detailsHeading: "تفاصيل الدعوة",
|
||
howToApplyHeading: "كيفية التقديم",
|
||
attachmentsHeading: "المستندات",
|
||
applyHeading: "قدّم طلبك",
|
||
statusLabel: "الحالة",
|
||
typeLabel: "النوع",
|
||
opensLabel: "تاريخ الفتح",
|
||
deadlineLabel: "الموعد النهائي",
|
||
formIntro: "أخبرنا باختصار عن سبب تقديمك. يراجع فريقنا كل طلب.",
|
||
messageHint: "صف باختصار منظمتك وسبب تقديمك.",
|
||
closedNote: "أُغلقت هذه الدعوة، وتبقى منشورة للاطّلاع.",
|
||
upcomingNote: "تُفتح هذه الدعوة بتاريخ {date}.",
|
||
externalApplication: "يتم التقديم على هذه الدعوة عبر موقع خارجي.",
|
||
applicationReceivedBody:
|
||
"شكرًا لتقديم طلبك. سيراجعه فريقنا ويتواصل معك عبر البريد الإلكتروني.",
|
||
},
|
||
form: {
|
||
honeypotLabel: "اترك هذا الحقل فارغًا",
|
||
submissionFailed:
|
||
"تعذّر إرسال بياناتك. يرجى المحاولة مرة أخرى بعد قليل.",
|
||
},
|
||
};
|
||
|
||
const COPY: Readonly<Record<Locale, RouteCopy>> = { en: EN, ar: AR };
|
||
|
||
/** The route copy for a locale. Falls back to English, never to "". */
|
||
export function getRouteCopy(locale: Locale): RouteCopy {
|
||
return COPY[locale] ?? EN;
|
||
}
|