Files
aramesh-backend/frontend/src/i18n/localize.ts
T

210 lines
9.5 KiB
TypeScript

// بومی‌سازی درخت داده سایت — انتخاب فیلدهای انگلیسی با برگشت به فارسی.
// فرانت‌اند هر دو زبان را در یک درخواست از GET /api/site می‌گیرد و سوییچ زبان
// بی‌درنگ و بدون درخواست تازه انجام می‌شود. داده خام برای پنل مدیریت دست‌نخورده می‌ماند.
import type { Article, ArticleBodyDoc } from '../data/site'
import type { SiteData } from '../api'
import type { Lang } from './lang'
/** متن انگلیسی فقط وقتی جایگزین می‌شود که واقعاً چیزی نوشته شده باشد */
const s = (fa: string, en?: string | null): string => (en && en.trim() ? en : fa)
/** لیست انگلیسی فقط وقتی جایگزین می‌شود که غیرخالی باشد */
const list = <T,>(fa: T[], en?: T[] | null): T[] => (en && en.length ? en : fa)
/** سند TipTap انگلیسی فقط وقتی متن واقعی دارد جایگزین بدنه فارسی می‌شود */
function docHasText(doc: unknown): boolean {
if (!doc || typeof doc !== 'object') return false
const content = (doc as ArticleBodyDoc).content
if (!Array.isArray(content)) return false
let found = false
const walk = (node: unknown): void => {
if (!node || typeof node !== 'object' || found) return
const n = node as { type?: string; text?: string; content?: unknown[] }
if (n.type === 'text' && typeof n.text === 'string' && n.text.trim()) found = true
n.content?.forEach(walk)
}
content.forEach(walk)
return found
}
export function localizeSite(data: SiteData, lang: Lang): SiteData {
if (lang === 'fa') return data
return {
brand: {
...data.brand,
name: s(data.brand.name, data.brand.name_en),
address: s(data.brand.address, data.brand.address_en),
},
nav: data.nav.map((n) => ({ ...n, label: s(n.label, n.label_en) })),
hero: {
...data.hero,
kicker: s(data.hero.kicker, data.hero.kicker_en),
title: s(data.hero.title, data.hero.title_en),
title_highlight: s(data.hero.title_highlight, data.hero.title_highlight_en),
subtitle: s(data.hero.subtitle, data.hero.subtitle_en),
badge_value: s(data.hero.badge_value, data.hero.badge_value_en),
badge_label: s(data.hero.badge_label, data.hero.badge_label_en),
cta_primary_label: s(data.hero.cta_primary_label, data.hero.cta_primary_label_en),
cta_secondary_label: s(data.hero.cta_secondary_label, data.hero.cta_secondary_label_en),
scroll_label: s(data.hero.scroll_label, data.hero.scroll_label_en),
},
story: {
...data.story,
title: s(data.story.title, data.story.title_en),
paragraphs: list(data.story.paragraphs, data.story.paragraphs_en),
quote: s(data.story.quote, data.story.quote_en),
quote_author: s(data.story.quote_author, data.story.quote_author_en),
stat1_value: s(data.story.stat1_value, data.story.stat1_value_en),
stat1_label: s(data.story.stat1_label, data.story.stat1_label_en),
stat2_value: s(data.story.stat2_value, data.story.stat2_value_en),
stat2_label: s(data.story.stat2_label, data.story.stat2_label_en),
link_label: s(data.story.link_label, data.story.link_label_en),
bento: list(data.story.bento, data.story.bento_en),
},
section_headings: data.section_headings.map((h) => ({
...h,
label: s(h.label, h.label_en),
title: s(h.title, h.title_en),
lead: s(h.lead, h.lead_en),
cta_label: s(h.cta_label, h.cta_label_en),
})),
page_heroes: data.page_heroes.map((p) => ({
...p,
title: s(p.title, p.title_en),
lead: s(p.lead, p.lead_en),
})),
services: data.services.map((sv) => ({
...sv,
title: s(sv.title, sv.title_en),
desc: s(sv.desc, sv.desc_en),
})),
projects: data.projects.map((p) => ({
...p,
title: s(p.title, p.title_en),
location: s(p.location, p.location_en),
year: s(p.year, p.year_en),
area: s(p.area, p.area_en),
floors: s(p.floors, p.floors_en),
units: s(p.units, p.units_en),
client: s(p.client, p.client_en),
summary: s(p.summary, p.summary_en),
body: list(p.body, p.body_en),
})),
articles: data.articles.map((a: Article) => ({
...a,
title: s(a.title, a.title_en),
date: s(a.date, a.date_en),
category: s(a.category, a.category_en),
read: s(a.read, a.read_en),
excerpt: s(a.excerpt, a.excerpt_en),
body: docHasText(a.body_en) ? a.body_en! : a.body,
})),
testimonials: data.testimonials.map((tm) => ({
...tm,
name: s(tm.name, tm.name_en),
role: s(tm.role, tm.role_en),
quote: s(tm.quote, tm.quote_en),
})),
faqs: data.faqs.map((f) => ({
...f,
question: s(f.question, f.question_en),
answer: s(f.answer, f.answer_en),
})),
team: data.team.map((m) => ({
...m,
name: s(m.name, m.name_en),
role: s(m.role, m.role_en),
bio: s(m.bio, m.bio_en),
})),
timeline: data.timeline.map((tm) => ({
...tm,
year: s(tm.year, tm.year_en),
title: s(tm.title, tm.title_en),
desc: s(tm.desc, tm.desc_en),
})),
stats: data.stats.map((st) => ({
...st,
suffix: s(st.suffix, st.suffix_en),
label: s(st.label, st.label_en),
})),
why_us: data.why_us.map((w) => ({
...w,
title: s(w.title, w.title_en),
desc: s(w.desc, w.desc_en),
})),
before_after: {
...data.before_after,
title: s(data.before_after.title, data.before_after.title_en),
lead: s(data.before_after.lead, data.before_after.lead_en),
before_label: s(data.before_after.before_label, data.before_after.before_label_en),
after_label: s(data.before_after.after_label, data.before_after.after_label_en),
},
investment_models: data.investment_models.map((im) => ({
...im,
title: s(im.title, im.title_en),
desc: s(im.desc, im.desc_en),
points: list(im.points, im.points_en),
})),
process_steps: data.process_steps.map((ps) => ({
...ps,
step: s(ps.step, ps.step_en),
title: s(ps.title, ps.title_en),
desc: s(ps.desc, ps.desc_en),
})),
offices: data.offices.map((o) => ({
...o,
city: s(o.city, o.city_en),
address: s(o.address, o.address_en),
hours: s(o.hours, o.hours_en),
})),
gallery: data.gallery.map((g) => ({ ...g, alt: s(g.alt, g.alt_en) })),
status_labels: data.status_labels.map((sl) => ({ ...sl, label: s(sl.label, sl.label_en) })),
contact_section: {
...data.contact_section,
label: s(data.contact_section.label, data.contact_section.label_en),
title: s(data.contact_section.title, data.contact_section.title_en),
lead: s(data.contact_section.lead, data.contact_section.lead_en),
address_label: s(data.contact_section.address_label, data.contact_section.address_label_en),
phone_label: s(data.contact_section.phone_label, data.contact_section.phone_label_en),
email_label: s(data.contact_section.email_label, data.contact_section.email_label_en),
name_label: s(data.contact_section.name_label, data.contact_section.name_label_en),
name_placeholder: s(data.contact_section.name_placeholder, data.contact_section.name_placeholder_en),
phone_field_label: s(data.contact_section.phone_field_label, data.contact_section.phone_field_label_en),
phone_placeholder: s(data.contact_section.phone_placeholder, data.contact_section.phone_placeholder_en),
note_label: s(data.contact_section.note_label, data.contact_section.note_label_en),
note_optional_label: s(data.contact_section.note_optional_label, data.contact_section.note_optional_label_en),
note_placeholder: s(data.contact_section.note_placeholder, data.contact_section.note_placeholder_en),
submit_label: s(data.contact_section.submit_label, data.contact_section.submit_label_en),
sending_label: s(data.contact_section.sending_label, data.contact_section.sending_label_en),
success_title: s(data.contact_section.success_title, data.contact_section.success_title_en),
success_text: s(data.contact_section.success_text, data.contact_section.success_text_en),
success_button_label: s(data.contact_section.success_button_label, data.contact_section.success_button_label_en),
},
newsletter: {
...data.newsletter,
title: s(data.newsletter.title, data.newsletter.title_en),
lead: s(data.newsletter.lead, data.newsletter.lead_en),
placeholder: s(data.newsletter.placeholder, data.newsletter.placeholder_en),
button_label: s(data.newsletter.button_label, data.newsletter.button_label_en),
sending_label: s(data.newsletter.sending_label, data.newsletter.sending_label_en),
success_text: s(data.newsletter.success_text, data.newsletter.success_text_en),
},
footer: {
...data.footer,
about_text: s(data.footer.about_text, data.footer.about_text_en),
pages_heading: s(data.footer.pages_heading, data.footer.pages_heading_en),
services_heading: s(data.footer.services_heading, data.footer.services_heading_en),
contact_heading: s(data.footer.contact_heading, data.footer.contact_heading_en),
copyright: s(data.footer.copyright, data.footer.copyright_en),
tagline: s(data.footer.tagline, data.footer.tagline_en),
socials: list(data.footer.socials, data.footer.socials_en),
},
seo_meta: data.seo_meta.map((m) => ({
...m,
title: s(m.title, m.title_en),
description: s(m.description, m.description_en),
})),
}
}