lang full

This commit is contained in:
payacom
2026-08-16 11:42:39 +03:30
parent c879976c56
commit 0c6e1a7125
101 changed files with 565082 additions and 1137 deletions

View File

@@ -37,9 +37,29 @@ export function resolveBrowserLanguage(): AppLanguage {
return resolveLanguageFromLanguageTags(tags) ?? DEFAULT_LANGUAGE;
}
/** Client locale: saved preference → system language → default (fa) */
/** The language the server already rendered `<html lang>` with. Every registry
* entry's `htmlLang` equals its `code`, so this is a direct, reliable readback —
* no separate mapping table needed. Used to avoid re-guessing the language on the
* client from `navigator.language`, which frequently disagrees with the server's
* Accept-Language-based guess (browsers/proxies often send a different header
* than the OS locale) and was causing a full-page hydration mismatch on every
* first visit (no cookie yet) — visible as every label/alt-text swapping
* language right after hydration, which also broke SEO tools reading the page. */
export function resolveServerRenderedLanguage(): AppLanguage | null {
if (typeof document === "undefined") return null;
const htmlLang = document.documentElement.lang;
return isAppLanguage(htmlLang) ? htmlLang : null;
}
/** Client locale: saved preference → language the server already rendered with
* → guessed system language → default (fa). Preferring the server's own choice
* over an independent client-side guess keeps hydration consistent. */
export function resolveClientLanguage(): AppLanguage {
return readStoredLanguagePreference() ?? resolveBrowserLanguage();
return (
readStoredLanguagePreference() ??
resolveServerRenderedLanguage() ??
resolveBrowserLanguage()
);
}
export function persistLanguagePreference(lang: AppLanguage) {

View File

@@ -45,44 +45,41 @@ export const LANGUAGE_REGISTRY: Record<string, LanguageDefinition> = {
enabled: true,
primary: true,
},
// Below: registered but not yet enabled — no locale files exist for these yet, so
// they render in the picker's "Other languages" section without being selectable.
// Set enabled: true once src/locales/{code}/common.json (+ seo.json) exist and are
// registered in src/lib/i18n/resources.ts.
// All languages below have full translated locale files and are enabled.
ar: { code: "ar", nativeLabel: "العربية", englishLabel: "Arabic", direction: "rtl", htmlLang: "ar", ogLocale: "ar_SA", dateLocale: "ar-SA", enabled: true },
ku: { code: "ku", nativeLabel: "کوردی", englishLabel: "Kurdish", direction: "rtl", htmlLang: "ku", ogLocale: "ku_TR", dateLocale: "ku", enabled: false },
tr: { code: "tr", nativeLabel: "Türkçe", englishLabel: "Turkish", direction: "ltr", htmlLang: "tr", ogLocale: "tr_TR", dateLocale: "tr-TR", enabled: false },
az: { code: "az", nativeLabel: "Azərbaycan dili", englishLabel: "Azerbaijani", direction: "ltr", htmlLang: "az", ogLocale: "az_AZ", dateLocale: "az-AZ", enabled: false },
ru: { code: "ru", nativeLabel: "Русский", englishLabel: "Russian", direction: "ltr", htmlLang: "ru", ogLocale: "ru_RU", dateLocale: "ru-RU", enabled: false },
uk: { code: "uk", nativeLabel: "Українська", englishLabel: "Ukrainian", direction: "ltr", htmlLang: "uk", ogLocale: "uk_UA", dateLocale: "uk-UA", enabled: false },
es: { code: "es", nativeLabel: "Español", englishLabel: "Spanish", direction: "ltr", htmlLang: "es", ogLocale: "es_ES", dateLocale: "es-ES", enabled: false },
ro: { code: "ro", nativeLabel: "Română", englishLabel: "Romanian", direction: "ltr", htmlLang: "ro", ogLocale: "ro_RO", dateLocale: "ro-RO", enabled: false },
sk: { code: "sk", nativeLabel: "Slovenčina", englishLabel: "Slovak", direction: "ltr", htmlLang: "sk", ogLocale: "sk_SK", dateLocale: "sk-SK", enabled: false },
pl: { code: "pl", nativeLabel: "Polski", englishLabel: "Polish", direction: "ltr", htmlLang: "pl", ogLocale: "pl_PL", dateLocale: "pl-PL", enabled: false },
hu: { code: "hu", nativeLabel: "Magyar", englishLabel: "Hungarian", direction: "ltr", htmlLang: "hu", ogLocale: "hu_HU", dateLocale: "hu-HU", enabled: false },
hr: { code: "hr", nativeLabel: "Hrvatski", englishLabel: "Croatian", direction: "ltr", htmlLang: "hr", ogLocale: "hr_HR", dateLocale: "hr-HR", enabled: false },
sr: { code: "sr", nativeLabel: "Српски", englishLabel: "Serbian", direction: "ltr", htmlLang: "sr", ogLocale: "sr_RS", dateLocale: "sr-RS", enabled: false },
el: { code: "el", nativeLabel: "Ελληνικά", englishLabel: "Greek", direction: "ltr", htmlLang: "el", ogLocale: "el_GR", dateLocale: "el-GR", enabled: false },
sv: { code: "sv", nativeLabel: "Svenska", englishLabel: "Swedish", direction: "ltr", htmlLang: "sv", ogLocale: "sv_SE", dateLocale: "sv-SE", enabled: false },
de: { code: "de", nativeLabel: "Deutsch", englishLabel: "German", direction: "ltr", htmlLang: "de", ogLocale: "de_DE", dateLocale: "de-DE", enabled: false },
it: { code: "it", nativeLabel: "Italiano", englishLabel: "Italian", direction: "ltr", htmlLang: "it", ogLocale: "it_IT", dateLocale: "it-IT", enabled: false },
hi: { code: "hi", nativeLabel: "हिन्दी", englishLabel: "Hindi", direction: "ltr", htmlLang: "hi", ogLocale: "hi_IN", dateLocale: "hi-IN", enabled: false },
bn: { code: "bn", nativeLabel: "বাংলা", englishLabel: "Bengali", direction: "ltr", htmlLang: "bn", ogLocale: "bn_BD", dateLocale: "bn-BD", enabled: false },
th: { code: "th", nativeLabel: "ไทย", englishLabel: "Thai", direction: "ltr", htmlLang: "th", ogLocale: "th_TH", dateLocale: "th-TH", enabled: false },
ms: { code: "ms", nativeLabel: "Bahasa Melayu", englishLabel: "Malay", direction: "ltr", htmlLang: "ms", ogLocale: "ms_MY", dateLocale: "ms-MY", enabled: false },
tl: { code: "tl", nativeLabel: "Filipino", englishLabel: "Filipino", direction: "ltr", htmlLang: "tl", ogLocale: "fil_PH", dateLocale: "fil-PH", enabled: false },
fr: { code: "fr", nativeLabel: "Français", englishLabel: "French", direction: "ltr", htmlLang: "fr", ogLocale: "fr_FR", dateLocale: "fr-FR", enabled: false },
nl: { code: "nl", nativeLabel: "Nederlands", englishLabel: "Dutch", direction: "ltr", htmlLang: "nl", ogLocale: "nl_NL", dateLocale: "nl-NL", enabled: false },
no: { code: "no", nativeLabel: "Norsk", englishLabel: "Norwegian", direction: "ltr", htmlLang: "no", ogLocale: "nb_NO", dateLocale: "nb-NO", enabled: false },
fi: { code: "fi", nativeLabel: "Suomi", englishLabel: "Finnish", direction: "ltr", htmlLang: "fi", ogLocale: "fi_FI", dateLocale: "fi-FI", enabled: false },
ps: { code: "ps", nativeLabel: "پښتو", englishLabel: "Pashto", direction: "rtl", htmlLang: "ps", ogLocale: "ps_AF", dateLocale: "ps-AF", enabled: false },
uz: { code: "uz", nativeLabel: "O'zbekcha", englishLabel: "Uzbek", direction: "ltr", htmlLang: "uz", ogLocale: "uz_UZ", dateLocale: "uz-UZ", enabled: false },
hy: { code: "hy", nativeLabel: "Հայերեն", englishLabel: "Armenian", direction: "ltr", htmlLang: "hy", ogLocale: "hy_AM", dateLocale: "hy-AM", enabled: false },
"zh-CN": { code: "zh-CN", nativeLabel: "简体中文", englishLabel: "Chinese (Simplified)", direction: "ltr", htmlLang: "zh-CN", ogLocale: "zh_CN", dateLocale: "zh-CN", enabled: false },
"zh-TW": { code: "zh-TW", nativeLabel: "繁體中文(台灣)", englishLabel: "Chinese (Traditional, Taiwan)", direction: "ltr", htmlLang: "zh-TW", ogLocale: "zh_TW", dateLocale: "zh-TW", enabled: false },
"zh-HK": { code: "zh-HK", nativeLabel: "繁體中文(香港)", englishLabel: "Chinese (Traditional, Hong Kong)", direction: "ltr", htmlLang: "zh-HK", ogLocale: "zh_HK", dateLocale: "zh-HK", enabled: false },
ja: { code: "ja", nativeLabel: "日本語", englishLabel: "Japanese", direction: "ltr", htmlLang: "ja", ogLocale: "ja_JP", dateLocale: "ja-JP", enabled: false },
ko: { code: "ko", nativeLabel: "한국어", englishLabel: "Korean", direction: "ltr", htmlLang: "ko", ogLocale: "ko_KR", dateLocale: "ko-KR", enabled: false },
ku: { code: "ku", nativeLabel: "کوردی", englishLabel: "Kurdish", direction: "rtl", htmlLang: "ku", ogLocale: "ku_TR", dateLocale: "ku", enabled: true },
tr: { code: "tr", nativeLabel: "Türkçe", englishLabel: "Turkish", direction: "ltr", htmlLang: "tr", ogLocale: "tr_TR", dateLocale: "tr-TR", enabled: true },
az: { code: "az", nativeLabel: "Azərbaycan dili", englishLabel: "Azerbaijani", direction: "ltr", htmlLang: "az", ogLocale: "az_AZ", dateLocale: "az-AZ", enabled: true },
ru: { code: "ru", nativeLabel: "Русский", englishLabel: "Russian", direction: "ltr", htmlLang: "ru", ogLocale: "ru_RU", dateLocale: "ru-RU", enabled: true },
uk: { code: "uk", nativeLabel: "Українська", englishLabel: "Ukrainian", direction: "ltr", htmlLang: "uk", ogLocale: "uk_UA", dateLocale: "uk-UA", enabled: true },
es: { code: "es", nativeLabel: "Español", englishLabel: "Spanish", direction: "ltr", htmlLang: "es", ogLocale: "es_ES", dateLocale: "es-ES", enabled: true },
ro: { code: "ro", nativeLabel: "Română", englishLabel: "Romanian", direction: "ltr", htmlLang: "ro", ogLocale: "ro_RO", dateLocale: "ro-RO", enabled: true },
sk: { code: "sk", nativeLabel: "Slovenčina", englishLabel: "Slovak", direction: "ltr", htmlLang: "sk", ogLocale: "sk_SK", dateLocale: "sk-SK", enabled: true },
pl: { code: "pl", nativeLabel: "Polski", englishLabel: "Polish", direction: "ltr", htmlLang: "pl", ogLocale: "pl_PL", dateLocale: "pl-PL", enabled: true },
hu: { code: "hu", nativeLabel: "Magyar", englishLabel: "Hungarian", direction: "ltr", htmlLang: "hu", ogLocale: "hu_HU", dateLocale: "hu-HU", enabled: true },
hr: { code: "hr", nativeLabel: "Hrvatski", englishLabel: "Croatian", direction: "ltr", htmlLang: "hr", ogLocale: "hr_HR", dateLocale: "hr-HR", enabled: true },
sr: { code: "sr", nativeLabel: "Српски", englishLabel: "Serbian", direction: "ltr", htmlLang: "sr", ogLocale: "sr_RS", dateLocale: "sr-RS", enabled: true },
el: { code: "el", nativeLabel: "Ελληνικά", englishLabel: "Greek", direction: "ltr", htmlLang: "el", ogLocale: "el_GR", dateLocale: "el-GR", enabled: true },
sv: { code: "sv", nativeLabel: "Svenska", englishLabel: "Swedish", direction: "ltr", htmlLang: "sv", ogLocale: "sv_SE", dateLocale: "sv-SE", enabled: true },
de: { code: "de", nativeLabel: "Deutsch", englishLabel: "German", direction: "ltr", htmlLang: "de", ogLocale: "de_DE", dateLocale: "de-DE", enabled: true },
it: { code: "it", nativeLabel: "Italiano", englishLabel: "Italian", direction: "ltr", htmlLang: "it", ogLocale: "it_IT", dateLocale: "it-IT", enabled: true },
hi: { code: "hi", nativeLabel: "हिन्दी", englishLabel: "Hindi", direction: "ltr", htmlLang: "hi", ogLocale: "hi_IN", dateLocale: "hi-IN", enabled: true },
bn: { code: "bn", nativeLabel: "বাংলা", englishLabel: "Bengali", direction: "ltr", htmlLang: "bn", ogLocale: "bn_BD", dateLocale: "bn-BD", enabled: true },
th: { code: "th", nativeLabel: "ไทย", englishLabel: "Thai", direction: "ltr", htmlLang: "th", ogLocale: "th_TH", dateLocale: "th-TH", enabled: true },
ms: { code: "ms", nativeLabel: "Bahasa Melayu", englishLabel: "Malay", direction: "ltr", htmlLang: "ms", ogLocale: "ms_MY", dateLocale: "ms-MY", enabled: true },
tl: { code: "tl", nativeLabel: "Filipino", englishLabel: "Filipino", direction: "ltr", htmlLang: "tl", ogLocale: "fil_PH", dateLocale: "fil-PH", enabled: true },
fr: { code: "fr", nativeLabel: "Français", englishLabel: "French", direction: "ltr", htmlLang: "fr", ogLocale: "fr_FR", dateLocale: "fr-FR", enabled: true },
nl: { code: "nl", nativeLabel: "Nederlands", englishLabel: "Dutch", direction: "ltr", htmlLang: "nl", ogLocale: "nl_NL", dateLocale: "nl-NL", enabled: true },
no: { code: "no", nativeLabel: "Norsk", englishLabel: "Norwegian", direction: "ltr", htmlLang: "no", ogLocale: "nb_NO", dateLocale: "nb-NO", enabled: true },
fi: { code: "fi", nativeLabel: "Suomi", englishLabel: "Finnish", direction: "ltr", htmlLang: "fi", ogLocale: "fi_FI", dateLocale: "fi-FI", enabled: true },
ps: { code: "ps", nativeLabel: "پښتو", englishLabel: "Pashto", direction: "rtl", htmlLang: "ps", ogLocale: "ps_AF", dateLocale: "ps-AF", enabled: true },
uz: { code: "uz", nativeLabel: "O'zbekcha", englishLabel: "Uzbek", direction: "ltr", htmlLang: "uz", ogLocale: "uz_UZ", dateLocale: "uz-UZ", enabled: true },
hy: { code: "hy", nativeLabel: "Հայերեն", englishLabel: "Armenian", direction: "ltr", htmlLang: "hy", ogLocale: "hy_AM", dateLocale: "hy-AM", enabled: true },
"zh-CN": { code: "zh-CN", nativeLabel: "简体中文", englishLabel: "Chinese (Simplified)", direction: "ltr", htmlLang: "zh-CN", ogLocale: "zh_CN", dateLocale: "zh-CN", enabled: true },
"zh-TW": { code: "zh-TW", nativeLabel: "繁體中文(台灣)", englishLabel: "Chinese (Traditional, Taiwan)", direction: "ltr", htmlLang: "zh-TW", ogLocale: "zh_TW", dateLocale: "zh-TW", enabled: true },
"zh-HK": { code: "zh-HK", nativeLabel: "繁體中文(香港)", englishLabel: "Chinese (Traditional, Hong Kong)", direction: "ltr", htmlLang: "zh-HK", ogLocale: "zh_HK", dateLocale: "zh-HK", enabled: true },
ja: { code: "ja", nativeLabel: "日本語", englishLabel: "Japanese", direction: "ltr", htmlLang: "ja", ogLocale: "ja_JP", dateLocale: "ja-JP", enabled: true },
ko: { code: "ko", nativeLabel: "한국어", englishLabel: "Korean", direction: "ltr", htmlLang: "ko", ogLocale: "ko_KR", dateLocale: "ko-KR", enabled: true },
};
export type AppLanguage = keyof typeof LANGUAGE_REGISTRY;

View File

@@ -4,6 +4,72 @@ import enCommon from "@/locales/en/common.json";
import enSeo from "@/locales/en/seo.json";
import arCommon from "@/locales/ar/common.json";
import arSeo from "@/locales/ar/seo.json";
import trCommon from "@/locales/tr/common.json";
import trSeo from "@/locales/tr/seo.json";
import azCommon from "@/locales/az/common.json";
import azSeo from "@/locales/az/seo.json";
import ruCommon from "@/locales/ru/common.json";
import ruSeo from "@/locales/ru/seo.json";
import esCommon from "@/locales/es/common.json";
import esSeo from "@/locales/es/seo.json";
import kuCommon from "@/locales/ku/common.json";
import kuSeo from "@/locales/ku/seo.json";
import ukCommon from "@/locales/uk/common.json";
import ukSeo from "@/locales/uk/seo.json";
import roCommon from "@/locales/ro/common.json";
import roSeo from "@/locales/ro/seo.json";
import skCommon from "@/locales/sk/common.json";
import skSeo from "@/locales/sk/seo.json";
import plCommon from "@/locales/pl/common.json";
import plSeo from "@/locales/pl/seo.json";
import huCommon from "@/locales/hu/common.json";
import huSeo from "@/locales/hu/seo.json";
import hrCommon from "@/locales/hr/common.json";
import hrSeo from "@/locales/hr/seo.json";
import srCommon from "@/locales/sr/common.json";
import srSeo from "@/locales/sr/seo.json";
import elCommon from "@/locales/el/common.json";
import elSeo from "@/locales/el/seo.json";
import svCommon from "@/locales/sv/common.json";
import svSeo from "@/locales/sv/seo.json";
import deCommon from "@/locales/de/common.json";
import deSeo from "@/locales/de/seo.json";
import itCommon from "@/locales/it/common.json";
import itSeo from "@/locales/it/seo.json";
import hiCommon from "@/locales/hi/common.json";
import hiSeo from "@/locales/hi/seo.json";
import bnCommon from "@/locales/bn/common.json";
import bnSeo from "@/locales/bn/seo.json";
import thCommon from "@/locales/th/common.json";
import thSeo from "@/locales/th/seo.json";
import msCommon from "@/locales/ms/common.json";
import msSeo from "@/locales/ms/seo.json";
import tlCommon from "@/locales/tl/common.json";
import tlSeo from "@/locales/tl/seo.json";
import frCommon from "@/locales/fr/common.json";
import frSeo from "@/locales/fr/seo.json";
import nlCommon from "@/locales/nl/common.json";
import nlSeo from "@/locales/nl/seo.json";
import noCommon from "@/locales/no/common.json";
import noSeo from "@/locales/no/seo.json";
import fiCommon from "@/locales/fi/common.json";
import fiSeo from "@/locales/fi/seo.json";
import psCommon from "@/locales/ps/common.json";
import psSeo from "@/locales/ps/seo.json";
import uzCommon from "@/locales/uz/common.json";
import uzSeo from "@/locales/uz/seo.json";
import hyCommon from "@/locales/hy/common.json";
import hySeo from "@/locales/hy/seo.json";
import zhCNCommon from "@/locales/zh-CN/common.json";
import zhCNSeo from "@/locales/zh-CN/seo.json";
import zhTWCommon from "@/locales/zh-TW/common.json";
import zhTWSeo from "@/locales/zh-TW/seo.json";
import zhHKCommon from "@/locales/zh-HK/common.json";
import zhHKSeo from "@/locales/zh-HK/seo.json";
import jaCommon from "@/locales/ja/common.json";
import jaSeo from "@/locales/ja/seo.json";
import koCommon from "@/locales/ko/common.json";
import koSeo from "@/locales/ko/seo.json";
import {
DEFAULT_LANGUAGE,
ENABLED_LANGUAGES,
@@ -16,9 +82,8 @@ export const LOCALE_NAMESPACES = ["common", "seo"] as const;
export type LocaleNamespace = (typeof LOCALE_NAMESPACES)[number];
/**
* Locale bundles — add a new language by importing its JSON files here.
* Partial: most registry entries are `enabled: false` placeholders with no
* locale files yet (see registry.ts) — only enabled languages need a bundle.
* Locale bundles — add a new language by importing its JSON files here
* and registering it in registry.ts with `enabled: true`.
*/
export const LOCALE_BUNDLES: Partial<
Record<AppLanguage, Record<LocaleNamespace, Record<string, unknown>>>
@@ -35,6 +100,138 @@ export const LOCALE_BUNDLES: Partial<
common: arCommon,
seo: arSeo,
},
tr: {
common: trCommon,
seo: trSeo,
},
az: {
common: azCommon,
seo: azSeo,
},
ru: {
common: ruCommon,
seo: ruSeo,
},
es: {
common: esCommon,
seo: esSeo,
},
ku: {
common: kuCommon,
seo: kuSeo,
},
uk: {
common: ukCommon,
seo: ukSeo,
},
ro: {
common: roCommon,
seo: roSeo,
},
sk: {
common: skCommon,
seo: skSeo,
},
pl: {
common: plCommon,
seo: plSeo,
},
hu: {
common: huCommon,
seo: huSeo,
},
hr: {
common: hrCommon,
seo: hrSeo,
},
sr: {
common: srCommon,
seo: srSeo,
},
el: {
common: elCommon,
seo: elSeo,
},
sv: {
common: svCommon,
seo: svSeo,
},
de: {
common: deCommon,
seo: deSeo,
},
it: {
common: itCommon,
seo: itSeo,
},
hi: {
common: hiCommon,
seo: hiSeo,
},
bn: {
common: bnCommon,
seo: bnSeo,
},
th: {
common: thCommon,
seo: thSeo,
},
ms: {
common: msCommon,
seo: msSeo,
},
tl: {
common: tlCommon,
seo: tlSeo,
},
fr: {
common: frCommon,
seo: frSeo,
},
nl: {
common: nlCommon,
seo: nlSeo,
},
no: {
common: noCommon,
seo: noSeo,
},
fi: {
common: fiCommon,
seo: fiSeo,
},
ps: {
common: psCommon,
seo: psSeo,
},
uz: {
common: uzCommon,
seo: uzSeo,
},
hy: {
common: hyCommon,
seo: hySeo,
},
"zh-CN": {
common: zhCNCommon,
seo: zhCNSeo,
},
"zh-TW": {
common: zhTWCommon,
seo: zhTWSeo,
},
"zh-HK": {
common: zhHKCommon,
seo: zhHKSeo,
},
ja: {
common: jaCommon,
seo: jaSeo,
},
ko: {
common: koCommon,
seo: koSeo,
},
};
export function buildI18nResources() {

View File

@@ -1,4 +1,8 @@
export type PwaLang = "fa" | "en";
import { getDirection, type AppLanguage } from "@/lib/i18n/registry";
/** Any registered app language may reach the manifest route — COPY below only
* has translated entries for a subset; getPwaLabels() falls back to English. */
export type PwaLang = AppLanguage;
export const PWA_THEME_COLOR = "#FF107D";
export const PWA_BACKGROUND_COLOR = "#FF107D";
@@ -7,7 +11,22 @@ export const PWA_VERSION = "11";
export { resolvePwaLang, toPwaLang } from "@/lib/i18n/pwaLang";
const COPY = {
const COPY: Partial<Record<AppLanguage, {
name: string;
short_name: string;
description: string;
lang: string;
dir: "rtl" | "ltr";
installTitle: string;
installDescription: string;
installButton: string;
installing: string;
later: string;
gotIt: string;
ok: string;
close: string;
iosSteps: string[];
}>> = {
fa: {
name: "مدستاگرام",
short_name: "مدستاگرام",
@@ -52,19 +71,21 @@ const COPY = {
},
};
/** Falls back to English copy for languages without translated PWA install-prompt text. */
export function getPwaLabels(lang: PwaLang) {
return COPY[lang];
return COPY[lang] ?? COPY.en!;
}
export function buildWebManifest(lang: PwaLang) {
const labels = getPwaLabels(lang);
const direction = getDirection(lang);
return {
name: labels.name,
short_name: labels.short_name,
description: labels.description,
lang: labels.lang,
dir: labels.dir,
lang,
dir: direction,
scope: "/",
start_url: `/?source=pwa&lang=${lang}`,
id: PWA_ID,

View File

@@ -241,17 +241,9 @@ export async function countCoursesPages(): Promise<number> {
}
export async function countAcademiesPages(): Promise<number> {
const data = await sitemapFetchJson<{
data?: {
pagination?: { totalPages?: number; totalItems?: number };
academies?: unknown[];
};
success?: boolean;
}>(`/academy/academies?page=1&limit=${SITEMAP_PAGE_SIZE}`);
const pagination = data?.data?.pagination;
if (pagination?.totalPages) return clampPages(pagination.totalPages);
if (pagination?.totalItems) return pagesFromTotal(pagination.totalItems);
return data?.data?.academies?.length ? 1 : 1;
// نکته: /academy/academies فقط ادمین است — تعداد صفحات از دوره‌های عمومی
// (همان منبعی که buildAcademiesPage شناسه آموزشگاه‌ها را ازش استخراج می‌کند) محاسبه می‌شود.
return countCoursesPages();
}
/* ─── page builders ─── */
@@ -383,30 +375,8 @@ export async function buildCoursesPage(page: number): Promise<SitemapEntry[]> {
export async function buildAcademiesPage(
page: number
): Promise<SitemapEntry[]> {
const data = await sitemapFetchJson<{
data?: {
academies?: Array<{
_id: string;
updatedAt?: string;
createdAt?: string;
}>;
};
}>(`/academy/academies?page=${page}&limit=${SITEMAP_PAGE_SIZE}`);
const academies = data?.data?.academies || [];
if (academies.length) {
return academies
.filter((a) => a?._id)
.map((a) =>
entry(`/academy/profile/${a._id}`, {
changeFrequency: "weekly",
priority: 0.75,
lastModified: a.updatedAt || a.createdAt || new Date(),
})
);
}
// fallback: استخراج academyId از پکیج‌ها
// نکته: /academy/academies فقط ادمین است (401 برای کاربر عمومی) —
// شناسه آموزشگاه‌ها از دوره‌های عمومی (endpoint عمومی) استخراج می‌شود.
const coursesData = await sitemapFetchJson<{
data?: {
courses?: Array<{ academyId?: string; updatedAt?: string }>;