فرانتاند: نسخه انگلیسی با مسیر /en/ — مسیریابی دوزبانه، دیکشنری رابط، بومیسازی داده، تغییر زبان در هدر، ویرایش انگلیسی در پنل و پشتیبانی LTR
This commit is contained in:
+38
-4
@@ -1,7 +1,10 @@
|
||||
// تگهای سئوی سمت مرورگر — عنوان، توضیح متا، OG، توییتر، canonical و JSON-LD.
|
||||
// تگهای سئوی سمت مرورگر — عنوان، توضیح متا، OG، توییتر، canonical، hreflang و JSON-LD.
|
||||
// محتوا از دیتابیس میآید؛ برای خزندههای بدون جاوااسکریپت، بکاند همان تگها را
|
||||
// سمت سرور داخل index.html تزریق میکند (app/seo_pages.py).
|
||||
|
||||
import { localeHref, langFromPathname } from './i18n/lang'
|
||||
import type { Lang } from './i18n/lang'
|
||||
|
||||
export function setMetaDescription(content: string): void {
|
||||
// بدون توضیح از دیتابیس، پیشفرض استاتیک index.html دستنخورده میماند
|
||||
if (!content.trim()) return
|
||||
@@ -16,11 +19,13 @@ export function setMetaDescription(content: string): void {
|
||||
|
||||
/** کلید سئوی مسیر جاری — فقط صفحههای فهرست دقیق؛ صفحههای جزئیات خودشان عنوان میگذارند */
|
||||
export function seoKeyForPath(pathname: string): string | null {
|
||||
if (pathname === '/') return 'home'
|
||||
const seg = pathname.split('/')[1] ?? ''
|
||||
// زبان از پیشوند /en جدا میشود — کلیدهای سئو برای هر دو زبان یکی است
|
||||
const clean = pathname === '/en' ? '/' : pathname.startsWith('/en/') ? pathname.slice(3) : pathname
|
||||
if (clean === '/') return 'home'
|
||||
const seg = clean.split('/')[1] ?? ''
|
||||
const known = ['about', 'services', 'projects', 'investment', 'blog', 'faq', 'contact']
|
||||
// مسیرهای جزئی مثل /blog/{slug} شامل نمیشوند تا اثر خود صفحه جزئیات برسانند
|
||||
return known.includes(seg) && pathname === `/${seg}` ? seg : null
|
||||
return known.includes(seg) && clean === `/${seg}` ? seg : null
|
||||
}
|
||||
|
||||
function upsertMeta(attr: 'name' | 'property', key: string, content: string): void {
|
||||
@@ -43,6 +48,30 @@ function upsertCanonical(path: string): void {
|
||||
el.setAttribute('href', new URL(path, window.location.origin).href)
|
||||
}
|
||||
|
||||
const HREFLANGS: [Lang, string][] = [
|
||||
['fa', 'fa'],
|
||||
['en', 'en'],
|
||||
['fa', 'x-default'],
|
||||
]
|
||||
|
||||
/** لینکهای alternate دو زبانه — مسیر جاری و معادل آن در زبان دیگر */
|
||||
function upsertAlternates(path: string): void {
|
||||
for (const [targetLang, hreflang] of HREFLANGS) {
|
||||
// x-default همیشه نسخه فارسی است
|
||||
const target = localeHref(path, targetLang)
|
||||
const attr = `data-hreflang="${hreflang}"`
|
||||
let el = document.querySelector<HTMLLinkElement>(`link[${attr}]`)
|
||||
if (!el) {
|
||||
el = document.createElement('link')
|
||||
el.setAttribute('rel', 'alternate')
|
||||
el.setAttribute('hreflang', hreflang)
|
||||
el.setAttribute('data-hreflang', hreflang)
|
||||
document.head.appendChild(el)
|
||||
}
|
||||
el.setAttribute('href', new URL(target, window.location.origin).href)
|
||||
}
|
||||
}
|
||||
|
||||
function setJsonLd(data: Record<string, unknown> | null): void {
|
||||
const id = 'seo-jsonld'
|
||||
let el = document.getElementById(id)
|
||||
@@ -67,6 +96,8 @@ export type SeoInput = {
|
||||
/** تصویر OG — مسیر نسبی مثل /media/... به آدرس مطلق تبدیل میشود */
|
||||
image?: string
|
||||
type?: 'website' | 'article'
|
||||
/** زبان جاری — برای og:locale و لینکهای hreflang */
|
||||
lang?: Lang
|
||||
jsonLd?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
@@ -76,6 +107,7 @@ export function applySeo(input: SeoInput): void {
|
||||
const description = (input.description ?? '').trim()
|
||||
const path = input.path ?? window.location.pathname
|
||||
const image = (input.image ?? '').trim()
|
||||
const lang: Lang = input.lang ?? langFromPathname(path)
|
||||
|
||||
document.title = title
|
||||
setMetaDescription(description)
|
||||
@@ -84,11 +116,13 @@ export function applySeo(input: SeoInput): void {
|
||||
upsertMeta('property', 'og:description', description)
|
||||
upsertMeta('property', 'og:type', input.type ?? 'website')
|
||||
upsertMeta('property', 'og:url', new URL(path, window.location.origin).href)
|
||||
upsertMeta('property', 'og:locale', lang === 'en' ? 'en_US' : 'fa_IR')
|
||||
upsertMeta('name', 'twitter:card', 'summary_large_image')
|
||||
if (image) {
|
||||
upsertMeta('property', 'og:image', new URL(image, window.location.origin).href)
|
||||
} else {
|
||||
document.querySelector<HTMLMetaElement>('meta[property="og:image"]')?.remove()
|
||||
}
|
||||
upsertAlternates(path)
|
||||
setJsonLd(input.jsonLd ?? null)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user