mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
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:
70
src/components/admin/LocalizedInput.tsx
Normal file
70
src/components/admin/LocalizedInput.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { LOCALE_META, type LocaleDict, type SupportedLocale } from '../../api/landings';
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
interface LocalizedInputProps {
|
||||
value: LocaleDict;
|
||||
onChange: (value: LocaleDict) => void;
|
||||
locale: SupportedLocale;
|
||||
placeholder?: string;
|
||||
multiline?: boolean;
|
||||
rows?: number;
|
||||
maxLength?: number;
|
||||
id?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An input (or textarea) that edits a single locale key within a LocaleDict.
|
||||
* Shows `value[locale]` and updates the dict immutably on change.
|
||||
*/
|
||||
export function LocalizedInput({
|
||||
value,
|
||||
onChange,
|
||||
locale,
|
||||
placeholder,
|
||||
multiline = false,
|
||||
rows = 2,
|
||||
maxLength,
|
||||
id,
|
||||
className,
|
||||
}: LocalizedInputProps) {
|
||||
const currentValue = value[locale] ?? '';
|
||||
const isRtl = LOCALE_META[locale].rtl;
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
onChange({ ...value, [locale]: newText });
|
||||
};
|
||||
|
||||
const inputClasses = cn(
|
||||
'w-full rounded-lg border border-dark-700 bg-dark-800 px-3 py-2 text-sm text-dark-100 outline-none focus:border-accent-500',
|
||||
className,
|
||||
);
|
||||
|
||||
if (multiline) {
|
||||
return (
|
||||
<textarea
|
||||
id={id}
|
||||
dir={isRtl ? 'rtl' : 'ltr'}
|
||||
value={currentValue}
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
rows={rows}
|
||||
maxLength={maxLength}
|
||||
className={inputClasses}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
dir={isRtl ? 'rtl' : 'ltr'}
|
||||
value={currentValue}
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
maxLength={maxLength}
|
||||
className={inputClasses}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user