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: 'es', key: 'spanish' },
{ 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": "الإيطالية",
"croatian": "الكرواتية",
"chinese": "الصينية (المبسطة)",
"hindi": "الهندية",
"account": "الحساب",
"content_discovery": "المحتوى والاكتشاف",
"appearance": "المظهر",

View file

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

View file

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

View file

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

View file

@ -632,6 +632,7 @@
"italian": "Italien",
"croatian": "Croate",
"chinese": "Chinois (Simplifié)",
"hindi": "Hindi",
"account": "Compte",
"content_discovery": "Contenu et découverte",
"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",
"croatian": "Hrvatski",
"chinese": "Kineski (Pojednostavljeni)",
"hindi": "Hindski",
"account": "Račun",
"content_discovery": "Sadržaj i otkrivanje",
"appearance": "Izgled",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -183,6 +183,15 @@ export const AboutSettingsContent: React.FC<AboutSettingsContentProps> = ({
return (
<>
<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
title={t('settings.items.legal')}
icon="file-text"