Complete English translation for presentation page and update Bitcoin address help text

- Translate all hardcoded French text in presentation form to use i18n
- Update Bitcoin address help text to specify '0.046 BTC excluding fees'
- Add all presentation field translations (labels, placeholders, help texts)
- Add image upload field translations
- Add validation error message translation
- Add fallback user name translation
- All TypeScript checks pass
This commit is contained in:
Nicolas Cantu 2025-12-28 16:15:28 +01:00
parent 58e1ace081
commit f082befb36
3 changed files with 43 additions and 23 deletions

View File

@ -61,14 +61,14 @@ function PresentationField({
return (
<ArticleField
id="presentation"
label="Présentation personnelle"
label={t('presentation.field.presentation')}
value={draft.presentation}
onChange={(value) => onChange({ ...draft, presentation: value as string })}
required
type="textarea"
rows={6}
placeholder="Présentez-vous : qui êtes-vous, votre parcours, vos intérêts..."
helpText="Cette présentation sera visible par tous les lecteurs"
placeholder={t('presentation.field.presentation.placeholder')}
helpText={t('presentation.field.presentation.help')}
/>
)
}
@ -83,14 +83,14 @@ function ContentDescriptionField({
return (
<ArticleField
id="contentDescription"
label="Description de votre contenu"
label={t('presentation.field.contentDescription')}
value={draft.contentDescription}
onChange={(value) => onChange({ ...draft, contentDescription: value as string })}
required
type="textarea"
rows={6}
placeholder="Décrivez le type de contenu que vous publiez : science-fiction, recherche scientifique, thèmes abordés..."
helpText="Aidez les lecteurs à comprendre le type d'articles que vous publiez"
placeholder={t('presentation.field.contentDescription.placeholder')}
helpText={t('presentation.field.contentDescription.help')}
/>
)
}
@ -105,13 +105,13 @@ function MainnetAddressField({
return (
<ArticleField
id="mainnetAddress"
label="Adresse Bitcoin mainnet (pour le sponsoring)"
label={t('presentation.field.mainnetAddress')}
value={draft.mainnetAddress}
onChange={(value) => onChange({ ...draft, mainnetAddress: value as string })}
required
type="text"
placeholder="1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
helpText="Adresse Bitcoin mainnet où vous recevrez les paiements de sponsoring (0.046 BTC par sponsoring)"
placeholder={t('presentation.field.mainnetAddress.placeholder')}
helpText={t('presentation.field.mainnetAddress.help')}
/>
)
}
@ -126,10 +126,8 @@ function PictureField({
return (
<ImageUploadField
id="picture"
label="Photo de profil"
value={draft.pictureUrl}
onChange={(url) => onChange({ ...draft, pictureUrl: url })}
helpText="Image de profil pour votre page auteur (max 5Mo, formats: PNG, JPG, WebP)"
/>
)
}
@ -206,7 +204,7 @@ function useAuthorPresentationState(pubkey: string | null) {
e.preventDefault()
const address = draft.mainnetAddress.trim()
if (!ADDRESS_PATTERN.test(address)) {
setValidationError('Adresse Bitcoin invalide (doit commencer par 1, 3 ou bc1)')
setValidationError(t('presentation.validation.invalidAddress'))
return
}
setValidationError(null)
@ -237,7 +235,7 @@ function AuthorPresentationFormView({
}
// Get user name or fallback to shortened pubkey
const userName = profile?.name ?? (pubkey ? `${pubkey.substring(0, 16)}...` : 'Utilisateur')
const userName = profile?.name ?? (pubkey ? `${pubkey.substring(0, 16)}...` : t('presentation.fallback.user'))
return (
<PresentationForm

View File

@ -1,10 +1,11 @@
import { useState } from 'react'
import { uploadNip95Media } from '@/lib/nip95'
import { t } from '@/lib/i18n'
import Image from 'next/image'
interface ImageUploadFieldProps {
id: string
label: string
label?: string | undefined
value?: string | undefined
onChange: (url: string) => void
helpText?: string | undefined
@ -28,25 +29,28 @@ export function ImageUploadField({ id, label, value, onChange, helpText }: Image
if (media.type === 'image') {
onChange(media.url)
} else {
setError('Seules les images sont autorisées')
setError(t('presentation.field.picture.error.imagesOnly'))
}
} catch (e) {
setError(e instanceof Error ? e.message : 'Erreur lors de l\'upload')
setError(e instanceof Error ? e.message : t('presentation.field.picture.error.uploadFailed'))
} finally {
setUploading(false)
}
}
const displayLabel = label ?? t('presentation.field.picture')
const displayHelpText = helpText ?? t('presentation.field.picture.help')
return (
<div className="space-y-2">
<label htmlFor={id} className="block text-sm font-medium text-neon-cyan">
{label}
{displayLabel}
</label>
{value && (
<div className="relative w-32 h-32 rounded-lg overflow-hidden border border-neon-cyan/20">
<Image
src={value}
alt="Profile picture"
alt={t('presentation.field.picture')}
fill
className="object-cover"
/>
@ -57,7 +61,7 @@ export function ImageUploadField({ id, label, value, onChange, helpText }: Image
htmlFor={id}
className="px-4 py-2 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded-lg text-sm font-medium transition-all border border-neon-cyan/50 hover:shadow-glow-cyan cursor-pointer"
>
{uploading ? 'Upload en cours...' : value ? 'Changer l\'image' : 'Télécharger une image'}
{uploading ? t('presentation.field.picture.uploading') : value ? t('presentation.field.picture.change') : t('presentation.field.picture.upload')}
</label>
<input
id={id}
@ -73,17 +77,16 @@ export function ImageUploadField({ id, label, value, onChange, helpText }: Image
onClick={() => onChange('')}
className="px-4 py-2 bg-red-500/20 hover:bg-red-500/30 text-red-400 rounded-lg text-sm font-medium transition-all border border-red-500/50"
>
Supprimer
{t('presentation.field.picture.remove')}
</button>
)}
</div>
{error && (
<p className="text-sm text-red-400">{error}</p>
)}
{helpText && (
<p className="text-sm text-cyber-accent">{helpText}</p>
{displayHelpText && (
<p className="text-sm text-cyber-accent">{displayHelpText}</p>
)}
</div>
)
}

View File

@ -61,6 +61,25 @@ presentation.success=Presentation article created!
presentation.successMessage=Your presentation article has been created successfully. You can now publish articles.
presentation.notConnected=Connect with Nostr to create your presentation article
presentation.profileNote=This profile data is specific to zapwall.fr and may differ from your Nostr profile.
presentation.field.picture=Profile picture
presentation.field.picture.help=Profile image for your author page (max 5MB, formats: PNG, JPG, WebP)
presentation.field.picture.change=Change image
presentation.field.picture.upload=Upload an image
presentation.field.picture.uploading=Uploading...
presentation.field.picture.remove=Remove
presentation.field.picture.error.imagesOnly=Only images are allowed
presentation.field.picture.error.uploadFailed=Upload error
presentation.field.presentation=Personal presentation
presentation.field.presentation.placeholder=Introduce yourself: who you are, your background, your interests...
presentation.field.presentation.help=This presentation will be visible to all readers
presentation.field.contentDescription=Content description
presentation.field.contentDescription.placeholder=Describe the type of content you publish: science fiction, scientific research, themes covered...
presentation.field.contentDescription.help=Help readers understand the type of articles you publish
presentation.field.mainnetAddress=Bitcoin mainnet address (for sponsoring)
presentation.field.mainnetAddress.placeholder=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
presentation.field.mainnetAddress.help=Bitcoin mainnet address where you will receive sponsoring payments (0.046 BTC excluding fees per sponsoring)
presentation.validation.invalidAddress=Invalid Bitcoin address (must start with 1, 3 or bc1)
presentation.fallback.user=User
# Filters
filters.clear=Clear all