feat: мультиязычные лендинги + переключатель языка + исправления по ревью

Мультиязычность:
- LanguageSwitcher на публичной странице покупки
- Вкладки локалей в админ-редакторе (ru/en/zh/fa)
- LocaleTabs и LocalizedInput компоненты
- Типы: LocaleDict, AdminLandingFeature, toLocaleDict хелпер
- ?lang= параметр при запросе конфига лендинга

Исправления по ревью:
- contentIndicators включает все локализуемые поля
- LocaleTabs вынесен над секциями аккордеона
- text-left → text-start для RTL
- nonEmptyDict: защита от undefined
- featureIds/methodIds обёрнуты в useMemo
- toggleSection/updateFeature* обёрнуты в useCallback
- formatPrice вынесен в shared utils/format.ts
- RTL через LOCALE_META.rtl вместо хардкода
- Переводы zh/fa для валидации и landing
- Variable shadowing: t → tariff
This commit is contained in:
Fringg
2026-03-06 16:11:58 +03:00
parent e01c9f5143
commit ab13616b0f
11 changed files with 375 additions and 122 deletions

View File

@@ -3193,7 +3193,9 @@
"noSystemMethods": "No payment methods configured in the system",
"methodOrder": "Drag to reorder",
"loadingPeriods": "Loading...",
"periodDaySuffix": "d"
"periodDaySuffix": "d",
"localeTab": "Language",
"localeHint": "Switch language to edit text in that language"
}
},
"adminUpdates": {

View File

@@ -2911,15 +2911,17 @@
"content": "محتوا",
"save": "ذخیره",
"back": "بازگشت",
"invalidSlug": "Slug can only contain lowercase letters, numbers, and hyphens",
"titleRequired": "Title is required",
"noTariffs": "Select at least one tariff",
"noPaymentMethods": "Add at least one payment method",
"invalidSlug": "Slug \u0641\u0642\u0637 \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0634\u0627\u0645\u0644 \u062d\u0631\u0648\u0641 \u06a9\u0648\u0686\u06a9\u060c \u0627\u0639\u062f\u0627\u062f \u0648 \u062e\u0637 \u062a\u06cc\u0631\u0647 \u0628\u0627\u0634\u062f",
"titleRequired": "\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0632\u0627\u0645\u06cc \u0627\u0633\u062a",
"noTariffs": "\u062d\u062f\u0627\u0642\u0644 \u06cc\u06a9 \u062a\u0639\u0631\u0641\u0647 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f",
"noPaymentMethods": "\u062d\u062f\u0627\u0642\u0644 \u06cc\u06a9 \u0631\u0648\u0634 \u067e\u0631\u062f\u0627\u062e\u062a \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f",
"selectMethods": "انتخاب روش‌های پرداخت موجود",
"noSystemMethods": "هیچ روش پرداختی در سیستم پیکربندی نشده است",
"methodOrder": "برای تغییر ترتیب بکشید",
"loadingPeriods": "در حال بارگذاری...",
"periodDaySuffix": "روز"
"periodDaySuffix": "روز",
"localeTab": "زبان",
"localeHint": "زبان را تغییر دهید تا متن را به آن زبان ویرایش کنید"
}
},
"adminUpdates": {
@@ -3511,8 +3513,8 @@
"choosePeriod": "انتخاب دوره",
"copied": "کپی شد!",
"copyLink": "کپی لینک",
"giftToggleLabel": "Purchase type",
"pollTimedOut": "Taking longer than expected",
"pollTimedOutDesc": "Payment processing is taking longer than usual. You can try checking again."
"giftToggleLabel": "\u0646\u0648\u0639 \u062e\u0631\u06cc\u062f",
"pollTimedOut": "\u0632\u0645\u0627\u0646 \u0628\u06cc\u0634\u062a\u0631\u06cc \u0637\u0648\u0644 \u06a9\u0634\u06cc\u062f",
"pollTimedOutDesc": "\u067e\u0631\u062f\u0627\u0632\u0634 \u067e\u0631\u062f\u0627\u062e\u062a \u0628\u06cc\u0634\u062a\u0631 \u0627\u0632 \u062d\u062f \u0645\u0639\u0645\u0648\u0644 \u0637\u0648\u0644 \u06a9\u0634\u06cc\u062f\u0647 \u0627\u0633\u062a. \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u06cc\u062f \u062f\u0648\u0628\u0627\u0631\u0647 \u0628\u0631\u0631\u0633\u06cc \u06a9\u0646\u06cc\u062f."
}
}

View File

@@ -3748,7 +3748,9 @@
"noSystemMethods": "В системе не настроено ни одного способа оплаты",
"methodOrder": "Перетащите для изменения порядка",
"loadingPeriods": "Загрузка...",
"periodDaySuffix": "д"
"periodDaySuffix": "д",
"localeTab": "Язык",
"localeHint": "Переключите язык для редактирования текста на этом языке"
}
},
"adminUpdates": {

View File

@@ -2910,15 +2910,17 @@
"content": "内容",
"save": "保存",
"back": "返回",
"invalidSlug": "Slug can only contain lowercase letters, numbers, and hyphens",
"titleRequired": "Title is required",
"noTariffs": "Select at least one tariff",
"noPaymentMethods": "Add at least one payment method",
"invalidSlug": "Slug\u53ea\u80fd\u5305\u542b\u5c0f\u5199\u5b57\u6bcd\u3001\u6570\u5b57\u548c\u8fde\u5b57\u7b26",
"titleRequired": "\u6807\u9898\u4e3a\u5fc5\u586b\u9879",
"noTariffs": "\u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u5957\u9910",
"noPaymentMethods": "\u8bf7\u81f3\u5c11\u6dfb\u52a0\u4e00\u4e2a\u652f\u4ed8\u65b9\u5f0f",
"selectMethods": "选择可用的支付方式",
"noSystemMethods": "系统中未配置任何支付方式",
"methodOrder": "拖动以调整顺序",
"loadingPeriods": "加载中...",
"periodDaySuffix": "天"
"periodDaySuffix": "天",
"localeTab": "语言",
"localeHint": "切换语言以编辑该语言的文本"
}
},
"adminUpdates": {
@@ -3510,8 +3512,8 @@
"choosePeriod": "选择时长",
"copied": "已复制!",
"copyLink": "复制链接",
"giftToggleLabel": "Purchase type",
"pollTimedOut": "Taking longer than expected",
"pollTimedOutDesc": "Payment processing is taking longer than usual. You can try checking again."
"giftToggleLabel": "\u8d2d\u4e70\u7c7b\u578b",
"pollTimedOut": "\u5904\u7406\u65f6\u95f4\u8f83\u957f",
"pollTimedOutDesc": "\u4ed8\u6b3e\u5904\u7406\u65f6\u95f4\u6bd4\u5e73\u65f6\u957f\u3002\u60a8\u53ef\u4ee5\u5c1d\u8bd5\u518d\u6b21\u68c0\u67e5\u3002"
}
}