Add multilingual SEO support in routes-seo.js</message>

<message>
- Introduced a new SEO_LOCALES array to support multiple languages for SEO content.
- Implemented a function to build multilingual SEO content based on available locale files.
- Enhanced the generateHTML function to include multilingual sections for the home route, improving accessibility for diverse audiences.

These changes enhance the site's SEO capabilities by providing localized content for better search engine indexing and user engagement.
This commit is contained in:
Torsten Schulz (local)
2026-04-07 15:37:49 +02:00
parent 27f928d8a4
commit 43a5e595d7

View File

@@ -4,6 +4,17 @@ import { loadFeedback } from './feedback-store.js';
const SITE_URL = 'https://www.ypchat.net'; const SITE_URL = 'https://www.ypchat.net';
const DEFAULT_IMAGE = `${SITE_URL}/static/favicon.png`; const DEFAULT_IMAGE = `${SITE_URL}/static/favicon.png`;
const SEO_LOCALES = [
{ code: 'de', label: 'Deutsch' },
{ code: 'en', label: 'English' },
{ code: 'fr', label: 'Francais' },
{ code: 'es', label: 'Espanol' },
{ code: 'it', label: 'Italiano' },
{ code: 'ja', label: 'Japanese' },
{ code: 'zh', label: 'Chinese' },
{ code: 'th', label: 'Thai' },
{ code: 'tl', label: 'Tagalog' }
];
const seoData = { const seoData = {
'/': { '/': {
@@ -212,6 +223,45 @@ function upsertJsonLd(html, schema) {
return html.replace('</head>', ` ${tag}\n</head>`); return html.replace('</head>', ` ${tag}\n</head>`);
} }
function sanitizeLocalizedHtml(input = '') {
return String(input)
.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '')
.trim();
}
function buildMultilingualSeoContent(__dirname) {
const localesDir = join(__dirname, '../client/src/i18n/locales');
const blocks = [];
for (const locale of SEO_LOCALES) {
const filePath = join(localesDir, `${locale.code}.json`);
if (!existsSync(filePath)) continue;
try {
const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
const welcome = sanitizeLocalizedHtml(parsed.welcome || '');
const intro = sanitizeLocalizedHtml(parsed.introduction || '');
if (!welcome && !intro) continue;
blocks.push(`<section lang="${escapeHtml(locale.code)}" style="margin-bottom:20px;">
<h2 style="font:600 22px/1.2 sans-serif;color:#18201b;margin:0 0 10px;">${escapeHtml(locale.label)}</h2>
${welcome}
${intro}
</section>`);
} catch (error) {
console.warn(`[SEO] Locale konnte nicht gelesen werden (${locale.code}): ${error.message}`);
}
}
if (blocks.length === 0) return '';
return `<section style="max-width:960px;margin:24px auto;padding:0 16px;">
<h2 style="font:600 28px/1.15 sans-serif;color:#18201b;margin:0 0 12px;">Mehrsprachige Inhalte</h2>
<p style="font:400 15px/1.5 sans-serif;color:#4f5d54;margin:0 0 18px;">Diese Texte sind serverseitig eingebettet, damit Suchmaschinen die Inhalte in allen verfügbaren Sprachen direkt erfassen können.</p>
${blocks.join('\n')}
</section>`;
}
function generateHTML(route, meta, __dirname) { function generateHTML(route, meta, __dirname) {
const distIndexPath = join(__dirname, '../docroot/dist/index.html'); const distIndexPath = join(__dirname, '../docroot/dist/index.html');
@@ -244,6 +294,13 @@ function generateHTML(route, meta, __dirname) {
html = upsertLinkTag(html, 'canonical', meta.ogUrl); html = upsertLinkTag(html, 'canonical', meta.ogUrl);
html = upsertJsonLd(html, meta.schema); html = upsertJsonLd(html, meta.schema);
if (route === '/') {
const multilingual = buildMultilingualSeoContent(__dirname);
if (multilingual) {
html = html.replace('<div id="app"></div>', `<div id="app">${multilingual}</div>`);
}
}
if (route === '/feedback') { if (route === '/feedback') {
const feedbackItems = loadFeedback(__dirname) const feedbackItems = loadFeedback(__dirname)
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))