i18n/a11y polish: translate StashPage, honest Pro-activation copy, contrast

- StashPage: Friends only / Add to Wishlist / Add Item / Edit Item / Public
  stash / Uploading… / Add Photos / placeholders / item count / list-grid aria
  labels now go through t() (t added to the sub-components).
- Manual Pro copy no longer claims the pass is already active: it says the pass
  activates once the provider confirms the payment.
- Bumped low-contrast gray-600 guidance text to gray-400 (DM empty state, E2EE
  note, stash item count).
This commit is contained in:
SkateSpots 2026-10-03 10:03:48 +07:00
parent 4d32622443
commit 52eff36bce
5 changed files with 47 additions and 18 deletions

View file

@ -64,9 +64,9 @@ const sasquatchUi = {
perk2: 'Action drafts: list gear, post a skate status, plan a trip — you confirm before anything publishes',
perk3: 'Answers use your personal context, not just public data',
perk4: '⚡ Directly support SkateSpots & keep it free and ad-free',
welcomeTitle: 'Welcome to Sasquatch Pro! 🦧⚡',
welcomeTitle: 'Thanks for supporting Sasquatch Pro! 🦧⚡',
welcomeDesc:
'Your pass is active — a receipt just landed in your DMs. Thanks for keeping the streets free.',
'Payment sent — your pass activates once the provider confirms it (usually within a minute). Thanks for keeping the streets free.',
invoiceCopied: 'Invoice copied',
dialogDesc: 'Supercharge your AI skate guide. 100% funds a free, no-ads community app.',
proSupporter: "You're a Pro supporter ⚡",
@ -195,9 +195,9 @@ const sasquatchUi = {
perk2: 'Borradores de acciones: publica material, comparte tu estado de skate, planifica un viaje — tú confirmas antes de que se publique nada',
perk3: 'Las respuestas usan tu contexto personal, no solo datos públicos',
perk4: '⚡ Apoya directamente a SkateSpots y ayúdanos a mantenerla gratis y sin anuncios',
welcomeTitle: '¡Bienvenido a Sasquatch Pro! 🦧⚡',
welcomeTitle: '¡Gracias por apoyar Sasquatch Pro! 🦧⚡',
welcomeDesc:
'Tu pase está activo — acaba de llegar un recibo a tus MD. Gracias por mantener las calles libres.',
'Pago enviado: tu pase se activa cuando el proveedor lo confirme (normalmente en un minuto). Gracias por mantener las calles libres.',
invoiceCopied: 'Factura copiada',
dialogDesc: 'Potencia tu guía de skate con IA. El 100 % financia una app comunitaria gratis y sin anuncios.',
proSupporter: 'Eres un colaborador Pro ⚡',

View file

@ -6285,6 +6285,19 @@ walletUi: {
sortValue: 'Value',
sortCondition: 'Condition',
sortAz: 'A–Z',
friendsOnly: 'Friends only',
addToWishlist: 'Add to Wishlist',
addItemToStash: 'Add Item to Stash',
addItem: 'Add Item',
editItem: 'Edit Item',
publicStash: 'Public stash',
listView: 'List view',
gridView: 'Grid view',
uploading: 'Uploading...',
addPhotos: 'Add Photos',
wishlistReqPlaceholder: 'Any specific requirements...',
itemDescPlaceholder: 'Describe the item, any damage...',
itemsCount: '{{count}} item(s)',
wishlistEmpty: 'Wishlist Empty',
wishlistEmptyDesc: "Add gear you're hunting for — we'll match you with friends' stashes.",
stashEmpty: 'Your Stash is Empty',

View file

@ -6285,6 +6285,19 @@ walletUi: {
sortValue: 'Valor',
sortCondition: 'Estado',
sortAz: 'A–Z',
friendsOnly: 'Solo amigos',
addToWishlist: 'Añadir a la lista de deseos',
addItemToStash: 'Añadir artículo al stash',
addItem: 'Añadir artículo',
editItem: 'Editar artículo',
publicStash: 'Stash público',
listView: 'Vista de lista',
gridView: 'Vista de cuadrícula',
uploading: 'Subiendo...',
addPhotos: 'Añadir fotos',
wishlistReqPlaceholder: '¿Algún requisito concreto?',
itemDescPlaceholder: 'Describe el artículo, daños...',
itemsCount: '{{count}} artículo(s)',
wishlistEmpty: 'Lista de deseos vacía',
wishlistEmptyDesc: 'Añade el material que buscas — te emparejaremos con los stashes de tus amigos.',
stashEmpty: 'Tu stash está vacío',

View file

@ -434,7 +434,7 @@ export default function ConversationPage() {
) : (
<div className="text-center py-12">
<p className="text-gray-500">{t('socialUi.dms.emptyTitle')}</p>
<p className="text-gray-600 text-sm mt-1">
<p className="text-gray-400 text-sm mt-1">
{t('socialUi.conversation.startThread')}
</p>
</div>
@ -466,7 +466,7 @@ export default function ConversationPage() {
)}
</Button>
</div>
<p className="text-[10px] sm:text-xs text-gray-600 mt-1.5 sm:mt-2 text-center hidden sm:block">
<p className="text-[10px] sm:text-xs text-gray-400 mt-1.5 sm:mt-2 text-center hidden sm:block">
{t('socialUi.conversation.e2eeNote')}
</p>
{/* A 1:1 NIP-17 DM encrypts + signs to the recipient AND to yourself

View file

@ -185,6 +185,7 @@ function StashItemCard({ item, onEdit, onDelete, onListOnMarketplace, isWTB = fa
item: StashItem; onEdit: () => void; onDelete: () => void; onListOnMarketplace?: () => void; isWTB?: boolean;
selectable?: boolean; selected?: boolean; onToggleSelect?: () => void;
}) {
const { t } = useTranslation();
const category = STASH_CATEGORIES[item.category];
const condition = CONDITION_LABELS[item.condition];
const rarity = (item.rarity || category.rarity) as keyof typeof RARITY_COLORS;
@ -279,7 +280,7 @@ function StashItemCard({ item, onEdit, onDelete, onListOnMarketplace, isWTB = fa
)}
<div className="flex items-center gap-1">
{item.visibility === 'private' && <Tooltip><TooltipTrigger><Lock className="h-3.5 w-3.5 text-red-400/60" /></TooltipTrigger><TooltipContent>Private</TooltipContent></Tooltip>}
{item.visibility === 'friends' && <Tooltip><TooltipTrigger><Users className="h-3.5 w-3.5 text-blue-400/60" /></TooltipTrigger><TooltipContent>Friends only</TooltipContent></Tooltip>}
{item.visibility === 'friends' && <Tooltip><TooltipTrigger><Users className="h-3.5 w-3.5 text-blue-400/60" /></TooltipTrigger><TooltipContent>{t('walletUi.stash.friendsOnly')}</TooltipContent></Tooltip>}
{item.willingToTrade && <Badge variant="outline" className="text-[9px] h-5 px-1 border-cyan-500/40 text-cyan-400"><ArrowRightLeft className="h-2.5 w-2.5" /></Badge>}
{item.willingToSell && !isWTB && <Badge variant="outline" className="text-[9px] h-5 px-1 border-green-500/40 text-green-400"><Zap className="h-2.5 w-2.5" /></Badge>}
</div>
@ -291,6 +292,7 @@ function StashItemCard({ item, onEdit, onDelete, onListOnMarketplace, isWTB = fa
// ─── Add Item Dialog ──────────────────────────────────────────────────────────
function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit<StashItem, 'id' | 'addedAt'>) => void; mode?: 'stash' | 'wtb'; trigger?: React.ReactNode }) {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const isWTB = mode === 'wtb';
const fileInputRef = useRef<HTMLInputElement>(null);
@ -336,7 +338,7 @@ function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit<
};
const defaultTrigger = isWTB ? (
<Button variant="outline" className="gap-2 border-cyan-500/50 text-cyan-400 hover:bg-cyan-500/10"><Heart className="h-4 w-4" />Add to Wishlist</Button>
<Button variant="outline" className="gap-2 border-cyan-500/50 text-cyan-400 hover:bg-cyan-500/10"><Heart className="h-4 w-4" />{t('walletUi.stash.addToWishlist')}</Button>
) : (
<Button className="gap-2 bg-gradient-to-r from-[#ff0040] to-[#ff6600] hover:opacity-90 border-0 shadow-lg shadow-[#ff0040]/20"><Plus className="h-4 w-4" />Add to Stash</Button>
);
@ -347,7 +349,7 @@ function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit<
<DialogContent className="max-w-lg max-h-[90vh] overflow-y-auto bg-black/95 border-2 border-[#ff6600]/40">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-white">
{isWTB ? <><Heart className="h-5 w-5 text-cyan-400" />Add to Wishlist</> : <><Package className="h-5 w-5 text-[#ff6600]" />Add Item to Stash</>}
{isWTB ? <><Heart className="h-5 w-5 text-cyan-400" />{t('walletUi.stash.addToWishlist')}</> : <><Package className="h-5 w-5 text-[#ff6600]" />{t('walletUi.stash.addItemToStash')}</>}
</DialogTitle>
<DialogDescription className="text-muted-foreground">
{isWTB ? "Add items you're looking for." : "Add gear to your encrypted private inventory."}
@ -386,7 +388,7 @@ function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit<
</div>
<div className="space-y-2">
<Label>{isWTB ? 'Additional Notes' : 'Description'}</Label>
<Textarea placeholder={isWTB ? "Any specific requirements..." : "Describe the item, any damage..."} value={formData.description || ''} onChange={(e) => setFormData({ ...formData, description: e.target.value })} rows={3} className="bg-black/50 border-white/20" />
<Textarea placeholder={isWTB ? t('walletUi.stash.wishlistReqPlaceholder') : t('walletUi.stash.itemDescPlaceholder')} value={formData.description || ''} onChange={(e) => setFormData({ ...formData, description: e.target.value })} rows={3} className="bg-black/50 border-white/20" />
</div>
{!isWTB && (
<div className="space-y-2">
@ -404,7 +406,7 @@ function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit<
)}
<input ref={fileInputRef} type="file" accept="image/*" multiple onChange={handleImageUpload} className="hidden" />
<Button type="button" variant="outline" className="w-full border-dashed border-white/30 hover:border-[#ff6600]/50 hover:bg-[#ff6600]/5" onClick={() => fileInputRef.current?.click()} disabled={isUploading}>
{isUploading ? <><Loader2 className="h-4 w-4 mr-2 animate-spin" />Uploading...</> : <><ImagePlus className="h-4 w-4 mr-2" />Add Photos</>}
{isUploading ? <><Loader2 className="h-4 w-4 mr-2 animate-spin" />{t('walletUi.stash.uploading')}</> : <><ImagePlus className="h-4 w-4 mr-2" />{t('walletUi.stash.addPhotos')}</>}
</Button>
</div>
</div>
@ -460,7 +462,7 @@ function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit<
<DialogFooter>
<DialogClose asChild><Button variant="outline" className="border-white/20">Cancel</Button></DialogClose>
<Button onClick={handleSubmit} disabled={!formData.title || isUploading} className={cn(isWTB ? "bg-cyan-500 hover:bg-cyan-600 text-black" : "bg-[#ff6600] hover:bg-[#ff6600]/90")}>
{isWTB ? <><Heart className="h-4 w-4 mr-2" />Add to Wishlist</> : <><Plus className="h-4 w-4 mr-2" />Add Item</>}
{isWTB ? <><Heart className="h-4 w-4 mr-2" />{t('walletUi.stash.addToWishlist')}</> : <><Plus className="h-4 w-4 mr-2" />{t('walletUi.stash.addItem')}</>}
</Button>
</DialogFooter>
</DialogContent>
@ -470,6 +472,7 @@ function AddItemDialog({ onAdd, mode = 'stash', trigger }: { onAdd: (item: Omit<
// ─── Edit Item Dialog ─────────────────────────────────────────────────────────
function EditItemDialog({ item, open, onOpenChange, onSave }: { item: StashItem; open: boolean; onOpenChange: (open: boolean) => void; onSave: (item: StashItem) => void }) {
const { t } = useTranslation();
const fileInputRef = useRef<HTMLInputElement>(null);
const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile();
const isWTB = item.itemType === 'wtb';
@ -501,7 +504,7 @@ function EditItemDialog({ item, open, onOpenChange, onSave }: { item: StashItem;
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg max-h-[90vh] overflow-y-auto bg-black/95 border-2 border-[#ff6600]/40">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-white"><Edit3 className="h-5 w-5 text-[#ff6600]" />Edit Item</DialogTitle>
<DialogTitle className="flex items-center gap-2 text-white"><Edit3 className="h-5 w-5 text-[#ff6600]" />{t('walletUi.stash.editItem')}</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
@ -548,7 +551,7 @@ function EditItemDialog({ item, open, onOpenChange, onSave }: { item: StashItem;
)}
<input ref={fileInputRef} type="file" accept="image/*" multiple onChange={handleImageUpload} className="hidden" />
<Button type="button" variant="outline" className="w-full border-dashed border-white/30" onClick={() => fileInputRef.current?.click()} disabled={isUploading}>
{isUploading ? <><Loader2 className="h-4 w-4 mr-2 animate-spin" />Uploading...</> : <><ImagePlus className="h-4 w-4 mr-2" />Add Photos</>}
{isUploading ? <><Loader2 className="h-4 w-4 mr-2 animate-spin" />{t('walletUi.stash.uploading')}</> : <><ImagePlus className="h-4 w-4 mr-2" />{t('walletUi.stash.addPhotos')}</>}
</Button>
</div>
</div>
@ -958,7 +961,7 @@ export default function StashPage() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Globe className="h-4 w-4 text-emerald-400" />
<span className="text-[10px] font-black uppercase tracking-widest text-emerald-300">Public stash</span>
<span className="text-[10px] font-black uppercase tracking-widest text-emerald-300">{t('walletUi.stash.publicStash')}</span>
</div>
<span className="text-2xl font-black text-emerald-400">{publicItems.length}</span>
</div>
@ -1078,8 +1081,8 @@ export default function StashPage() {
{/* View toggle — only useful when there are items to view */}
{stats.total > 0 && (
<div className="flex items-center rounded-lg border border-white/10 overflow-hidden">
<button aria-label="List view" onClick={() => setViewMode('list')} className={cn('px-2.5 py-1.5 transition-colors', viewMode === 'list' ? 'bg-white/12 text-white' : 'text-gray-400 hover:text-gray-300')}><List className="h-3.5 w-3.5" /></button>
<button aria-label="Grid view" onClick={() => setViewMode('grid')} className={cn('px-2.5 py-1.5 transition-colors', viewMode === 'grid' ? 'bg-white/12 text-white' : 'text-gray-400 hover:text-gray-300')}><LayoutGrid className="h-3.5 w-3.5" /></button>
<button aria-label={t('walletUi.stash.listView')} onClick={() => setViewMode('list')} className={cn('px-2.5 py-1.5 transition-colors', viewMode === 'list' ? 'bg-white/12 text-white' : 'text-gray-400 hover:text-gray-300')}><List className="h-3.5 w-3.5" /></button>
<button aria-label={t('walletUi.stash.gridView')} onClick={() => setViewMode('grid')} className={cn('px-2.5 py-1.5 transition-colors', viewMode === 'grid' ? 'bg-white/12 text-white' : 'text-gray-400 hover:text-gray-300')}><LayoutGrid className="h-3.5 w-3.5" /></button>
</div>
)}
{/* Mobile-only quick add — desktop has sidebar buttons */}
@ -1159,7 +1162,7 @@ export default function StashPage() {
})}
</div>
)}
<span className="ml-auto text-xs text-gray-500 hidden lg:block flex-shrink-0">{filteredItems.length} item{filteredItems.length !== 1 ? 's' : ''}</span>
<span className="ml-auto text-xs text-gray-400 hidden lg:block flex-shrink-0">{t('walletUi.stash.itemsCount', { count: filteredItems.length })}</span>
</div>
</div>