added Hindi

This commit is contained in:
tapframe 2026-01-25 22:55:22 +05:30
parent 86f6bc4ae2
commit 77f5cb2b80
15 changed files with 1476 additions and 86 deletions

View file

@ -8,5 +8,6 @@ export const LOCALES = [
{ code: 'it', key: 'italian' }, { code: 'it', key: 'italian' },
{ code: 'es', key: 'spanish' }, { code: 'es', key: 'spanish' },
{ code: 'hr', key: 'croatian' }, { code: 'hr', key: 'croatian' },
{ code: 'zh-CN', key: 'chinese' } { code: 'zh-CN', key: 'chinese' },
{ code: 'hi', key: 'hindi' }
]; ];

View file

@ -632,6 +632,7 @@
"italian": "الإيطالية", "italian": "الإيطالية",
"croatian": "الكرواتية", "croatian": "الكرواتية",
"chinese": "الصينية (المبسطة)", "chinese": "الصينية (المبسطة)",
"hindi": "الهندية",
"account": "الحساب", "account": "الحساب",
"content_discovery": "المحتوى والاكتشاف", "content_discovery": "المحتوى والاكتشاف",
"appearance": "المظهر", "appearance": "المظهر",

View file

@ -632,6 +632,7 @@
"italian": "Italienisch", "italian": "Italienisch",
"croatian": "Kroatisch", "croatian": "Kroatisch",
"chinese": "Chinesisch (Vereinfacht)", "chinese": "Chinesisch (Vereinfacht)",
"hindi": "Hindi",
"account": "Konto", "account": "Konto",
"content_discovery": "Inhalt & Entdeckung", "content_discovery": "Inhalt & Entdeckung",
"appearance": "Aussehen", "appearance": "Aussehen",

View file

@ -632,6 +632,7 @@
"italian": "Italian", "italian": "Italian",
"croatian": "Croatian", "croatian": "Croatian",
"chinese": "Chinese (Simplified)", "chinese": "Chinese (Simplified)",
"hindi": "Hindi",
"account": "Account", "account": "Account",
"content_discovery": "Content & Discovery", "content_discovery": "Content & Discovery",
"appearance": "Appearance", "appearance": "Appearance",

View file

@ -632,6 +632,7 @@
"italian": "Italiano", "italian": "Italiano",
"croatian": "Croata", "croatian": "Croata",
"chinese": "Chino (Simplificado)", "chinese": "Chino (Simplificado)",
"hindi": "Hindi",
"account": "Cuenta", "account": "Cuenta",
"content_discovery": "Contenido y descubrimiento", "content_discovery": "Contenido y descubrimiento",
"appearance": "Apariencia", "appearance": "Apariencia",

View file

@ -632,6 +632,7 @@
"italian": "Italien", "italian": "Italien",
"croatian": "Croate", "croatian": "Croate",
"chinese": "Chinois (Simplifié)", "chinese": "Chinois (Simplifié)",
"hindi": "Hindi",
"account": "Compte", "account": "Compte",
"content_discovery": "Contenu et découverte", "content_discovery": "Contenu et découverte",
"appearance": "Apparence", "appearance": "Apparence",

1366
src/i18n/locales/hi.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -632,6 +632,7 @@
"italian": "Talijanski", "italian": "Talijanski",
"croatian": "Hrvatski", "croatian": "Hrvatski",
"chinese": "Kineski (Pojednostavljeni)", "chinese": "Kineski (Pojednostavljeni)",
"hindi": "Hindski",
"account": "Račun", "account": "Račun",
"content_discovery": "Sadržaj i otkrivanje", "content_discovery": "Sadržaj i otkrivanje",
"appearance": "Izgled", "appearance": "Izgled",

View file

@ -632,6 +632,7 @@
"italian": "Italiano", "italian": "Italiano",
"croatian": "Croato", "croatian": "Croato",
"chinese": "Cinese (Semplificato)", "chinese": "Cinese (Semplificato)",
"hindi": "Hindi",
"account": "Account", "account": "Account",
"content_discovery": "Contenuti e Scoperta", "content_discovery": "Contenuti e Scoperta",
"appearance": "Aspetto", "appearance": "Aspetto",

View file

@ -646,6 +646,7 @@
"italian": "Italiano", "italian": "Italiano",
"croatian": "Croata", "croatian": "Croata",
"chinese": "Chinês (Simplificado)", "chinese": "Chinês (Simplificado)",
"hindi": "Hindi",
"account": "Conta", "account": "Conta",
"content_discovery": "Conteúdo e Descoberta", "content_discovery": "Conteúdo e Descoberta",
"appearance": "Aparência", "appearance": "Aparência",

View file

@ -646,6 +646,7 @@
"italian": "Italiano", "italian": "Italiano",
"croatian": "Croata", "croatian": "Croata",
"chinese": "Chinês (Simplificado)", "chinese": "Chinês (Simplificado)",
"hindi": "Hindi",
"account": "Conta", "account": "Conta",
"content_discovery": "Conteúdo e Descoberta", "content_discovery": "Conteúdo e Descoberta",
"appearance": "Aparência", "appearance": "Aparência",

View file

@ -632,6 +632,7 @@
"italian": "意大利语", "italian": "意大利语",
"croatian": "克罗地亚语", "croatian": "克罗地亚语",
"chinese": "简体中文", "chinese": "简体中文",
"hindi": "印地语",
"account": "账户", "account": "账户",
"content_discovery": "内容与发现", "content_discovery": "内容与发现",
"appearance": "外观", "appearance": "外观",

View file

@ -8,6 +8,7 @@ import it from './locales/it.json';
import de from './locales/de.json'; import de from './locales/de.json';
import hr from './locales/hr.json'; import hr from './locales/hr.json';
import hi from './locales/hi.json';
import zhCN from './locales/zh-CN.json'; import zhCN from './locales/zh-CN.json';
export const resources = { export const resources = {
@ -21,4 +22,5 @@ export const resources = {
de: { translation: de }, de: { translation: de },
hr: { translation: hr }, hr: { translation: hr },
'zh-CN': { translation: zhCN }, 'zh-CN': { translation: zhCN },
hi: { translation: hi },
}; };

View file

@ -254,7 +254,7 @@ const DonorCard: React.FC<DonorCardProps> = ({ donor, currentTheme, isTablet })
try { try {
const date = new Date(dateString); const date = new Date(dateString);
if (isNaN(date.getTime())) return dateString; if (isNaN(date.getTime())) return dateString;
return date.toLocaleDateString(undefined, { return date.toLocaleDateString(undefined, {
year: 'numeric', year: 'numeric',
month: 'short', month: 'short',
@ -333,7 +333,7 @@ const ContributorsScreen: React.FC = () => {
try { try {
const date = new Date(dateString); const date = new Date(dateString);
if (isNaN(date.getTime())) return dateString; if (isNaN(date.getTime())) return dateString;
// Use locale-aware formatting // Use locale-aware formatting
return date.toLocaleDateString(undefined, { return date.toLocaleDateString(undefined, {
year: 'numeric', year: 'numeric',
@ -382,15 +382,17 @@ const ContributorsScreen: React.FC = () => {
const sorted = Array.from(map.values()).sort((a, b) => b.total - a.total); const sorted = Array.from(map.values()).sort((a, b) => b.total - a.total);
let lastTotal: number | null = null; let lastTotal: number | null = null;
let lastRank = 0; let currentRank = 0;
return sorted.map((entry, index) => { return sorted.map((entry) => {
const rank = lastTotal !== null && entry.total === lastTotal ? lastRank : index + 1; if (lastTotal === null || entry.total !== lastTotal) {
currentRank += 1;
}
lastTotal = entry.total; lastTotal = entry.total;
lastRank = rank;
return { return {
...entry, ...entry,
rank, rank: currentRank,
}; };
}); });
}, [donations, getDonationTs]); }, [donations, getDonationTs]);
@ -803,87 +805,87 @@ const ContributorsScreen: React.FC = () => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{donationsLoading ? ( {donationsLoading ? (
<View style={styles.loadingContainer}> <View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={currentTheme.colors.primary} /> <ActivityIndicator size="large" color={currentTheme.colors.primary} />
<Text style={[styles.loadingText, { color: currentTheme.colors.mediumEmphasis }]}>{t('contributors.loading_donors')}</Text> <Text style={[styles.loadingText, { color: currentTheme.colors.mediumEmphasis }]}>{t('contributors.loading_donors')}</Text>
</View> </View>
) : donationsError ? ( ) : donationsError ? (
<View style={styles.errorContainer}> <View style={styles.errorContainer}>
<Feather name="alert-circle" size={48} color={currentTheme.colors.mediumEmphasis} /> <Feather name="alert-circle" size={48} color={currentTheme.colors.mediumEmphasis} />
<Text style={[styles.errorText, { color: currentTheme.colors.mediumEmphasis }]}> <Text style={[styles.errorText, { color: currentTheme.colors.mediumEmphasis }]}>
{donationsError} {donationsError}
</Text> </Text>
<TouchableOpacity <TouchableOpacity
style={[styles.retryButton, { backgroundColor: currentTheme.colors.primary }]} style={[styles.retryButton, { backgroundColor: currentTheme.colors.primary }]}
onPress={() => loadDonations(true)} onPress={() => loadDonations(true)}
> >
<Text style={[styles.retryText, { color: currentTheme.colors.white }]}>{t('common.retry')}</Text> <Text style={[styles.retryText, { color: currentTheme.colors.white }]}>{t('common.retry')}</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
) : donations.length === 0 ? ( ) : donations.length === 0 ? (
<View style={styles.emptyContainer}> <View style={styles.emptyContainer}>
<Feather name="gift" size={48} color={currentTheme.colors.mediumEmphasis} /> <Feather name="gift" size={48} color={currentTheme.colors.mediumEmphasis} />
<Text style={[styles.emptyText, { color: currentTheme.colors.mediumEmphasis }]}>{t('contributors.no_donors')}</Text> <Text style={[styles.emptyText, { color: currentTheme.colors.mediumEmphasis }]}>{t('contributors.no_donors')}</Text>
</View> </View>
) : (
donorsTab === 'latest' ? (
latestDonations.map((donor, index) => (
<DonorCard
key={`${donor.name}-${donor.date}-${index}`}
donor={donor}
currentTheme={currentTheme}
isTablet={isTablet}
/>
))
) : ( ) : (
donorsTab === 'latest' ? ( leaderboardDonations.map((entry, index) => (
latestDonations.map((donor, index) => ( <View
<DonorCard key={`${entry.name}-${entry.currency}-${index}`}
key={`${donor.name}-${donor.date}-${index}`} style={[
donor={donor} styles.leaderboardCard,
currentTheme={currentTheme} { backgroundColor: currentTheme.colors.elevation1 },
isTablet={isTablet} isTablet && styles.tabletContributorCard
/> ]}
)) >
) : ( <View style={styles.leaderboardAvatar}>
leaderboardDonations.map((entry, index) => ( {getRankAnimation(entry.rank) ? (
<View <View style={styles.leaderboardBadge}>
key={`${entry.name}-${entry.currency}-${index}`}
style={[
styles.leaderboardCard,
{ backgroundColor: currentTheme.colors.elevation1 },
isTablet && styles.tabletContributorCard
]}
>
<View style={styles.leaderboardAvatar}>
{getRankAnimation(entry.rank) ? (
<View style={styles.leaderboardBadge}>
<Text style={[styles.leaderboardRankText, { color: currentTheme.colors.white }]}>{entry.rank}</Text>
<LottieView
source={getRankAnimation(entry.rank)}
autoPlay
loop={false}
style={styles.leaderboardLottie}
/>
</View>
) : (
<Text style={[styles.leaderboardRankText, { color: currentTheme.colors.white }]}>{entry.rank}</Text> <Text style={[styles.leaderboardRankText, { color: currentTheme.colors.white }]}>{entry.rank}</Text>
)} <LottieView
</View> source={getRankAnimation(entry.rank)}
<View style={styles.contributorInfo}> autoPlay
<Text style={[ loop={false}
styles.username, style={styles.leaderboardLottie}
{ color: currentTheme.colors.highEmphasis }, />
isTablet && styles.tabletUsername </View>
]}> ) : (
{entry.name} <Text style={[styles.leaderboardRankText, { color: currentTheme.colors.white }]}>{entry.rank}</Text>
</Text> )}
<Text style={[
styles.donorAmount,
{ color: currentTheme.colors.mediumEmphasis },
isTablet && styles.tabletContributions
]}>
{entry.total.toFixed(2)} {entry.currency} · {entry.count} {entry.count === 1 ? 'donation' : 'donations'}
</Text>
<Text style={[styles.donorMessage, { color: currentTheme.colors.mediumEmphasis }]}>
Rank #{entry.rank} · Last: {formatDonationDate(entry.lastDate)}
</Text>
</View>
</View> </View>
)) <View style={styles.contributorInfo}>
) <Text style={[
)} styles.username,
{ color: currentTheme.colors.highEmphasis },
isTablet && styles.tabletUsername
]}>
{entry.name}
</Text>
<Text style={[
styles.donorAmount,
{ color: currentTheme.colors.mediumEmphasis },
isTablet && styles.tabletContributions
]}>
{entry.total.toFixed(2)} {entry.currency} · {entry.count} {entry.count === 1 ? 'donation' : 'donations'}
</Text>
<Text style={[styles.donorMessage, { color: currentTheme.colors.mediumEmphasis }]}>
Rank #{entry.rank} · Last: {formatDonationDate(entry.lastDate)}
</Text>
</View>
</View>
))
)
)}
</ScrollView> </ScrollView>
) : activeTab === 'contributors' ? ( ) : activeTab === 'contributors' ? (
// Contributors Tab // Contributors Tab

View file

@ -183,6 +183,15 @@ export const AboutSettingsContent: React.FC<AboutSettingsContentProps> = ({
return ( return (
<> <>
<SettingsCard title={t('settings.sections.information')} isTablet={isTablet}> <SettingsCard title={t('settings.sections.information')} isTablet={isTablet}>
{isTablet && (
<SettingItem
title={t('contributors.title', 'Contributors')}
icon="users"
onPress={() => navigation.navigate('Contributors')}
renderControl={() => <ChevronRight />}
isTablet={isTablet}
/>
)}
<SettingItem <SettingItem
title={t('settings.items.legal')} title={t('settings.items.legal')}
icon="file-text" icon="file-text"