lang full
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }>;
|
||||
|
||||
Reference in New Issue
Block a user