**Motivations:** - total_sponsoring should be calculated from cache, not stored in tags - Author searches should use cache first, not query Nostr directly - All elements should be read/written from/to database - Background sync should scan all notes with service='zapwall.fr' tag and update cache - Sync should run at startup and resume on each page navigation **Root causes:** - total_sponsoring was stored in tags which required updates on every sponsoring payment - Author queries were querying Nostr directly instead of using cache - No background sync service to keep cache up to date with new notes **Correctifs:** - Removed totalSponsoring from AuthorTags interface and buildAuthorTags - Modified getAuthorSponsoring to calculate from cache (sponsoring queries) instead of tags - Modified parsePresentationEvent to set totalSponsoring to 0 (calculated on demand from cache) - Modified fetchAuthorByHashId and fetchAuthorPresentationFromPool to use cache first and calculate totalSponsoring from cache - Created platformSyncService that scans all notes with service='zapwall.fr' tag and caches them - Modified _app.tsx to start continuous sync on mount and resume on page navigation - All author presentations now calculate totalSponsoring from cache when loaded **Evolutions:** - Cache-first architecture: all queries check cache before querying Nostr - Background sync service keeps cache up to date automatically - totalSponsoring is always calculated from actual sponsoring data in cache - Better performance: cache queries are faster than Nostr queries - Non-blocking sync: background sync doesn't block UI **Pages affectées:** - lib/nostrTagSystemTypes.ts - lib/nostrTagSystemBuild.ts - lib/articlePublisherHelpersPresentation.ts - lib/sponsoring.ts - lib/authorQueries.ts - lib/platformSync.ts (new) - pages/_app.tsx
88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { setLocale, getLocale, type Locale } from '@/lib/i18n'
|
|
import { t } from '@/lib/i18n'
|
|
|
|
const LOCALE_STORAGE_KEY = 'zapwall-locale'
|
|
|
|
interface LocaleOptionProps {
|
|
locale: Locale
|
|
label: string
|
|
currentLocale: Locale
|
|
onClick: (locale: Locale) => void
|
|
}
|
|
|
|
function LocaleOption({ locale, label, currentLocale, onClick }: LocaleOptionProps): React.ReactElement {
|
|
const isActive = currentLocale === locale
|
|
return (
|
|
<button
|
|
onClick={() => onClick(locale)}
|
|
className={`px-4 py-2 rounded-lg font-medium transition-colors border ${
|
|
isActive
|
|
? 'bg-neon-cyan/20 text-neon-cyan border-neon-cyan/50'
|
|
: 'bg-cyber-darker text-cyber-accent hover:text-neon-cyan border-neon-cyan/30 hover:border-neon-cyan/50'
|
|
}`}
|
|
>
|
|
{label}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export function LanguageSettingsManager(): React.ReactElement {
|
|
const [currentLocale, setCurrentLocale] = useState<Locale>(getLocale())
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => {
|
|
const loadLocale = (): void => {
|
|
try {
|
|
if (typeof window === 'undefined') {
|
|
setLoading(false)
|
|
return
|
|
}
|
|
const savedLocale = localStorage.getItem(LOCALE_STORAGE_KEY) as Locale | null
|
|
if (savedLocale && (savedLocale === 'fr' || savedLocale === 'en')) {
|
|
setLocale(savedLocale)
|
|
setCurrentLocale(savedLocale)
|
|
}
|
|
} catch (e) {
|
|
console.error('Error loading locale:', e)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
loadLocale()
|
|
}, [])
|
|
|
|
const handleLocaleChange = (locale: Locale): void => {
|
|
setLocale(locale)
|
|
setCurrentLocale(locale)
|
|
try {
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.setItem(LOCALE_STORAGE_KEY, locale)
|
|
}
|
|
} catch (e) {
|
|
console.error('Error saving locale:', e)
|
|
}
|
|
// Force page reload to update all translations
|
|
window.location.reload()
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-6">
|
|
<div>{t('settings.language.loading')}</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-6">
|
|
<h2 className="text-2xl font-bold text-neon-cyan mb-4">{t('settings.language.title')}</h2>
|
|
<p className="text-cyber-accent mb-4 text-sm">{t('settings.language.description')}</p>
|
|
<div className="flex items-center gap-3">
|
|
<LocaleOption locale="fr" label={t('settings.language.french')} currentLocale={currentLocale} onClick={handleLocaleChange} />
|
|
<LocaleOption locale="en" label={t('settings.language.english')} currentLocale={currentLocale} onClick={handleLocaleChange} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|