diff --git a/components/LanguageSelector.tsx b/components/LanguageSelector.tsx index 122daf4..3ec3031 100644 --- a/components/LanguageSelector.tsx +++ b/components/LanguageSelector.tsx @@ -30,28 +30,30 @@ export function LanguageSelector(): React.ReactElement { const [currentLocale, setCurrentLocale] = useState(getLocale()) useEffect(() => { - // Load saved locale from IndexedDB - const loadLocale = async (): Promise => { + // Load saved locale from localStorage + const loadLocale = (): void => { try { - const { storageService } = await import('@/lib/storage/indexedDB') - const savedLocale = await storageService.get(LOCALE_STORAGE_KEY, 'app_storage') - if (savedLocale && (savedLocale === 'fr' || savedLocale === 'en')) { - setLocale(savedLocale) - setCurrentLocale(savedLocale) + if (typeof window !== 'undefined') { + 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) } } - void loadLocale() + loadLocale() }, []) - const handleLocaleChange = async (locale: Locale): Promise => { + const handleLocaleChange = (locale: Locale): void => { setLocale(locale) setCurrentLocale(locale) try { - const { storageService } = await import('@/lib/storage/indexedDB') - await storageService.set(LOCALE_STORAGE_KEY, locale, 'app_storage') + if (typeof window !== 'undefined') { + localStorage.setItem(LOCALE_STORAGE_KEY, locale) + } } catch (e) { console.error('Error saving locale:', e) } diff --git a/components/LanguageSettingsManager.tsx b/components/LanguageSettingsManager.tsx new file mode 100644 index 0000000..44525bb --- /dev/null +++ b/components/LanguageSettingsManager.tsx @@ -0,0 +1,88 @@ +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 ( + + ) +} + +export function LanguageSettingsManager(): React.ReactElement { + const [currentLocale, setCurrentLocale] = useState(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 ( +
+
{t('settings.language.loading')}
+
+ ) + } + + return ( +
+

{t('settings.language.title')}

+

{t('settings.language.description')}

+
+ + +
+
+ ) +} + diff --git a/hooks/useI18n.ts b/hooks/useI18n.ts index 9896d06..fe27f80 100644 --- a/hooks/useI18n.ts +++ b/hooks/useI18n.ts @@ -12,11 +12,12 @@ export function useI18n(locale: Locale = 'fr'): { useEffect(() => { const load = async (): Promise => { try { - // Get saved locale from IndexedDB or use provided locale + // Get saved locale from localStorage or use provided locale let savedLocale: Locale | null = null try { - const { storageService } = await import('@/lib/storage/indexedDB') - savedLocale = await storageService.get('zapwall-locale', 'app_storage') + if (typeof window !== 'undefined') { + savedLocale = localStorage.getItem('zapwall-locale') as Locale | null + } } catch { // Fallback to provided locale } diff --git a/lib/nostr.ts b/lib/nostr.ts index d76c574..645bff9 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -219,7 +219,7 @@ class NostrService { } const filters = [{ ids: [eventId], kinds: [1] }] - return subscribeWithTimeout(this.pool, filters, (event: Event) => event, 5000) + return subscribeWithTimeout(this.pool, filters, (event: Event): Event => event, 5000) } getProfile(pubkey: string): Promise { @@ -235,7 +235,7 @@ class NostrService { }, ] - const parseProfile = (event: Event) => { + const parseProfile = (event: Event): NostrProfile | null => { try { const profile = JSON.parse(event.content) as NostrProfile return { ...profile, pubkey } diff --git a/lib/nostrEventParsing.ts b/lib/nostrEventParsing.ts index 10d30c7..7033df2 100644 --- a/lib/nostrEventParsing.ts +++ b/lib/nostrEventParsing.ts @@ -159,7 +159,7 @@ export async function parseReviewFromEvent(event: Event): Promise } -function getPreviewContent(content: string, previewTag?: string) { +function getPreviewContent(content: string, previewTag?: string): { previewContent: string } { const lines = content.split('\n') const previewContent = previewTag ?? lines[0] ?? content.substring(0, 200) return { previewContent } diff --git a/lib/nostrSubscription.ts b/lib/nostrSubscription.ts index 89c906f..74dcb7c 100644 --- a/lib/nostrSubscription.ts +++ b/lib/nostrSubscription.ts @@ -19,14 +19,14 @@ export function subscribeWithTimeout( const sub = createSubscription(pool, [relayUrl], filters) let timeoutId: NodeJS.Timeout | null = null - const cleanup = () => { + const cleanup = (): void => { if (timeoutId) { clearTimeout(timeoutId) } sub.unsub() } - const resolveOnce = (value: T | null) => { + const resolveOnce = (value: T | null): void => { if (resolved.value) { return } @@ -35,11 +35,13 @@ export function subscribeWithTimeout( resolve(value) } - sub.on('event', async (event: Event) => { + sub.on('event', async (event: Event): Promise => { const result = await parser(event) resolveOnce(result) }) - sub.on('eose', () => resolveOnce(null)) + sub.on('eose', (): void => { + resolveOnce(null) + }) timeoutId = setTimeout(() => resolveOnce(null), timeout) }) } diff --git a/lib/nostrTagSystemExtract.ts b/lib/nostrTagSystemExtract.ts index 10d4150..337ae12 100644 --- a/lib/nostrTagSystemExtract.ts +++ b/lib/nostrTagSystemExtract.ts @@ -18,7 +18,30 @@ export function extractTypeAndCategory(event: { tags: string[][] }): { type?: Ta return result } -export function extractCommonTags(findTag: (key: string) => string | undefined, hasTag: (key: string) => boolean) { +export function extractCommonTags(findTag: (key: string) => string | undefined, hasTag: (key: string) => boolean): { + id?: string + service?: string + version: number + hidden: boolean + paywall: boolean + payment: boolean + title?: string + preview?: string + description?: string + mainnetAddress?: string + totalSponsoring?: number + pictureUrl?: string + seriesId?: string + coverUrl?: string + bannerUrl?: string + zapAmount?: number + invoice?: string + paymentHash?: string + encryptedKey?: string + articleId?: string + reviewerPubkey?: string + json?: string +} { return { id: findTag('id'), service: findTag('service'), diff --git a/lib/settingsCache.ts b/lib/settingsCache.ts index a43c393..73785c7 100644 --- a/lib/settingsCache.ts +++ b/lib/settingsCache.ts @@ -127,4 +127,3 @@ class SettingsCacheService { } export const settingsCache = new SettingsCacheService() - diff --git a/locales/en.txt b/locales/en.txt index 3e92b80..2b8fb53 100644 --- a/locales/en.txt +++ b/locales/en.txt @@ -263,3 +263,8 @@ settings.nip95.list.editUrl=Click to edit URL settings.nip95.note.title=Note: settings.nip95.note.priority=Endpoints are tried in priority order (lower number = higher priority). Only enabled endpoints will be used for uploads. settings.nip95.note.fallback=If an endpoint fails, the next enabled endpoint will be tried automatically. +settings.language.title=Preferred Language +settings.language.description=Choose your preferred language for the interface +settings.language.loading=Loading... +settings.language.french=French +settings.language.english=English diff --git a/locales/fr.txt b/locales/fr.txt index e19f152..9838256 100644 --- a/locales/fr.txt +++ b/locales/fr.txt @@ -225,6 +225,11 @@ settings.nip95.list.editUrl=Cliquer pour modifier l'URL settings.nip95.note.title=Note : settings.nip95.note.priority=Les endpoints sont essayés dans l'ordre de priorité (nombre plus bas = priorité plus haute). Seuls les endpoints activés seront utilisés pour les uploads. settings.nip95.note.fallback=Si un endpoint échoue, le prochain endpoint activé sera essayé automatiquement. +settings.language.title=Langue de préférence +settings.language.description=Choisissez votre langue préférée pour l'interface +settings.language.loading=Chargement... +settings.language.french=Français +settings.language.english=Anglais # Account account.create.title=Créer un compte diff --git a/pages/_app.tsx b/pages/_app.tsx index 72e41fb..7669d4d 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -4,14 +4,13 @@ import { useI18n } from '@/hooks/useI18n' import React from 'react' function I18nProvider({ children }: { children: React.ReactNode }) { - // Get saved locale from IndexedDB or default to French - const getInitialLocale = async (): Promise<'fr' | 'en'> => { + // Get saved locale from localStorage or default to French + const getInitialLocale = (): 'fr' | 'en' => { if (typeof window === 'undefined') { return 'fr' } try { - const { storageService } = await import('@/lib/storage/indexedDB') - const savedLocale = await storageService.get<'fr' | 'en'>('zapwall-locale', 'app_storage') + const savedLocale = localStorage.getItem('zapwall-locale') as 'fr' | 'en' | null if (savedLocale === 'fr' || savedLocale === 'en') { return savedLocale } @@ -27,10 +26,9 @@ function I18nProvider({ children }: { children: React.ReactNode }) { const [localeLoaded, setLocaleLoaded] = React.useState(false) React.useEffect(() => { - getInitialLocale().then((locale) => { - setInitialLocale(locale) - setLocaleLoaded(true) - }) + const locale = getInitialLocale() + setInitialLocale(locale) + setLocaleLoaded(true) }, []) const { loaded } = useI18n(initialLocale) diff --git a/pages/author/[pubkey].tsx b/pages/author/[pubkey].tsx index 1c40b99..cd05bdc 100644 --- a/pages/author/[pubkey].tsx +++ b/pages/author/[pubkey].tsx @@ -278,7 +278,7 @@ export default function AuthorPage(): React.ReactElement { if (typeof pubkey === 'string') { // Try to parse as new format first (hash_index_version) const urlMatch = pubkey.match(/^([a-f0-9]+)_(\d+)_(\d+)$/i) - if (urlMatch && urlMatch[1]) { + if (urlMatch?.[1]) { // Extract hash ID from the format hash_index_version hashIdOrPubkey = urlMatch[1] } else { diff --git a/pages/settings.tsx b/pages/settings.tsx index 8145916..98e235e 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -4,6 +4,7 @@ import { Footer } from '@/components/Footer' import { Nip95ConfigManager } from '@/components/Nip95ConfigManager' import { KeyManagementManager } from '@/components/KeyManagementManager' import { CacheUpdateManager } from '@/components/CacheUpdateManager' +import { LanguageSettingsManager } from '@/components/LanguageSettingsManager' import { t } from '@/lib/i18n' export default function SettingsPage(): React.ReactElement { @@ -20,6 +21,7 @@ export default function SettingsPage(): React.ReactElement {

{t('settings.title')}

+