You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
6.2 KiB
163 lines
6.2 KiB
import React from 'react'
|
|
import { Ic } from '@/icons'
|
|
import {
|
|
SERVICE_LANGUAGES,
|
|
type ServiceLanguageCode,
|
|
type ServiceLanguageMeta,
|
|
type ServiceTranslationProps,
|
|
} from './types'
|
|
|
|
export function ServiceTranslation({
|
|
activeLang,
|
|
onLangChange,
|
|
completedLangs,
|
|
dirtyLangs,
|
|
primaryLang,
|
|
title = 'زبان ترجمه و محتوا',
|
|
showDirectionBadge = true,
|
|
showGlobeIcon = true,
|
|
required = false,
|
|
className = '',
|
|
variant = 'grid',
|
|
children,
|
|
}: ServiceTranslationProps) {
|
|
const currentLang =
|
|
SERVICE_LANGUAGES.find((l) => l.code === activeLang) || SERVICE_LANGUAGES[0]
|
|
|
|
const isCompleted = (code: ServiceLanguageCode): boolean => {
|
|
if (!completedLangs) return false
|
|
if (Array.isArray(completedLangs)) {
|
|
return completedLangs.includes(code)
|
|
}
|
|
return Boolean(completedLangs[code])
|
|
}
|
|
|
|
const isDirty = (code: ServiceLanguageCode): boolean => {
|
|
if (!dirtyLangs) return false
|
|
if (Array.isArray(dirtyLangs)) {
|
|
return dirtyLangs.includes(code)
|
|
}
|
|
return Boolean(dirtyLangs[code])
|
|
}
|
|
|
|
return (
|
|
<div className={`space-y-3 ${className}`}>
|
|
{/* هدر انتخاب زبان و نمایش دایرکشن */}
|
|
<div className="flex items-center justify-between flex-wrap gap-2">
|
|
<label className="text-xs font-bold text-foreground flex items-center gap-1.5">
|
|
{showGlobeIcon && <Ic name="globe" className="size-3.5 text-primary" />}
|
|
<span>{title}</span>
|
|
{required && <span className="text-rose-500">*</span>}
|
|
</label>
|
|
|
|
{showDirectionBadge && (
|
|
<div className="flex items-center gap-1.5 text-[11px] text-muted-foreground font-mono">
|
|
<span>جهت متن:</span>
|
|
<span
|
|
className={`px-1.5 py-0.5 rounded-md font-bold text-[10px] uppercase border ${
|
|
currentLang.dir === 'rtl'
|
|
? 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20'
|
|
: 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20'
|
|
}`}
|
|
>
|
|
{currentLang.dir.toUpperCase()}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* تبهای انتخاب زبان */}
|
|
{variant === 'pills' ? (
|
|
<div className="flex items-center gap-1.5 overflow-x-auto no-scrollbar p-1 rounded-xl bg-surface-base border border-border-soft">
|
|
{SERVICE_LANGUAGES.map((lang) => {
|
|
const isSelected = activeLang === lang.code
|
|
const hasData = isCompleted(lang.code)
|
|
const dirty = isDirty(lang.code)
|
|
const isBase = primaryLang ? primaryLang === lang.code : lang.code === 'ar'
|
|
|
|
return (
|
|
<button
|
|
key={lang.code}
|
|
type="button"
|
|
onClick={() => onLangChange(lang.code)}
|
|
className={`relative px-3 py-1.5 text-xs rounded-lg transition-colors font-medium flex items-center gap-1.5 shrink-0 cursor-pointer ${
|
|
isSelected
|
|
? 'bg-primary text-white shadow-xs font-bold'
|
|
: hasData
|
|
? 'bg-card text-foreground hover:bg-card/80 border border-border-soft'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-card/50'
|
|
}`}
|
|
>
|
|
<span className="text-sm leading-none">{lang.flag}</span>
|
|
<span>{lang.name}</span>
|
|
{isBase && !isSelected && (
|
|
<span className="text-[9px] px-1 rounded bg-muted text-muted-foreground font-normal">
|
|
پیشفرض
|
|
</span>
|
|
)}
|
|
{hasData && (
|
|
<span
|
|
className={`size-1.5 rounded-full shrink-0 ${
|
|
isSelected ? 'bg-white' : 'bg-emerald-500'
|
|
}`}
|
|
/>
|
|
)}
|
|
{dirty && (
|
|
<span className="size-1.5 rounded-full bg-amber-400 animate-pulse shrink-0" />
|
|
)}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-2 sm:grid-cols-5 gap-2 p-1.5 rounded-2xl bg-surface-base/80 border border-border-soft">
|
|
{SERVICE_LANGUAGES.map((lang) => {
|
|
const isSelected = activeLang === lang.code
|
|
const hasData = isCompleted(lang.code)
|
|
const dirty = isDirty(lang.code)
|
|
const isBase = primaryLang ? primaryLang === lang.code : lang.code === 'ar'
|
|
|
|
return (
|
|
<button
|
|
key={lang.code}
|
|
type="button"
|
|
onClick={() => onLangChange(lang.code)}
|
|
className={`h-11 px-3 rounded-xl text-xs font-bold transition flex items-center justify-center gap-2 relative cursor-pointer ${
|
|
isSelected
|
|
? 'bg-primary text-primary-foreground shadow-xs'
|
|
: 'text-muted-foreground hover:text-foreground hover:bg-card border border-transparent hover:border-border-soft'
|
|
}`}
|
|
>
|
|
<span className="text-base">{lang.flag}</span>
|
|
<span className="truncate">{lang.name}</span>
|
|
{isBase && (
|
|
<span
|
|
className={`text-[9px] px-1 py-0.2 rounded font-normal shrink-0 ${
|
|
isSelected ? 'bg-white/20 text-white' : 'bg-muted text-muted-foreground'
|
|
}`}
|
|
>
|
|
پایه
|
|
</span>
|
|
)}
|
|
{hasData && (
|
|
<span
|
|
className={`size-2 rounded-full shrink-0 ${
|
|
isSelected ? 'bg-white' : 'bg-emerald-500'
|
|
}`}
|
|
title="دارای محتوای ترجمهشده"
|
|
/>
|
|
)}
|
|
{dirty && (
|
|
<span className="size-2 rounded-full bg-amber-400 animate-pulse shrink-0" />
|
|
)}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{/* اگر کامپوننت حاوی فرزند بود، اینجا قرار میگیرد */}
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|