Import source: app/scripts/generate-semantic-pages.mjs

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:23:34 +03:30
parent e61e7f5471
commit 58f4415bf0
+440
View File
@@ -0,0 +1,440 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { TeknavData } from '../teknav-data.js';
import { renderStaticFallback, replaceStaticFallback } from './static-fallback.mjs';
const SITE_URL = (process.env.TEKNAV_SITE_URL || process.env.VITE_SITE_URL || 'https://www.teknav.ir').replace(/\/$/, '');
const SITE_NAME = 'تکناو';
const DEFAULT_IMAGE = `${SITE_URL}/images/og/default.jpg`;
const DEFAULT_KEYWORDS = 'تکناو, فناوری, هوش مصنوعی, علم داده, امنیت سایبری, نرم افزار, سخت افزار, استارتاپ, آینده فناوری, تحلیل تکنولوژی';
const template = readFileSync(resolve('dist/index.html'), 'utf8');
function esc(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function clean(value) {
return String(value ?? '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
}
function truncate(value, max = 155) {
const text = clean(value);
return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text;
}
const org = {
'@type': ['Organization', 'NewsMediaOrganization'],
'@id': `${SITE_URL}/#organization`,
name: 'تکناو',
alternateName: ['Teknav', 'تکنّاو'],
url: SITE_URL,
logo: {
'@type': 'ImageObject',
'@id': `${SITE_URL}/#logo`,
url: `${SITE_URL}/favicon.png`,
width: 512,
height: 512,
},
image: { '@id': `${SITE_URL}/#logo` },
};
const website = {
'@type': 'WebSite',
'@id': `${SITE_URL}/#website`,
url: SITE_URL,
name: 'تکناو',
alternateName: 'Teknav',
inLanguage: 'fa-IR',
publisher: { '@id': `${SITE_URL}/#organization` },
};
const GLOSSARY_SAMEAS_MAP = {
ai: [
'https://www.wikidata.org/wiki/Q11660',
'https://fa.wikipedia.org/wiki/%D9%87%D9%88%D8%B4_%D9%85%D8%B5%D9%86%D9%88%D8%B5%DB%8C'
],
llm: [
'https://www.wikidata.org/wiki/Q115305900',
'https://fa.wikipedia.org/wiki/%D9%85%D8%AF%D9%84_%D8%B2%D8%A8%D8%A7%D9%86%DB%8C_%D8%A8%D8%B2%D8%B1%DA%AF'
],
rag: [
'https://www.wikidata.org/wiki/Q124378129',
'https://en.wikipedia.org/wiki/Retrieval-augmented_generation'
],
'prompt-injection': [
'https://www.wikidata.org/wiki/Q117409023',
'https://en.wikipedia.org/wiki/Prompt_injection'
],
'vector-database': [
'https://www.wikidata.org/wiki/Q119790697',
'https://en.wikipedia.org/wiki/Vector_database'
],
'agentic-ai': [
'https://www.wikidata.org/wiki/Q127415174'
]
};
function buildBreadcrumb(items) {
return {
'@type': 'BreadcrumbList',
itemListElement: items.map((item, idx) => ({
'@type': 'ListItem',
position: idx + 1,
name: item.name,
...(item.path ? { item: `${SITE_URL}${item.path}` } : {}),
})),
};
}
let count = 0;
function writePage(routePath, title, description, keywords, jsonLdGraph) {
const canonical = `${SITE_URL}${routePath}`;
const jsonLd = JSON.stringify({
'@context': 'https://schema.org',
'@graph': [
org,
website,
...jsonLdGraph,
]
});
let html = template;
html = html.replace(/<html lang="[^"]*" dir="[^"]*">/, `<html lang="fa" dir="rtl">`);
html = html.replace(/<title>[^<]*<\/title>/, `<title>${title}</title>`);
html = html.replace(/(<meta name="description" content=")[^"]*(")/, `$1${description}$2`);
html = html.replace(/(<meta name="keywords" content=")[^"]*(")/, `$1${esc(keywords)}$2`);
html = html.replace(/(<meta name="language" content=")[^"]*(")/, `$1fa-IR$2`);
html = html.replace(/(<meta property="og:locale" content=")[^"]*(")/, `$1fa_IR$2`);
html = html.replace(/(<meta property="og:title" content=")[^"]*(")/, `$1${title}$2`);
html = html.replace(/(<meta property="og:description" content=")[^"]*(")/, `$1${description}$2`);
html = html.replace(/(<meta property="og:url" content=")[^"]*(")/, `$1${esc(canonical)}$2`);
html = html.replace(/(<meta property="og:image" content=")[^"]*(")/, `$1${esc(DEFAULT_IMAGE)}$2`);
html = html.replace(/(<meta name="twitter:title" content=")[^"]*(")/, `$1${title}$2`);
html = html.replace(/(<meta name="twitter:description" content=")[^"]*(")/,`$1${description}$2`);
html = html.replace(/(<meta name="twitter:image" content=")[^"]*(")/, `$1${esc(DEFAULT_IMAGE)}$2`);
html = html.replace(/(<link rel="canonical" href=")[^"]*(")/, `$1${esc(canonical)}$2`);
html = html.replace(/<link rel="alternate" hreflang="[^"]+" href="[^"]+" \/>/, `<link rel="alternate" hreflang="fa" href="${esc(canonical)}" />`);
html = html.replace(/<script type="application\/ld\+json">[\s\S]*?<\/script>/,
`<script type="application/ld+json">${jsonLd}</script>`);
html = replaceStaticFallback(html, renderStaticFallback({
eyebrow: 'تکناو',
title,
description,
facts: [
{ label: 'زبان', value: 'فارسی' },
{ label: 'نشانی canonical', value: canonical },
],
links: [
{ href: '/', label: SITE_NAME, text: 'صفحه اصلی' },
{ href: '/articles', label: 'مقاله‌های فناوری', text: 'آرشیو تحلیل‌ها' },
{ href: '/authors', label: 'نویسندگان تکناو', text: 'پروفایل نویسندگان' },
],
}));
// Normalize path format for directory creation
const relativeDir = routePath.startsWith('/') ? routePath.slice(1) : routePath;
const localDir = resolve(`dist/${relativeDir}`);
mkdirSync(localDir, { recursive: true });
writeFileSync(`${localDir}/index.html`, html, 'utf8');
count++;
}
// 1. Generate Categories
for (const category of TeknavData.categories ?? []) {
const name = category.name;
const description = category.description || `مقاله‌ها و تحلیل‌های فارسی تکناو درباره ${name}.`;
const path = `/category/${category.slug}`;
const title = `${name} | مقاله‌ها و تحلیل‌های تکناو`;
const keywords = `${name}, مقاله ${name}, تحلیل ${name}, فناوری, تکناو`;
const jsonLdGraph = [
{
'@type': 'CollectionPage',
name,
description,
url: `${SITE_URL}${path}`,
inLanguage: 'fa-IR',
},
buildBreadcrumb([
{ name: SITE_NAME, path: '/' },
{ name }
]),
];
writePage(path, title, description, keywords, jsonLdGraph);
}
// 2. Generate Tags
for (const tag of TeknavData.tags ?? []) {
const name = tag.name;
const path = `/tag/${encodeURIComponent(name)}`;
const title = `برچسب ${name} | مقاله‌های تکنـاو`;
const description = `همه مقاله‌های تکنـاو درباره ${name}، همراه با تحلیل فارسی، داده و مسیرهای مرتبط برای مطالعه بیشتر.`;
const keywords = `${name}, تکنـاو, مقاله فناوری, تحلیل فناوری`;
const jsonLdGraph = [
{
'@type': 'CollectionPage',
name: `برچسب ${name}`,
url: `${SITE_URL}${path}`,
inLanguage: 'fa-IR',
},
buildBreadcrumb([
{ name: SITE_NAME, path: '/' },
{ name: 'برچسب‌ها', path: '/articles' },
{ name }
]),
];
writePage(path, title, description, keywords, jsonLdGraph);
}
// 3. Generate Glossary
for (const term of TeknavData.glossary ?? []) {
const slug = term.slug;
const path = `/glossary/${slug}`;
const title = `${term.term} چیست؟ | فرهنگ واژگان تکنّاو`;
const description = truncate(term.summary, 155);
const keywords = `${term.term}, ${term.english || ''}, فرهنگ واژگان فناوری, تکنّاو`;
const sameAs = GLOSSARY_SAMEAS_MAP[slug] || [];
const jsonLdGraph = [
{
'@type': 'DefinedTerm',
name: term.term,
alternateName: term.english,
description,
inDefinedTermSet: `${SITE_URL}/glossary`,
url: `${SITE_URL}${path}`,
sameAs,
},
term.faqs?.length ? {
'@type': 'FAQPage',
mainEntity: term.faqs.map(([q, a]) => ({
'@type': 'Question',
name: q,
acceptedAnswer: { '@type': 'Answer', text: a },
})),
} : null,
buildBreadcrumb([
{ name: SITE_NAME, path: '/' },
{ name: 'فرهنگ واژگان', path: '/articles' },
{ name: term.term }
]),
].filter(Boolean);
writePage(path, title, description, keywords, jsonLdGraph);
}
// 4. Generate Topics
for (const topic of TeknavData.topicHubs ?? []) {
const slug = topic.slug;
const path = `/topics/${slug}`;
const name = topic.title;
const description = topic.description || `مقاله‌ها و تحلیل‌های تکناو درباره ${name}.`;
const title = topic.seoTitle || `${name} | خوشه موضوعی تکناو`;
const keywords = (topic.keywords ?? [name, 'تکناو', 'تحلیل فناوری']).join(', ');
const jsonLdGraph = [
{
'@type': 'CollectionPage',
name,
description,
url: `${SITE_URL}${path}`,
inLanguage: 'fa-IR',
isPartOf: { '@id': `${SITE_URL}/#website` },
},
topic.faqs?.length ? {
'@type': 'FAQPage',
mainEntity: topic.faqs.map(([q, a]) => ({
'@type': 'Question',
name: q,
acceptedAnswer: { '@type': 'Answer', text: a },
})),
} : null,
buildBreadcrumb([
{ name: SITE_NAME, path: '/' },
{ name: 'موضوعات', path: '/articles' },
{ name }
]),
].filter(Boolean);
writePage(path, title, description, keywords, jsonLdGraph);
}
// 5. Generate Series Pages
for (const series of TeknavData.articleSeries ?? []) {
const slug = series.slug;
const path = `/series/${slug}`;
const name = series.title;
const description = truncate(series.description || 'سری مقاله‌های تکناو برای دنبال کردن یک موضوع فناوری به‌ترتیب تحریریه.', 155);
const title = `${name} | سری مقاله‌های تکناو`;
const keywords = `${name}, سری مقاله, تکناو, تحلیل فناوری`;
const jsonLdGraph = [
{
'@type': 'CollectionPage',
name,
description,
url: `${SITE_URL}${path}`,
inLanguage: 'fa-IR',
},
buildBreadcrumb([
{ name: SITE_NAME, path: '/' },
{ name: 'سری مقاله‌ها', path: '/articles' },
{ name }
]),
];
writePage(path, title, description, keywords, jsonLdGraph);
}
// 6. Generate Static / Core Pages
const staticPages = [
{
path: '/articles',
title: 'مقاله‌های فناوری | تکناو',
description: 'آرشیو مقاله‌های فارسی تکناو درباره هوش مصنوعی، علم داده، امنیت سایبری، نرم‌افزار، سخت‌افزار و آینده فناوری.',
keywords: DEFAULT_KEYWORDS,
jsonLdGraph: [{ '@type': 'CollectionPage', name: 'مقاله‌های فناوری تکناو', url: `${SITE_URL}/articles`, inLanguage: 'fa-IR' }]
},
{
path: '/authors',
title: 'نویسندگان تکناو | تحلیلگران فناوری',
description: 'آشنایی با نویسندگان و تحلیلگران تکناو در حوزه هوش مصنوعی، امنیت سایبری، نرم‌افزار، سخت‌افزار و استارتاپ.',
keywords: 'نویسندگان تکناو, تحلیلگر فناوری, نویسنده هوش مصنوعی, نویسنده امنیت سایبری',
jsonLdGraph: [{ '@type': 'CollectionPage', name: 'نویسندگان تکناو', url: `${SITE_URL}/authors`, inLanguage: 'fa-IR' }]
},
{
path: '/newsletter',
title: 'آرشیو خبرنامه تکنـاو | تحلیل هفتگی فناوری',
description: 'آرشیو خبرنامه‌های ارسال‌شده تکنـاو درباره هوش مصنوعی، امنیت سایبری، نرم‌افزار، سخت‌افزار، استارتاپ و داده.',
keywords: 'خبرنامه تکنـاو, آرشیو خبرنامه فناوری, تحلیل هفتگی فناوری, هوش مصنوعی, امنیت سایبری',
jsonLdGraph: [
{ '@type': 'CollectionPage', name: 'آرشیو خبرنامه تکنـاو', url: `${SITE_URL}/newsletter`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'خبرنامه' }])
]
},
{
path: '/jobs',
title: 'فرصت‌های شغلی فناوری | تکناو',
description: 'آگهی‌های شغلی منتخب فناوری و امکان ثبت موقعیت شغلی شرکت‌ها در تکناو.',
keywords: 'شغل فناوری, استخدام برنامه نویس, استخدام هوش مصنوعی, تکناو جابز',
jsonLdGraph: [
{ '@type': 'CollectionPage', name: 'فرصت‌های شغلی فناوری | تکناو', url: `${SITE_URL}/jobs`, inLanguage: 'fa-IR', description: 'آگهی‌های شغلی منتخب فناوری و امکان ثبت موقعیت شغلی شرکت‌ها در تکناو.' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'فرصت‌های شغلی فناوری' }])
]
},
{
path: '/courses',
title: 'دوره‌های تخصصی فناوری | تکناو',
description: 'دوره‌های فارسی تکناو برای هوش مصنوعی، امنیت، نرم‌افزار و مهارت‌های فنی محصول‌محور.',
keywords: 'دوره فناوری, آموزش هوش مصنوعی, آموزش امنیت, دوره فارسی برنامه نویسی',
jsonLdGraph: [
{ '@type': 'CollectionPage', name: 'دوره‌های تخصصی فناوری | تکناو', url: `${SITE_URL}/courses`, inLanguage: 'fa-IR', description: 'دوره‌های فارسی تکناو برای هوش مصنوعی، امنیت، نرم‌افزار و مهارت‌های فنی محصول‌محور.' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'دوره‌های تخصصی فناوری' }])
]
},
{
path: '/membership',
title: 'عضویت پریمیوم تکناو | محتوای اختصاصی فناوری',
description: 'عضویت پریمیوم تکناو برای تجربه بدون تبلیغ، محتوای اختصاصی و دسترسی بهتر به دوره‌ها و گزارش‌های تخصصی.',
keywords: 'عضویت تکناو, محتوای پریمیوم فناوری, تحلیل اختصاصی فناوری',
jsonLdGraph: [
{ '@type': 'CollectionPage', name: 'عضویت پریمیوم تکناو | محتوای اختصاصی فناوری', url: `${SITE_URL}/membership`, inLanguage: 'fa-IR', description: 'عضویت پریمیوم تکناو برای تجربه بدون تبلیغ، محتوای اختصاصی و دسترسی بهتر به دوره‌ها و گزارش‌های تخصصی.' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'عضویت پریمیوم تکناو' }])
]
},
{
path: '/about',
title: 'درباره ما | تکناو',
description: 'درباره رسانه فارسی تکناو و ماموریت ما در تحلیل فناوری، هوش مصنوعی و امنیت سایبری.',
keywords: 'درباره تکناو, ماموریت تکناو, رسانه فناوری, تحلیل هوش مصنوعی',
jsonLdGraph: [
{ '@type': 'WebPage', '@id': `${SITE_URL}/about#webpage`, name: 'درباره ما | تکناو', description: 'درباره رسانه فارسی تکناو و ماموریت ما در تحلیل فناوری، هوش مصنوعی و امنیت سایبری.', url: `${SITE_URL}/about`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'درباره ما' }])
]
},
{
path: '/contact',
title: 'تماس با ما | تکناو',
description: 'ارتباط با تیم تحریریه و پشتیبانی تکناو برای نظرات، پیشنهادات و همکاری.',
keywords: 'تماس با تکناو, پشتیبانی تکناو, همکاری با تکناو, تبلیغات تکناو',
jsonLdGraph: [
{ '@type': 'WebPage', '@id': `${SITE_URL}/contact#webpage`, name: 'تماس با ما | تکناو', description: 'ارتباط با تیم تحریریه و پشتیبانی تکناو برای نظرات، پیشنهادات و همکاری.', url: `${SITE_URL}/contact`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'تماس با ما' }])
]
},
{
path: '/terms',
title: 'قوانین و مقررات | تکناو',
description: 'شرایط استفاده و قوانین و مقررات استفاده از محتوا و خدمات رسانه تکناو.',
keywords: 'قوانین تکناو, مقررات تکناو, شرایط استفاده تکناو',
jsonLdGraph: [
{ '@type': 'WebPage', '@id': `${SITE_URL}/terms#webpage`, name: 'قوانین و مقررات | تکناو', description: 'شرایط استفاده و قوانین و مقررات استفاده از محتوا و خدمات رسانه تکناو.', url: `${SITE_URL}/terms`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'قوانین و مقررات' }])
]
},
{
path: '/privacy',
title: 'حریم خصوصی | تکناو',
description: 'سیاست حریم خصوصی و نحوه حفاظت از اطلاعات کاربران در رسانه تکناو.',
keywords: 'حریم خصوصی تکناو, سیاست حریم خصوصی, امنیت اطلاعات کاربران',
jsonLdGraph: [
{ '@type': 'WebPage', '@id': `${SITE_URL}/privacy#webpage`, name: 'حریم خصوصی | تکناو', description: 'سیاست حریم خصوصی و نحوه حفاظت از اطلاعات کاربران در رسانه تکناو.', url: `${SITE_URL}/privacy`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'حریم خصوصی' }])
]
},
{
path: '/tools',
title: 'ابزارهای تخصصی فناوری | تکناو',
description: 'جعبه‌ابزار تخصصی تکناو برای توسعه‌دهندگان، مهندسان داده و تحلیلگران؛ شامل نرمال‌ساز متن و ماشین‌حساب RAG.',
keywords: 'ابزار توسعه دهنده, نرمال ساز متن, ماشین حساب RAG, جعبه ابزار تکناو',
jsonLdGraph: [
{ '@type': 'CollectionPage', name: 'ابزارهای تخصصی فناوری | تکناو', url: `${SITE_URL}/tools`, inLanguage: 'fa-IR', description: 'جعبه‌ابزار تخصصی تکناو برای توسعه‌دهندگان، مهندسان داده و تحلیلگران؛ شامل نرمال‌ساز متن و ماشین‌حساب RAG.' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'ابزارها' }])
]
},
{
path: '/tools/nlp-normalizer',
title: 'نرمال‌ساز متن فارسی (NLP) | ابزارهای تکناو',
description: 'ابزار تخصصی پاکسازی، نیم‌فاصله‌گذاری و پیش‌پردازش متن فارسی برای تسک‌های پردازش زبان طبیعی و مدل‌های زبانی.',
keywords: 'نرمال ساز متن فارسی, نیم فاصله گذاری, پیش پردازش متن فارسی, NLP فارسی',
jsonLdGraph: [
{ '@type': 'WebPage', '@id': `${SITE_URL}/tools/nlp-normalizer#webpage`, name: 'نرمال‌ساز متن فارسی (NLP) | ابزارهای تکناو', description: 'ابزار تخصصی پاکسازی، نیم‌فاصله‌گذاری و پیش‌پردازش متن فارسی برای تسک‌های پردازش زبان طبیعی و مدل‌های زبانی.', url: `${SITE_URL}/tools/nlp-normalizer`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'ابزارها', path: '/tools' }, { name: 'نرمال‌ساز متن فارسی' }])
]
},
{
path: '/tools/rag-calculator',
title: 'ماشین‌حساب ظرفیت و هزینه RAG | ابزارهای تکناو',
description: 'ماشین‌حساب تخصصی محاسبه تعداد توکن، فضای پایگاه‌داده برداری، هزینه‌های استنتاج LLM و پهنای باند معماری RAG.',
keywords: 'هزینه RAG, ماشین حساب RAG, پهنای باند RAG, پایگاه داده برداری',
jsonLdGraph: [
{ '@type': 'WebPage', '@id': `${SITE_URL}/tools/rag-calculator#webpage`, name: 'ماشین‌حساب ظرفیت و هزینه RAG | ابزارهای تکناو', description: 'ماشین‌حساب تخصصی محاسبه تعداد توکن، فضای پایگاه‌داده برداری، هزینه‌های استنتاج LLM و پهنای باند معماری RAG.', url: `${SITE_URL}/tools/rag-calculator`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'ابزارها', path: '/tools' }, { name: 'ماشین‌حساب RAG' }])
]
},
{
path: '/tekrag-persian',
title: 'بنچمارک TekRag | ارزیابی سامانه‌های RAG فارسی',
description: 'درگاه رسمی بنچمارک TekRag؛ اولین فریم‌ورک استاندارد ارزیابی دقت، بازیابی و پاسخ‌دهی سیستم‌های RAG زبان فارسی.',
keywords: 'TekRag benchmark, بنچمارک RAG فارسی, ارزیابی سیستم RAG, هوش مصنوعی فارسی',
jsonLdGraph: [
{ '@type': 'WebPage', '@id': `${SITE_URL}/tekrag-persian#webpage`, name: 'بنچمارک TekRag | ارزیابی سامانه‌های RAG فارسی', description: 'درگاه رسمی بنچمارک TekRag؛ اولین فریم‌ورک استاندارد ارزیابی دقت، بازیابی و پاسخ‌دهی سیستم‌های RAG زبان فارسی.', url: `${SITE_URL}/tekrag-persian`, inLanguage: 'fa-IR' },
buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'بنچمارک TekRag' }])
]
}
];
for (const pg of staticPages) {
writePage(pg.path, pg.title, pg.description, pg.keywords, pg.jsonLdGraph);
}
console.log(`[semantic-pages] generated ${count} static semantic HTML pages`);