(.*?)
', ] # Curated descriptions for pages with no extractable lead paragraph. # Keyed by page path (""=home) then folder lang code (en/mk/si). # MK/SI copy is AI-drafted — flag for native-speaker review. DESC_OVERRIDES = { "": { "en": "The first official Macedonian student organisation in Slovenia — support, events, community and a voice for Macedonian students from application to graduation.", "mk": "Првата официјална македонска студентска организација во Словенија — поддршка, настани, заедница и глас за македонските студенти од пријава до дипломирање.", "si": "Prva uradna makedonska študentska organizacija v Sloveniji — podpora, dogodki, skupnost in glas makedonskih študentov od prijave do diplome.", }, "become-a-member": { "en": "Join MSOS and find your people in Slovenia. Meet students, attend events, get support and help create projects across the Macedonian student community.", "mk": "Придружете се на МСОС и најдете ги вашите луѓе во Словенија. Запознајте студенти, посетувајте настани, добијте поддршка и создавајте проекти во македонската студентска заедница.", "si": "Pridružite se MSOS in najdite svoje ljudi v Sloveniji. Spoznajte študente, obiskujte dogodke, prejmite podporo in soustvarjajte projekte makedonske študentske skupnosti.", }, "faq": { "en": "Short answers to the questions Macedonian students ask most about studying and living in Slovenia — applications, residence, work, housing and more.", "mk": "Кратки одговори на прашањата што најчесто ги поставуваат македонските студенти за студирање и живеење во Словенија — пријави, престој, работа, сместување и повеќе.", "si": "Kratki odgovori na vprašanja, ki jih makedonski študenti najpogosteje postavljajo o študiju in življenju v Sloveniji — prijave, prebivanje, delo, nastanitev in več.", }, "gallery": { "en": "A look back at the events, gatherings and moments that make the MSOS community — browse photo albums from Macedonian student life in Slovenia.", "mk": "Поглед наназад на настаните, дружењата и моментите што ја градат заедницата на МСОС — прелистајте фото-албуми од македонскиот студентски живот во Словенија.", "si": "Pogled nazaj na dogodke, druženja in trenutke, ki gradijo skupnost MSOS — prebrskajte foto-albume iz makedonskega študentskega življenja v Sloveniji.", }, "news": { "en": "Everything new at MSOS in one place — events and projects, blog stories, announcements and official updates for Macedonian students in Slovenia.", "mk": "Сè ново кај МСОС на едно место — настани и проекти, блог приказни, соопштенија и официјални новости за македонските студенти во Словенија.", "si": "Vse novo pri MSOS na enem mestu — dogodki in projekti, blog zgodbe, obvestila in uradne novice za makedonske študente v Sloveniji.", }, "student-welcome-guide/my-route": { "en": "Your personal study route to Slovenia — follow the Student Welcome Guide steps for Macedonian students, from choosing a programme to enrolment and residence.", "mk": "Вашиот личен студиски пат до Словенија — следете ги чекорите од Водичот за добредојде за македонските студенти, од избор на програма до упис и престој.", "si": "Vaša osebna študijska pot v Slovenijo — sledite korakom Vodnika za bruce za makedonske študente, od izbire programa do vpisa in prebivanja.", }, } def extract_description(content, lang=None, path=None): if path is not None and path in DESC_OVERRIDES: ov = DESC_OVERRIDES[path].get(lang) if ov: return ov for pat in DESC_PATTERNS: m = re.search(pat, content, re.DOTALL) if m: txt = clean_text(m.group(1)) if len(txt) >= 40: return truncate(txt) return None HERO_PATTERNS = [ r']*>(.*?)
", re.DOTALL) def extract_h1(content): m = H1_RE.search(content) return clean_text(m.group(1)) if m else None def canonical_for(lang, path): return f"{DOMAIN}/{lang}/" + (f"{path}/" if path else "") def article_schema(lang, path, content, canon, desc, ogimg): """Article + BreadcrumbList JSON-LD for 2-segment 'section/slug' pages.""" parts = path.split("/") if len(parts) != 2 or parts[0] not in SECTION_NAMES: return [] section, _ = parts headline = extract_h1(content) or title_text(content).split(" - ")[0] org = {"@type": "Organization", "name": SITE_NAME[lang], "url": f"{DOMAIN}/{lang}/"} article = { "@context": "https://schema.org", "@type": "Article", "headline": headline, "description": desc or "", "image": ogimg, "inLanguage": HREF[lang], "author": org, "publisher": {"@type": "Organization", "name": SITE_NAME[lang], "logo": {"@type": "ImageObject", "url": f"{DOMAIN}/images/1-logo.png"}}, "mainEntityOfPage": canon, } if not article["description"]: del article["description"] dates = ARTICLE_DATES.get(path) if dates: pub = dates.get("published") or dates.get("modified") mod = dates.get("modified") or dates.get("published") else: # Auto-read the publish date from the English page (no manual curation). pub = mod = extract_publish_date(en_content_for(path, lang, content)) if pub: article["datePublished"] = pub article["dateModified"] = mod crumbs = { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [ {"@type": "ListItem", "position": 1, "name": HOME_CRUMB[lang], "item": f"{DOMAIN}/{lang}/"}, {"@type": "ListItem", "position": 2, "name": SECTION_NAMES[section][lang], "item": f"{DOMAIN}/{lang}/{section}/"}, {"@type": "ListItem", "position": 3, "name": headline, "item": canon}, ], } dump = lambda o: ' " return [dump(article), dump(crumbs)] FAQ_BLOCK_RE = re.compile( r'(.*?)
\s*' r'(.*?)
", re.DOTALL) def faq_schema(lang, path, content): """FAQPage JSON-LD, parsed from the inside .faq-a (the self-contained sentence), dropping the trailing
"Read more in the guide" link so the schema answer stands alone —
which is exactly what AI answer engines lift verbatim.
"""
if path != "faq":
return []
qas = []
for m in FAQ_BLOCK_RE.finditer(content):
q = clean_text(m.group(1))
ans_html = m.group(2)
pm = FAQ_ANSWER_P_RE.search(ans_html)
a = clean_text(pm.group(1)) if pm else clean_text(ans_html)
if q and a:
qas.append({
"@type": "Question",
"name": q,
"acceptedAnswer": {"@type": "Answer", "text": a},
})
if not qas:
return []
faq = {
"@context": "https://schema.org",
"@type": "FAQPage",
"inLanguage": HREF[lang],
"mainEntity": qas,
}
return [' "]
def event_schema(lang, path, content, canon, desc, ogimg):
"""Event JSON-LD for /projects/ recap pages (added alongside Article).
These are past events; the schema accurately records what happened and where,
which helps AI answer engines associate MSOS with real events in Slovenia.
Autonomous: any 2-segment projects/