Enhance farm dashboard localization by adding Persian translations for various UI elements, including subheaders, option menus, labels, and fallback text. Refactor components to utilize the new translations, improving the overall user experience for Persian-speaking users.

This commit is contained in:
2026-02-19 18:17:16 +03:30
parent 25344a8738
commit 7377f471e9
15 changed files with 146 additions and 44 deletions
+83
View File
@@ -170,6 +170,89 @@
"soilHeatmap": "نقشه حرارتی رطوبت خاک",
"ndviRecommendations": "NDVI و توصیه‌ها",
"economic": "خلاصه اقتصادی"
},
"subheaders": {
"requiresAttention": "نیاز به توجه",
"explainableRecommendations": "توصیه‌های قابل توضیح",
"costsAndRoi": "هزینه‌ها و بازگشت سرمایه",
"actionItems": "اقدامات پیشنهادی",
"vegetationIndex": "شاخص پوشش گیاهی",
"fieldZonesByTime": "نواحی مزرعه بر اساس زمان",
"aiForecast": "پیش‌بینی هوش مصنوعی",
"thisYearVsLastYear": "امسال در مقایسه با سال قبل",
"aiEstimatedDate": "تاریخ تخمینی هوش مصنوعی",
"outOfRangeValues": "مقادیر خارج از محدوده",
"todayVsLastWeek": "امروز در مقایسه با هفته گذشته",
"todayVsIdealRanges": "امروز در مقایسه با محدوده ایده‌آل",
"realtimeData": "داده لحظه‌ای"
},
"optionMenu": {
"refresh": "بروزرسانی",
"sevenDayForecast": "پیش‌بینی ۷ روزه",
"details": "جزئیات",
"viewAll": "مشاهده همه",
"dismiss": "رد کردن",
"configure": "پیکربندی",
"export": "خروجی",
"exportPdf": "خروجی PDF",
"exportExcel": "خروجی اکسل",
"snooze": "تعویق",
"markDone": "علامت تکمیل",
"adjust": "تنظیم",
"compare": "مقایسه",
"today": "امروز",
"thisWeek": "این هفته",
"thisMonth": "این ماه",
"lastHour": "۱ ساعت اخیر",
"last24h": "۲۴ ساعت اخیر",
"last7Days": "۷ روز اخیر",
"settings": "تنظیمات"
},
"labels": {
"humid": "رطوبت",
"wind": "باد",
"totalAlerts": "کل هشدارها",
"activeAlerts": "هشدارهای فعال",
"ndvi": "NDVI",
"ndviIndex": "شاخص NDVI (۰-۱)",
"totalNext7Days": "مجموع ۷ روز آینده",
"value": "مقدار",
"expected": "انتظاری",
"deviation": "انحراف",
"days": "روز",
"tons": "تن"
},
"colorScale": {
"low": "کم",
"moderate": "متوسط",
"optimal": "بهینه",
"high": "زیاد"
},
"empty": {
"noAnomalies": "ناهنجاریی یافت نشد. همه سنسورها در محدوده بهینه هستند."
},
"fallback": {
"dayN": "روز {n}",
"monthJan": "ژانویه",
"monthFeb": "فوریه",
"monthMar": "مارس",
"monthApr": "آوریل",
"monthMay": "مه",
"monthJun": "ژوئن",
"monthJul": "ژوئیه",
"monthAug": "اوت",
"monthSep": "سپتامبر",
"monthOct": "اکتبر",
"monthNov": "نوامبر",
"monthDec": "دسامبر",
"plusPercentVsLastWeek": "+{val}% نسبت به هفته گذشته",
"mon": "دوشنبه",
"tue": "سه‌شنبه",
"wed": "چهارشنبه",
"thu": "پنج‌شنبه",
"fri": "جمعه",
"sat": "شنبه",
"sun": "یکشنبه"
}
},
"sensorHub": {
@@ -34,14 +34,14 @@ const AnomalyDetectionCard = ({ data }: AnomalyDetectionCardProps) => {
<CardHeader
avatar={<i className='tabler-alert-triangle text-xl' />}
title={t('cards.anomalyDetectionCard')}
subheader='Out of range values'
action={<OptionMenu options={['View All', 'Configure', 'Export']} />}
subheader={t('subheaders.outOfRangeValues')}
action={<OptionMenu options={[t('optionMenu.viewAll'), t('optionMenu.configure'), t('optionMenu.export')]} />}
sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
/>
<CardContent className='flex flex-col gap-3'>
{anomalies.length === 0 ? (
<Typography variant='body2' color='text.secondary'>
No anomalies detected. All sensors within optimal range.
{t('empty.noAnomalies')}
</Typography>
) : (
anomalies.map((item, index) => (
@@ -58,7 +58,7 @@ const AnomalyDetectionCard = ({ data }: AnomalyDetectionCardProps) => {
/>
</div>
<Typography variant='caption' color='text.secondary'>
Value: {item.value} (Expected: {item.expected}) · Deviation: {item.deviation}
{t('labels.value')}: {item.value} ({t('labels.expected')}: {item.expected}) · {t('labels.deviation')}: {item.deviation}
</Typography>
</div>
))
@@ -39,7 +39,9 @@ const EconomicOverview = ({ data }: EconomicOverviewProps) => {
const t = useTranslations('farmDashboard')
const economicData = (data?.economicData as EconomicItem[] | undefined) ?? []
const chartSeries = (data?.chartSeries as Array<{ name: string; data: number[] }>) ?? []
const chartCategories = (data?.chartCategories as string[]) ?? ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
const chartCategories =
(data?.chartCategories as string[]) ??
[t('fallback.monthJan'), t('fallback.monthFeb'), t('fallback.monthMar'), t('fallback.monthApr'), t('fallback.monthMay'), t('fallback.monthJun')]
const theme = useTheme()
const options: ApexOptions = {
@@ -77,8 +79,8 @@ const EconomicOverview = ({ data }: EconomicOverviewProps) => {
<Card>
<CardHeader
title={t('cards.economicOverview')}
subheader='Costs & ROI'
action={<OptionMenu options={['Export PDF', 'Export Excel', 'Details']} />}
subheader={t('subheaders.costsAndRoi')}
action={<OptionMenu options={[t('optionMenu.exportPdf'), t('optionMenu.exportExcel'), t('optionMenu.details')]} />}
/>
<CardContent className='flex flex-col gap-4'>
{economicData.length > 0 && (
@@ -51,8 +51,8 @@ const FarmAlertsTimeline = ({ data }: FarmAlertsTimelineProps) => {
avatar={<i className='tabler-bell-ring text-xl' />}
title={t('cards.farmAlertsTimeline')}
titleTypographyProps={{ variant: 'h5' }}
subheader='Explainable recommendations'
action={<OptionMenu options={['View All', 'Configure', 'Export']} />}
subheader={t('subheaders.explainableRecommendations')}
action={<OptionMenu options={[t('optionMenu.viewAll'), t('optionMenu.configure'), t('optionMenu.export')]} />}
sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
/>
<CardContent className='flex flex-col gap-6 pbe-5'>
@@ -46,7 +46,7 @@ const FarmAlertsTracker = ({ data }: FarmAlertsTrackerProps) => {
const options: ApexOptions = {
stroke: { dashArray: 10 },
labels: ['Active Alerts'],
labels: [t('labels.activeAlerts')],
colors: ['var(--mui-palette-warning-main)'],
states: {
hover: { filter: { type: 'none' } },
@@ -97,14 +97,14 @@ const FarmAlertsTracker = ({ data }: FarmAlertsTrackerProps) => {
<Card>
<CardHeader
title={t('cards.farmAlertsTracker')}
subheader='Requires Attention'
action={<OptionMenu options={['View All', 'Dismiss', 'Settings']} />}
subheader={t('subheaders.requiresAttention')}
action={<OptionMenu options={[t('optionMenu.viewAll'), t('optionMenu.dismiss'), t('optionMenu.settings')]} />}
/>
<CardContent className='flex flex-col sm:flex-row items-center justify-between gap-7'>
<div className='flex flex-col gap-6 is-full sm:is-[unset]'>
<div className='flex flex-col'>
<Typography variant='h2'>{totalAlerts}</Typography>
<Typography>Total Alerts</Typography>
<Typography>{t('labels.totalAlerts')}</Typography>
</div>
<div className='flex flex-col gap-4 is-full'>
{alertStats.map((item, index) => (
@@ -91,7 +91,7 @@ const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => {
title={t('cards.farmWeatherCard')}
subheader={condition ? `${condition}, ${temperature}${unit}` : `${temperature}${unit}`}
className='pbe-3'
action={<OptionMenu options={['Refresh', '7-day forecast', 'Details']} />}
action={<OptionMenu options={[t('optionMenu.refresh'), t('optionMenu.sevenDayForecast'), t('optionMenu.details')]} />}
/>
<CardContent>
<div className='flex items-center gap-2 mbe-2'>
@@ -101,7 +101,7 @@ const FarmWeatherCard = ({ data }: FarmWeatherCardProps) => {
{unit}
</Typography>
<Typography color='text.disabled' variant='body2'>
Humid: {humidity}% | Wind: {windSpeed} {windUnit}
{t('labels.humid')}: {humidity}% | {t('labels.wind')}: {windSpeed} {windUnit}
</Typography>
</div>
</CardContent>
@@ -22,7 +22,7 @@ const HarvestPredictionCard = ({ data }: HarvestPredictionCardProps) => {
const t = useTranslations('farmDashboard')
const harvestDate = (data?.dateFormatted as string) ?? ''
const daysUntil = (data?.daysUntil as number | undefined) ?? 0
const daysLeftFormatted = daysUntil > 0 ? `${daysUntil} days` : ''
const daysLeftFormatted = daysUntil > 0 ? `${daysUntil} ${t('labels.days')}` : ''
const description = (data?.description as string) ?? ''
return (
@@ -34,8 +34,8 @@ const HarvestPredictionCard = ({ data }: HarvestPredictionCardProps) => {
</CustomAvatar>
}
title={t('cards.harvestPredictionCard')}
subheader='AI Estimated Date'
action={<OptionMenu options={['Details', 'Adjust', 'Export']} />}
subheader={t('subheaders.aiEstimatedDate')}
action={<OptionMenu options={[t('optionMenu.details'), t('optionMenu.adjust'), t('optionMenu.export')]} />}
/>
<CardContent className='flex flex-col gap-4'>
<div className='flex items-center gap-4'>
+3 -3
View File
@@ -37,7 +37,7 @@ const NDVIHealthCard = ({ data }: NDVIHealthCardProps) => {
const options: ApexOptions = {
stroke: { dashArray: 10 },
labels: ['NDVI'],
labels: [t('labels.ndvi')],
colors: [successColor],
states: {
hover: { filter: { type: 'none' } },
@@ -88,13 +88,13 @@ const NDVIHealthCard = ({ data }: NDVIHealthCardProps) => {
<CardHeader
avatar={<i className='tabler-chart-radar text-xl' />}
title={t('cards.ndviHealthCard')}
subheader='Vegetation Index'
subheader={t('subheaders.vegetationIndex')}
sx={{ '& .MuiCardHeader-avatar': { mr: 3 } }}
/>
<CardContent className='flex flex-col sm:flex-row items-center justify-between gap-6'>
<div className='flex flex-col gap-4 is-full sm:is-[unset]'>
<Typography variant='h2'>{ndviIndex}</Typography>
<Typography variant='body2'>NDVI Index (0-1)</Typography>
<Typography variant='body2'>{t('labels.ndviIndex')}</Typography>
{healthData.length > 0 && (
<div className='flex flex-col gap-3'>
{healthData.map((item, index) => (
@@ -36,8 +36,8 @@ const RecommendationsList = ({ data }: RecommendationsListProps) => {
<Card>
<CardHeader
title={t('cards.recommendationsList')}
subheader='Action Items'
action={<OptionMenu options={['Export', 'Snooze', 'Mark Done']} />}
subheader={t('subheaders.actionItems')}
action={<OptionMenu options={[t('optionMenu.export'), t('optionMenu.snooze'), t('optionMenu.markDone')]} />}
/>
<CardContent className='flex flex-col gap-4'>
{recommendations.map((item, index) => (
@@ -24,9 +24,11 @@ interface SensorComparisonChartProps {
const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => {
const t = useTranslations('farmDashboard')
const series = (data?.series as Array<{ name: string; data: number[] }>) ?? []
const categories = (data?.categories as string[]) ?? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
const categories =
(data?.categories as string[]) ??
[t('fallback.mon'), t('fallback.tue'), t('fallback.wed'), t('fallback.thu'), t('fallback.fri'), t('fallback.sat'), t('fallback.sun')]
const currentValue = data?.currentValue ?? 48
const vsLastWeek = (data?.vsLastWeek as string) ?? '+5% vs last week'
const vsLastWeek = (data?.vsLastWeek as string) ?? t('fallback.plusPercentVsLastWeek', { val: '5' })
const theme = useTheme()
if (series.length === 0) return null
@@ -78,7 +80,7 @@ const SensorComparisonChart = ({ data }: SensorComparisonChartProps) => {
<Card>
<CardHeader
title={t('cards.sensorComparisonChart')}
subheader='Today vs Last Week'
subheader={t('subheaders.todayVsLastWeek')}
/>
<CardContent>
<div className='flex items-center gap-4 mbe-4'>
@@ -73,8 +73,8 @@ const SensorRadarChart = ({ data }: SensorRadarChartProps) => {
<Card>
<CardHeader
title={t('cards.sensorRadarChart')}
subheader='Today vs Ideal Ranges'
action={<OptionMenu options={['Today', 'This Week', 'This Month']} />}
subheader={t('subheaders.todayVsIdealRanges')}
action={<OptionMenu options={[t('optionMenu.today'), t('optionMenu.thisWeek'), t('optionMenu.thisMonth')]} />}
/>
<CardContent>
<AppReactApexCharts type='radar' height={373} width='100%' series={series} options={options} />
@@ -36,8 +36,8 @@ const SensorValuesList = ({ data }: SensorValuesListProps) => {
<Card>
<CardHeader
title={t('cards.sensorValuesList')}
subheader='Real-time Data'
action={<OptionMenu options={['Last Hour', 'Last 24h', 'Last 7 Days']} />}
subheader={t('subheaders.realtimeData')}
action={<OptionMenu options={[t('optionMenu.lastHour'), t('optionMenu.last24h'), t('optionMenu.last7Days')]} />}
/>
<CardContent className='flex flex-col gap-4'>
{sensors.map((item, index) => (
@@ -52,10 +52,10 @@ const SoilMoistureHeatmap = ({ data }: SoilMoistureHeatmapProps) => {
enableShades: false,
colorScale: {
ranges: [
{ from: 0, to: 30, name: 'Low', color: '#ff6b6b' },
{ from: 31, to: 50, name: 'Moderate', color: '#ffd93d' },
{ from: 51, to: 70, name: 'Optimal', color: '#6bcb77' },
{ from: 71, to: 100, name: 'High', color: '#4d96ff' }
{ from: 0, to: 30, name: t('colorScale.low'), color: '#ff6b6b' },
{ from: 31, to: 50, name: t('colorScale.moderate'), color: '#ffd93d' },
{ from: 51, to: 70, name: t('colorScale.optimal'), color: '#6bcb77' },
{ from: 71, to: 100, name: t('colorScale.high'), color: '#4d96ff' }
]
}
}
@@ -75,7 +75,7 @@ const SoilMoistureHeatmap = ({ data }: SoilMoistureHeatmapProps) => {
<Card>
<CardHeader
title={t('cards.soilMoistureHeatmap')}
subheader='Field Zones by Time'
subheader={t('subheaders.fieldZonesByTime')}
/>
<CardContent>
<AppReactApexCharts type='heatmap' width='100%' height={350} options={options} series={series} />
@@ -29,7 +29,9 @@ interface WaterNeedPredictionProps {
const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => {
const t = useTranslations('farmDashboard')
const series = (data?.series as Array<{ name: string; data: number[] }>) ?? []
const categories = (data?.categories as string[]) ?? ['Day 1', 'Day 2', 'Day 3', 'Day 4', 'Day 5', 'Day 6', 'Day 7']
const categories =
(data?.categories as string[]) ??
[1, 2, 3, 4, 5, 6, 7].map(n => t('fallback.dayN', { n: String(n) }))
const totalNext7Days = data?.totalNext7Days ?? 0
const unit = (data?.unit as string) ?? 'm³'
const totalFormatted = typeof totalNext7Days === 'number' ? totalNext7Days.toLocaleString() : String(totalNext7Days)
@@ -79,8 +81,8 @@ const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => {
<Card>
<CardHeader
title={t('cards.waterNeedPrediction')}
subheader='AI Forecast'
action={<OptionMenu options={['Export', 'Adjust', 'Details']} />}
subheader={t('subheaders.aiForecast')}
action={<OptionMenu options={[t('optionMenu.export'), t('optionMenu.adjust'), t('optionMenu.details')]} />}
/>
<CardContent>
<div className='flex items-center gap-4 mbe-4'>
@@ -88,7 +90,7 @@ const WaterNeedPrediction = ({ data }: WaterNeedPredictionProps) => {
{totalFormatted} {unit}
</Typography>
<Typography variant='body2' color='text.secondary'>
Total next 7 days
{t('labels.totalNext7Days')}
</Typography>
</div>
<AppReactApexCharts type='area' height={250} width='100%' series={series} options={options} />
@@ -39,7 +39,20 @@ const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => {
const series = (data?.series as Array<{ name: string; data: number[] }>) ?? []
const categories =
(data?.categories as string[]) ??
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
[
t('fallback.monthJan'),
t('fallback.monthFeb'),
t('fallback.monthMar'),
t('fallback.monthApr'),
t('fallback.monthMay'),
t('fallback.monthJun'),
t('fallback.monthJul'),
t('fallback.monthAug'),
t('fallback.monthSep'),
t('fallback.monthOct'),
t('fallback.monthNov'),
t('fallback.monthDec')
]
const summary = (data?.summary as SummaryItem[]) ?? []
const theme = useTheme()
if (series.length === 0) return null
@@ -68,11 +81,11 @@ const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => {
yaxis: {
labels: {
style: { colors: 'var(--mui-palette-text-disabled)' },
formatter: (val: number) => `${val}t`
formatter: (val: number) => `${val} ${t('labels.tons')}`
}
},
tooltip: {
y: { formatter: (val: number) => `${val} tons` }
y: { formatter: (val: number) => `${val} ${t('labels.tons')}` }
}
}
@@ -80,8 +93,8 @@ const YieldPredictionChart = ({ data }: YieldPredictionChartProps) => {
<Card>
<CardHeader
title={t('cards.yieldPredictionChart')}
subheader='This Year vs Last Year'
action={<OptionMenu options={['Export', 'Compare', 'Details']} />}
subheader={t('subheaders.thisYearVsLastYear')}
action={<OptionMenu options={[t('optionMenu.export'), t('optionMenu.compare'), t('optionMenu.details')]} />}
/>
<CardContent className='flex flex-col gap-4'>
<AppReactApexCharts type='line' height={280} width='100%' series={series} options={options} />